Repository: pingcap/tidb
File: pkg/bindinfo/binding.go (Go)

Code before:
}

func matchSQLBindingForPlanCache(sctx sessionctx.Context, stmtNode ast.StmtNode, info *BindingMatchInfo) (binding *Binding, matched bool, scope string) {
	sessionVars := sctx.GetSessionVars()
	defer func(begin time.Time) {
		sessionVars.DurationOptimizer.BindingMatch = time.Since(begin)
	}(time.Now())
	useBinding := sessionVars.UsePlanBaselines
	if !useBinding || stmtNode == nil {
		return
	}
	// When the domain is initializing, the bind will be nil.
	if sctx.Value(SessionBindInfoKeyType) == nil {
		return
	}
	cache := getMatchSQLBindingCache(sessionVars, stmtNode)
	if intest.InTest {
		binding, matched, scope = matchSQLBindingInternal(sctx, sessionVars, stmtNode, info)
		assertMatchSQLBinding(cache, matched, binding, scope)
		return
	}
	if cache != nil {
		return cache.binding, cache.matched, cache.scope
	}
	return matchSQLBindingInternal(sctx, sessionVars, stmtNode, info)
}

func matchSQLBindingInternal(sctx sessionctx.Context, sessionVars *variable.SessionVars, stmtNode ast.StmtNode, info *BindingMatchInfo) (binding *Binding, matched bool, scope string) {
	// record the normalization result into info to avoid repeat normalization next time.
	var noDBDigest string
	var tableNames []*ast.TableName
	if info == nil || info.TableNames == nil || info.NoDBDigest == "" {
		_, noDBDigest = NormalizeStmtForBinding(stmtNode, "", true)
		tableNames = CollectTableNames(stmtNode)
		if info != nil {
			info.NoDBDigest = noDBDigest
			info.TableNames = tableNames
		}
	} else {
		noDBDigest = info.NoDBDigest
		tableNames = info.TableNames
	}

	sessionHandle := sctx.Value(SessionBindInfoKeyType).(SessionBindingHandle)
	if binding, matched := sessionHandle.MatchSessionBinding(sctx, noDBDigest, tableNames); matched {
		setMatchSQLBindingCache(sessionVars, stmtNode, matched, binding, metrics.ScopeSession)
		return binding, matched, metrics.ScopeSession
	}
	globalHandle := GetBindingHandle(sctx)
	if globalHandle == nil {
		return

Review comment (performance):
`matchSQLBindingForPlanCache` is now almost a copy of `matchSQLBinding` (same checks, cache lookup, timing). This duplication risks the two paths drifting over time; consider factoring the shared logic into a single helper that accepts an optional `*BindingMatchInfo` (nil for the normal path).

Code after:
	} else {
		noDBDigest = info.NoDBDigest
		tableNames = info.TableNames
	}

	sessionHandle := sctx.Value(SessionBindInfoKeyType).(SessionBindingHandle)
	if binding, matched := sessionHandle.MatchSessionBinding(sctx, noDBDigest, tableNames); matched {
		setMatchSQLBindingCache(sessionVars, stmtNode, matched, binding, metrics.ScopeSession)
		return binding, matched, metrics.ScopeSession
	}
	globalHandle := GetBindingHandle(sctx)
	if globalHandle == nil {
		return
	}
	binding, matched = globalHandle.MatchingBinding(sctx, noDBDigest, tableNames)
	if matched {
		if vardef.EnableBindingUsage.Load() {
			// After hitting the cache, update the usage time of the bind.
			binding.UpdateLastUsedAt()
		}
		setMatchSQLBindingCache(sessionVars, stmtNode, matched, binding, metrics.ScopeGlobal)
		return binding, matched, metrics.ScopeGlobal
	}
	setMatchSQLBindingCache(sessionVars, stmtNode, false, nil, "")
	return
}

func assertMatchSQLBinding(cache *BindingCacheItem, hit bool, binding *Binding, scope string) {
	intest.AssertFunc(func() bool {
		if cache == nil {
			return true
		}
		if hit {
			return cache.matched &&
				cache.binding == binding &&
				cache.scope == scope
		}
		return cache == nil || !cache.matched
	})
}

// BindingCacheItem is to cache the bindinfo to avoid getting bindinfo from bind cache again.
type BindingCacheItem struct {
	binding *Binding
	matched bool
	scope   string
}

func noDBDigestFromBinding(binding *Binding) (string, error) {
	p := parser.New()
	stmt, err := p.ParseOneStmt(binding.BindSQL, binding.Charset, binding.Collation)