Skip to content

Commit

Permalink
planner: wait until binding is loaded into memory if binding cache mi…
Browse files Browse the repository at this point in the history
…ss (#51400)

ref #51347
  • Loading branch information
hawkingrei authored Mar 6, 2024
1 parent 748dbc9 commit 0878b15
Show file tree
Hide file tree
Showing 8 changed files with 343 additions and 185 deletions.
2 changes: 2 additions & 0 deletions pkg/bindinfo/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ go_library(
"//pkg/util/table-filter",
"@com_github_ngaut_pools//:pools",
"@com_github_pingcap_errors//:errors",
"@com_github_pingcap_failpoint//:failpoint",
"@org_golang_x_sync//singleflight",
"@org_uber_go_zap//:zap",
],
)
Expand Down
65 changes: 56 additions & 9 deletions pkg/bindinfo/binding_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@ import (
"errors"
"sync"

"github.com/pingcap/failpoint"
"github.com/pingcap/tidb/pkg/bindinfo/norm"
"github.com/pingcap/tidb/pkg/parser"
"github.com/pingcap/tidb/pkg/parser/ast"
"github.com/pingcap/tidb/pkg/sessionctx"
"github.com/pingcap/tidb/pkg/sessionctx/variable"
"github.com/pingcap/tidb/pkg/util/hack"
"github.com/pingcap/tidb/pkg/util/kvcache"
"github.com/pingcap/tidb/pkg/util/logutil"
"github.com/pingcap/tidb/pkg/util/mathutil"
"github.com/pingcap/tidb/pkg/util/memory"
"go.uber.org/zap"
)

// FuzzyBindingCache is based on BindingCache, and provide some more advanced features, like
Expand All @@ -52,24 +55,40 @@ type fuzzyBindingCache struct {
fuzzy2SQLDigests map[string][]string // fuzzyDigest --> sqlDigests

sql2FuzzyDigest map[string]string // sqlDigest --> fuzzyDigest

// loadBindingFromStorageFunc is used to load binding from storage if cache miss.
loadBindingFromStorageFunc func(sqlDigest string) (Bindings, error)
}

func newFuzzyBindingCache() FuzzyBindingCache {
func newFuzzyBindingCache(loadBindingFromStorageFunc func(string) (Bindings, error)) FuzzyBindingCache {
return &fuzzyBindingCache{
BindingCache: newBindCache(),
fuzzy2SQLDigests: make(map[string][]string),
sql2FuzzyDigest: make(map[string]string),
BindingCache: newBindCache(),
fuzzy2SQLDigests: make(map[string][]string),
sql2FuzzyDigest: make(map[string]string),
loadBindingFromStorageFunc: loadBindingFromStorageFunc,
}
}

func (fbc *fuzzyBindingCache) FuzzyMatchingBinding(sctx sessionctx.Context, fuzzyDigest string, tableNames []*ast.TableName) (matchedBinding Binding, isMatched bool) {
matchedBinding, isMatched, missingSQLDigest := fbc.getFromMemory(sctx, fuzzyDigest, tableNames)
if len(missingSQLDigest) == 0 {
return
}
if fbc.loadBindingFromStorageFunc == nil {
return
}
fbc.loadFromStore(missingSQLDigest) // loadFromStore's SetBinding has a Mutex inside, so it's safe to call it without lock
matchedBinding, isMatched, _ = fbc.getFromMemory(sctx, fuzzyDigest, tableNames)
return
}

func (fbc *fuzzyBindingCache) getFromMemory(sctx sessionctx.Context, fuzzyDigest string, tableNames []*ast.TableName) (matchedBinding Binding, isMatched bool, missingSQLDigest []string) {
fbc.mu.RLock()
defer fbc.mu.RUnlock()
bindingCache := fbc.BindingCache
if bindingCache.Size() == 0 {
return
}

leastWildcards := len(tableNames) + 1
enableFuzzyBinding := sctx.GetSessionVars().EnableFuzzyBinding
for _, sqlDigest := range fbc.fuzzy2SQLDigests[fuzzyDigest] {
Expand All @@ -86,9 +105,33 @@ func (fbc *fuzzyBindingCache) FuzzyMatchingBinding(sctx sessionctx.Context, fuzz
break
}
}
} else {
missingSQLDigest = append(missingSQLDigest, sqlDigest)
}
}
return matchedBinding, isMatched, missingSQLDigest
}

func (fbc *fuzzyBindingCache) loadFromStore(missingSQLDigest []string) {
for _, sqlDigest := range missingSQLDigest {
bindings, err := fbc.loadBindingFromStorageFunc(sqlDigest)
if err != nil {
logutil.BgLogger().Warn("loadBindingFromStorageFunc binding failed", zap.String("sqlDigest", sqlDigest), zap.Error(err))
continue
}
// put binding into the cache
oldBinding := fbc.GetBinding(sqlDigest)
newBindings := removeDeletedBindings(merge(oldBinding, bindings))
if len(newBindings) > 0 {
err = fbc.SetBinding(sqlDigest, newBindings)
if err != nil {
// When the memory capacity of bing_cache is not enough,
// there will be some memory-related errors in multiple places.
// Only needs to be handled once.
logutil.BgLogger().Warn("BindHandle.Update", zap.String("category", "sql-bind"), zap.Error(err))
}
}
}
return
}

func (fbc *fuzzyBindingCache) SetBinding(sqlDigest string, bindings Bindings) (err error) {
Expand Down Expand Up @@ -153,9 +196,10 @@ func (fbc *fuzzyBindingCache) Copy() (c FuzzyBindingCache, err error) {
fuzzy2SQLDigests[k] = newList
}
return &fuzzyBindingCache{
BindingCache: bc,
fuzzy2SQLDigests: fuzzy2SQLDigests,
sql2FuzzyDigest: sql2FuzzyDigest,
BindingCache: bc,
fuzzy2SQLDigests: fuzzy2SQLDigests,
sql2FuzzyDigest: sql2FuzzyDigest,
loadBindingFromStorageFunc: fbc.loadBindingFromStorageFunc,
}, nil
}

Expand Down Expand Up @@ -273,6 +317,9 @@ func (c *bindingCache) delete(key bindingCacheKey) bool {
// The return value is not read-only, but it shouldn't be changed in the caller functions.
// The function is thread-safe.
func (c *bindingCache) GetBinding(sqlDigest string) Bindings {
failpoint.Inject("get_binding_return_nil", func(_ failpoint.Value) {
failpoint.Return(nil)
})
c.lock.Lock()
defer c.lock.Unlock()
return c.get(bindingCacheKey(sqlDigest))
Expand Down
2 changes: 1 addition & 1 deletion pkg/bindinfo/binding_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func bindingFuzzyDigest(t *testing.T, b Binding) string {
}

func TestFuzzyBindingCache(t *testing.T) {
fbc := newFuzzyBindingCache().(*fuzzyBindingCache)
fbc := newFuzzyBindingCache(nil).(*fuzzyBindingCache)
b1 := Binding{BindSQL: "SELECT * FROM db1.t1", SQLDigest: "b1"}
fDigest1 := bindingFuzzyDigest(t, b1)
b2 := Binding{BindSQL: "SELECT * FROM db2.t1", SQLDigest: "b2"}
Expand Down
168 changes: 0 additions & 168 deletions pkg/bindinfo/fuzzy_binding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,6 @@ func showBinding(tk *testkit.TestKit, showStmt string) [][]any {
return result
}

func removeAllBindings(tk *testkit.TestKit, global bool) {
scope := "session"
if global {
scope = "global"
}
res := showBinding(tk, fmt.Sprintf("show %v bindings", scope))
for _, r := range res {
if r[4] == "builtin" {
continue
}
tk.MustExec(fmt.Sprintf("drop %v binding for sql digest '%v'", scope, r[5]))
}
tk.MustQuery(fmt.Sprintf("show %v bindings", scope)).Check(testkit.Rows()) // empty
}

func TestFuzzyBindingBasic(t *testing.T) {
store := testkit.CreateMockStore(t)
tk1 := testkit.NewTestKit(t, store)
Expand Down Expand Up @@ -355,156 +340,3 @@ func TestFuzzyBindingPlanCache(t *testing.T) {
tk.MustExec(`execute stmt using @v`)
hasPlan("IndexFullScan", "index:c(c)")
}

func TestFuzzyBindingHints(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec(`use test`)

for _, db := range []string{"db1", "db2", "db3"} {
tk.MustExec(`create database ` + db)
tk.MustExec(`use ` + db)
tk.MustExec(`create table t1 (a int, b int, c int, d int, key(a), key(b), key(c), key(d))`)
tk.MustExec(`create table t2 (a int, b int, c int, d int, key(a), key(b), key(c), key(d))`)
tk.MustExec(`create table t3 (a int, b int, c int, d int, key(a), key(b), key(c), key(d))`)
}
tk.MustExec(`set @@tidb_opt_enable_fuzzy_binding=1`)

for _, c := range []struct {
binding string
qTemplate string
}{
// use index
{`create global binding using select /*+ use_index(t1, c) */ * from *.t1 where a=1`,
`select * from %st1 where a=1000`},
{`create global binding using select /*+ use_index(t1, c) */ * from *.t1 where d<1`,
`select * from %st1 where d<10000`},
{`create global binding using select /*+ use_index(t1, c) */ * from *.t1, *.t2 where t1.d<1`,
`select * from %st1, t2 where t1.d<100`},
{`create global binding using select /*+ use_index(t1, c) */ * from *.t1, *.t2 where t1.d<1`,
`select * from t1, %st2 where t1.d<100`},
{`create global binding using select /*+ use_index(t1, c), use_index(t2, a) */ * from *.t1, *.t2 where t1.d<1`,
`select * from %st1, t2 where t1.d<100`},
{`create global binding using select /*+ use_index(t1, c), use_index(t2, a) */ * from *.t1, *.t2 where t1.d<1`,
`select * from t1, %st2 where t1.d<100`},
{`create global binding using select /*+ use_index(t1, c), use_index(t2, a) */ * from *.t1, *.t2, *.t3 where t1.d<1`,
`select * from %st1, t2, t3 where t1.d<100`},
{`create global binding using select /*+ use_index(t1, c), use_index(t2, a) */ * from *.t1, *.t2, *.t3 where t1.d<1`,
`select * from t1, t2, %st3 where t1.d<100`},

// ignore index
{`create global binding using select /*+ ignore_index(t1, b) */ * from *.t1 where b=1`,
`select * from %st1 where b=1000`},
{`create global binding using select /*+ ignore_index(t1, b) */ * from *.t1 where b>1`,
`select * from %st1 where b>1000`},
{`create global binding using select /*+ ignore_index(t1, b) */ * from *.t1 where b in (1,2)`,
`select * from %st1 where b in (1)`},
{`create global binding using select /*+ ignore_index(t1, b) */ * from *.t1 where b in (1,2)`,
`select * from %st1 where b in (1,2,3,4,5)`},

// order index hint
{`create global binding using select /*+ order_index(t1, a) */ a from *.t1 where a<10 order by a limit 10`,
`select a from %st1 where a<10000 order by a limit 10`},
{`create global binding using select /*+ order_index(t1, b) */ b from *.t1 where b>10 order by b limit 1111`,
`select b from %st1 where b>2 order by b limit 10`},

// no order index hint
{`create global binding using select /*+ no_order_index(t1, c) */ c from *.t1 where c<10 order by c limit 10`,
`select c from %st1 where c<10000 order by c limit 10`},
{`create global binding using select /*+ no_order_index(t1, d) */ d from *.t1 where d>10 order by d limit 1111`,
`select d from %st1 where d>2 order by d limit 10`},

// agg hint
{`create global binding using select /*+ hash_agg() */ count(*) from *.t1 group by a`,
`select count(*) from %st1 group by a`},
{`create global binding using select /*+ stream_agg() */ count(*) from *.t1 group by b`,
`select count(*) from %st1 group by b`},

// to_cop hint
{`create global binding using select /*+ agg_to_cop() */ sum(a) from *.t1`,
`select sum(a) from %st1`},
{`create global binding using select /*+ limit_to_cop() */ a from *.t1 limit 10`,
`select a from %st1 limit 101`},

// index merge hint
{`create global binding using select /*+ use_index_merge(t1, c, d) */ * from *.t1 where c=1 or d=1`,
`select * from %st1 where c=1000 or d=1000`},
{`create global binding using select /*+ no_index_merge() */ * from *.t1 where a=1 or b=1`,
`select * from %st1 where a=1000 or b=1000`},

// join type hint
{`create global binding using select /*+ hash_join(t1) */ * from *.t1, *.t2 where t1.a=t2.a`,
`select * from %st1, t2 where t1.a=t2.a`},
{`create global binding using select /*+ hash_join(t2) */ * from *.t1, *.t2 where t1.a=t2.a`,
`select * from t1, %st2 where t1.a=t2.a`},
{`create global binding using select /*+ hash_join(t2) */ * from *.t1, *.t2, *.t3 where t1.a=t2.a and t3.b=t2.b`,
`select * from t1, %st2, t3 where t1.a=t2.a and t3.b=t2.b`},
{`create global binding using select /*+ hash_join_build(t1) */ * from *.t1, *.t2 where t1.a=t2.a`,
`select * from t1, %st2 where t1.a=t2.a`},
{`create global binding using select /*+ hash_join_probe(t1) */ * from *.t1, *.t2 where t1.a=t2.a`,
`select * from t1, %st2 where t1.a=t2.a`},
{`create global binding using select /*+ merge_join(t1) */ * from *.t1, *.t2 where t1.a=t2.a`,
`select * from %st1, t2 where t1.a=t2.a`},
{`create global binding using select /*+ merge_join(t2) */ * from *.t1, *.t2 where t1.a=t2.a`,
`select * from t1, %st2 where t1.a=t2.a`},
{`create global binding using select /*+ merge_join(t2) */ * from *.t1, *.t2, *.t3 where t1.a=t2.a and t3.b=t2.b`,
`select * from t1, %st2, t3 where t1.a=t2.a and t3.b=t2.b`},
{`create global binding using select /*+ inl_join(t1) */ * from *.t1, *.t2 where t1.a=t2.a`,
`select * from %st1, t2 where t1.a=t2.a`},
{`create global binding using select /*+ inl_join(t2) */ * from *.t1, *.t2 where t1.a=t2.a`,
`select * from t1, %st2 where t1.a=t2.a`},
{`create global binding using select /*+ inl_join(t2) */ * from *.t1, *.t2, *.t3 where t1.a=t2.a and t3.b=t2.b`,
`select * from t1, %st2, t3 where t1.a=t2.a and t3.b=t2.b`},

// no join type hint
{`create global binding using select /*+ no_hash_join(t1) */ * from *.t1, *.t2 where t1.b=t2.b`,
`select * from %st1, t2 where t1.b=t2.b`},
{`create global binding using select /*+ no_hash_join(t2) */ * from *.t1, *.t2 where t1.c=t2.c`,
`select * from t1, %st2 where t1.c=t2.c`},
{`create global binding using select /*+ no_hash_join(t2) */ * from *.t1, *.t2, *.t3 where t1.a=t2.a and t3.b=t2.b`,
`select * from t1, %st2, t3 where t1.a=t2.a and t3.b=t2.b`},
{`create global binding using select /*+ no_merge_join(t1) */ * from *.t1, *.t2 where t1.b=t2.b`,
`select * from %st1, t2 where t1.b=t2.b`},
{`create global binding using select /*+ no_merge_join(t2) */ * from *.t1, *.t2 where t1.c=t2.c`,
`select * from t1, %st2 where t1.c=t2.c`},
{`create global binding using select /*+ no_merge_join(t2) */ * from *.t1, *.t2, *.t3 where t1.a=t2.a and t3.b=t2.b`,
`select * from t1, %st2, t3 where t1.a=t2.a and t3.b=t2.b`},
{`create global binding using select /*+ no_index_join(t1) */ * from *.t1, *.t2 where t1.b=t2.b`,
`select * from %st1, t2 where t1.b=t2.b`},
{`create global binding using select /*+ no_index_join(t2) */ * from *.t1, *.t2 where t1.c=t2.c`,
`select * from t1, %st2 where t1.c=t2.c`},
{`create global binding using select /*+ no_index_join(t2) */ * from *.t1, *.t2, *.t3 where t1.a=t2.a and t3.b=t2.b`,
`select * from t1, %st2, t3 where t1.a=t2.a and t3.b=t2.b`},

// join order hint
{`create global binding using select /*+ leading(t2) */ * from *.t1, *.t2 where t1.b=t2.b`,
`select * from %st1, t2 where t1.b=t2.b`},
{`create global binding using select /*+ leading(t2) */ * from *.t1, *.t2 where t1.c=t2.c`,
`select * from t1, %st2 where t1.c=t2.c`},
{`create global binding using select /*+ leading(t2, t1) */ * from *.t1, *.t2 where t1.c=t2.c`,
`select * from t1, %st2 where t1.c=t2.c`},
{`create global binding using select /*+ leading(t1, t2) */ * from *.t1, *.t2 where t1.c=t2.c`,
`select * from t1, %st2 where t1.c=t2.c`},
{`create global binding using select /*+ leading(t1) */ * from *.t1, *.t2, *.t3 where t1.a=t2.a and t3.b=t2.b`,
`select * from t1, %st2, t3 where t1.a=t2.a and t3.b=t2.b`},
{`create global binding using select /*+ leading(t2) */ * from *.t1, *.t2, *.t3 where t1.a=t2.a and t3.b=t2.b`,
`select * from t1, %st2, t3 where t1.a=t2.a and t3.b=t2.b`},
{`create global binding using select /*+ leading(t2,t3) */ * from *.t1, *.t2, *.t3 where t1.a=t2.a and t3.b=t2.b`,
`select * from t1, %st2, t3 where t1.a=t2.a and t3.b=t2.b`},
{`create global binding using select /*+ leading(t2,t3,t1) */ * from *.t1, *.t2, *.t3 where t1.a=t2.a and t3.b=t2.b`,
`select * from t1, %st2, t3 where t1.a=t2.a and t3.b=t2.b`},
} {
removeAllBindings(tk, true)
tk.MustExec(c.binding)
for _, currentDB := range []string{"db1", "db2", "db3"} {
tk.MustExec(`use ` + currentDB)
for _, db := range []string{"db1.", "db2.", "db3.", ""} {
query := fmt.Sprintf(c.qTemplate, db)
tk.MustExec(query)
tk.MustQuery(`show warnings`).Check(testkit.Rows()) // no warning
tk.MustExec(query)
tk.MustQuery(`select @@last_plan_from_binding`).Check(testkit.Rows("1"))
}
}
}
}
Loading

0 comments on commit 0878b15

Please sign in to comment.