Skip to content

Commit

Permalink
planner: support creating universal bindings (#49426)
Browse files Browse the repository at this point in the history
ref #48875
  • Loading branch information
qw4990 authored Dec 14, 2023
1 parent 70aad89 commit f532328
Show file tree
Hide file tree
Showing 24 changed files with 216 additions and 109 deletions.
2 changes: 1 addition & 1 deletion pkg/bindinfo/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ go_test(
embed = [":bindinfo"],
flaky = True,
race = "on",
shard_count = 40,
shard_count = 41,
deps = [
"//pkg/bindinfo/internal",
"//pkg/config",
Expand Down
11 changes: 10 additions & 1 deletion pkg/bindinfo/bind_record.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ const (
History = "history"
)

const (
// TypeNormal indicates the binding is a normal binding.
TypeNormal string = ""
// TypeUniversal indicates the binding is a universal binding.
TypeUniversal string = "u"
)

// Binding stores the basic bind hint info.
type Binding struct {
BindSQL string
Expand All @@ -76,6 +83,8 @@ type Binding struct {
ID string `json:"-"`
SQLDigest string
PlanDigest string
// Type indicates the type of this binding, currently only 2 types: "" for normal and "u" for universal bindings.
Type string
}

func (b *Binding) isSame(rb *Binding) bool {
Expand Down Expand Up @@ -182,7 +191,7 @@ func (br *BindRecord) prepareHints(sctx sessionctx.Context) error {
if err != nil {
return err
}
if sctx != nil {
if sctx != nil && bind.Type == TypeNormal {
paramChecker := &paramMarkerChecker{}
stmt.Accept(paramChecker)
if !paramChecker.hasParamMarker {
Expand Down
3 changes: 2 additions & 1 deletion pkg/bindinfo/capture_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ func TestConcurrentCapture(t *testing.T) {
// Simulate an existing binding generated by concurrent CREATE BINDING, which has not been synchronized to current tidb-server yet.
// Actually, it is more common to be generated by concurrent baseline capture, I use Manual just for simpler test verification.
tk.MustExec("insert into mysql.bind_info values('select * from `test` . `t`', 'select * from `test` . `t`', '', 'enabled', '2000-01-01 09:00:00', '2000-01-01 09:00:00', '', '','" +
bindinfo.Manual + "', '', '')")
bindinfo.Manual + "', '', '', '')")
tk.MustQuery("select original_sql, source from mysql.bind_info where source != 'builtin'").Check(testkit.Rows(
"select * from `test` . `t` manual",
))
Expand Down Expand Up @@ -923,6 +923,7 @@ func TestCaptureFilter(t *testing.T) {
}

func TestCaptureHints(t *testing.T) {
t.Skip("deprecated")
store, dom := testkit.CreateMockStoreAndDomain(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec("SET GLOBAL tidb_capture_plan_baselines = on")
Expand Down
8 changes: 5 additions & 3 deletions pkg/bindinfo/global_handle.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ func (h *globalBindingHandle) Update(fullLoad bool) (err error) {
timeCondition = fmt.Sprintf("WHERE update_time>'%s'", lastUpdateTime.String())
}
selectStmt := fmt.Sprintf(`SELECT original_sql, bind_sql, default_db, status, create_time,
update_time, charset, collation, source, sql_digest, plan_digest FROM mysql.bind_info
update_time, charset, collation, source, sql_digest, plan_digest, type FROM mysql.bind_info
%s ORDER BY update_time, create_time`, timeCondition)

return h.callWithSCtx(false, func(sctx sessionctx.Context) error {
Expand Down Expand Up @@ -239,7 +239,7 @@ func (h *globalBindingHandle) Update(fullLoad bool) (err error) {
}

if err != nil {
logutil.BgLogger().Debug("failed to generate bind record from data row", zap.String("category", "sql-bind"), zap.Error(err))
logutil.BgLogger().Warn("failed to generate bind record from data row", zap.String("category", "sql-bind"), zap.Error(err))
continue
}

Expand Down Expand Up @@ -302,7 +302,7 @@ func (h *globalBindingHandle) CreateGlobalBinding(sctx sessionctx.Context, recor
record.Bindings[i].UpdateTime = now

// Insert the BindRecord to the storage.
_, err = exec(sctx, `INSERT INTO mysql.bind_info VALUES (%?,%?, %?, %?, %?, %?, %?, %?, %?, %?, %?)`,
_, err = exec(sctx, `INSERT INTO mysql.bind_info VALUES (%?,%?, %?, %?, %?, %?, %?, %?, %?, %?, %?, %?)`,
record.OriginalSQL,
record.Bindings[i].BindSQL,
record.Db,
Expand All @@ -314,6 +314,7 @@ func (h *globalBindingHandle) CreateGlobalBinding(sctx sessionctx.Context, recor
record.Bindings[i].Source,
record.Bindings[i].SQLDigest,
record.Bindings[i].PlanDigest,
record.Bindings[i].Type,
)
if err != nil {
return err
Expand Down Expand Up @@ -601,6 +602,7 @@ func newBindRecord(sctx sessionctx.Context, row chunk.Row) (string, *BindRecord,
Source: row.GetString(8),
SQLDigest: row.GetString(9),
PlanDigest: row.GetString(10),
Type: row.GetString(11),
}
bindRecord := &BindRecord{
OriginalSQL: row.GetString(0),
Expand Down
55 changes: 50 additions & 5 deletions pkg/bindinfo/global_handle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func TestBindingLastUpdateTimeWithInvalidBind(t *testing.T) {
require.Equal(t, updateTime0, "0000-00-00 00:00:00")

tk.MustExec("insert into mysql.bind_info values('select * from `test` . `t`', 'select * from `test` . `t` use index(`idx`)', 'test', 'enabled', '2000-01-01 09:00:00', '2000-01-01 09:00:00', '', '','" +
bindinfo.Manual + "', '', '')")
bindinfo.Manual + "', '', '', '')")
tk.MustExec("use test")
tk.MustExec("drop table if exists t")
tk.MustExec("create table t(a int)")
Expand Down Expand Up @@ -254,6 +254,51 @@ func TestSetBindingStatus(t *testing.T) {
require.Len(t, rows, 0)
}

// for testing, only returns Original_sql, Bind_sql, Default_db, Status, Source, Type, Sql_digest
func showBinding(tk *testkit.TestKit, showStmt string) [][]interface{} {
rows := tk.MustQuery(showStmt).Sort().Rows()
result := make([][]interface{}, len(rows))
for i, r := range rows {
result[i] = append(result[i], r[:4]...)
result[i] = append(result[i], r[8:11]...)
}
return result
}

func TestCreateUniversalBinding(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)

tk.MustExec(`use test`)
tk.MustExec(`create table t (a int)`)

tk.MustExec(`create global universal binding using select * from t`)
require.Equal(t, showBinding(tk, "show global bindings"),
[][]interface{}{{"select * from `t`", "SELECT * FROM `t`", "", "enabled", "manual", "u", "e5796985ccafe2f71126ed6c0ac939ffa015a8c0744a24b7aee6d587103fd2f7"}})
tk.MustExec(`create global binding using select * from t`)
require.Equal(t, showBinding(tk, "show global bindings"),
[][]interface{}{
{"select * from `t`", "SELECT * FROM `t`", "", "enabled", "manual", "u", "e5796985ccafe2f71126ed6c0ac939ffa015a8c0744a24b7aee6d587103fd2f7"},
{"select * from `test` . `t`", "SELECT * FROM `test`.`t`", "test", "enabled", "manual", "", "8b193b00413fdb910d39073e0d494c96ebf24d1e30b131ecdd553883d0e29b42"}})
tk.MustExec(`drop global binding for sql digest 'e5796985ccafe2f71126ed6c0ac939ffa015a8c0744a24b7aee6d587103fd2f7'`)
require.Equal(t, showBinding(tk, "show global bindings"),
[][]interface{}{
{"select * from `test` . `t`", "SELECT * FROM `test`.`t`", "test", "enabled", "manual", "", "8b193b00413fdb910d39073e0d494c96ebf24d1e30b131ecdd553883d0e29b42"}})

tk.MustExec(`create session universal binding using select * from t`)
require.Equal(t, showBinding(tk, "show session bindings"),
[][]interface{}{{"select * from `t`", "SELECT * FROM `t`", "", "enabled", "manual", "u", "e5796985ccafe2f71126ed6c0ac939ffa015a8c0744a24b7aee6d587103fd2f7"}})
tk.MustExec(`create session binding using select * from t`)
require.Equal(t, showBinding(tk, "show session bindings"),
[][]interface{}{
{"select * from `t`", "SELECT * FROM `t`", "", "enabled", "manual", "u", "e5796985ccafe2f71126ed6c0ac939ffa015a8c0744a24b7aee6d587103fd2f7"},
{"select * from `test` . `t`", "SELECT * FROM `test`.`t`", "test", "enabled", "manual", "", "8b193b00413fdb910d39073e0d494c96ebf24d1e30b131ecdd553883d0e29b42"}})
tk.MustExec(`drop session binding for sql digest 'e5796985ccafe2f71126ed6c0ac939ffa015a8c0744a24b7aee6d587103fd2f7'`)
require.Equal(t, showBinding(tk, "show session bindings"),
[][]interface{}{
{"select * from `test` . `t`", "SELECT * FROM `test`.`t`", "test", "enabled", "manual", "", "8b193b00413fdb910d39073e0d494c96ebf24d1e30b131ecdd553883d0e29b42"}})
}

func TestSetBindingStatusWithoutBindingInCache(t *testing.T) {
store, dom := testkit.CreateMockStoreAndDomain(t)

Expand All @@ -267,9 +312,9 @@ func TestSetBindingStatusWithoutBindingInCache(t *testing.T) {

// Simulate creating bindings on other machines
tk.MustExec("insert into mysql.bind_info values('select * from `test` . `t` where `a` > ?', 'SELECT /*+ USE_INDEX(`t` `idx_a`)*/ * FROM `test`.`t` WHERE `a` > 10', 'test', 'deleted', '2000-01-01 09:00:00', '2000-01-01 09:00:00', '', '','" +
bindinfo.Manual + "', '', '')")
bindinfo.Manual + "', '', '', '')")
tk.MustExec("insert into mysql.bind_info values('select * from `test` . `t` where `a` > ?', 'SELECT /*+ USE_INDEX(`t` `idx_a`)*/ * FROM `test`.`t` WHERE `a` > 10', 'test', 'enabled', '2000-01-02 09:00:00', '2000-01-02 09:00:00', '', '','" +
bindinfo.Manual + "', '', '')")
bindinfo.Manual + "', '', '', '')")
dom.BindHandle().Clear()
tk.MustExec("set binding disabled for select * from t where a > 10")
tk.MustExec("admin reload bindings")
Expand All @@ -282,9 +327,9 @@ func TestSetBindingStatusWithoutBindingInCache(t *testing.T) {

// Simulate creating bindings on other machines
tk.MustExec("insert into mysql.bind_info values('select * from `test` . `t` where `a` > ?', 'SELECT * FROM `test`.`t` WHERE `a` > 10', 'test', 'deleted', '2000-01-01 09:00:00', '2000-01-01 09:00:00', '', '','" +
bindinfo.Manual + "', '', '')")
bindinfo.Manual + "', '', '', '')")
tk.MustExec("insert into mysql.bind_info values('select * from `test` . `t` where `a` > ?', 'SELECT * FROM `test`.`t` WHERE `a` > 10', 'test', 'disabled', '2000-01-02 09:00:00', '2000-01-02 09:00:00', '', '','" +
bindinfo.Manual + "', '', '')")
bindinfo.Manual + "', '', '', '')")
dom.BindHandle().Clear()
tk.MustExec("set binding enabled for select * from t where a > 10")
tk.MustExec("admin reload bindings")
Expand Down
12 changes: 6 additions & 6 deletions pkg/bindinfo/session_handle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ func TestBaselineDBLowerCase(t *testing.T) {

// Simulate existing bindings with upper case default_db.
tk.MustExec("insert into mysql.bind_info values('select * from `spm` . `t`', 'select * from `spm` . `t`', 'SPM', 'enabled', '2000-01-01 09:00:00', '2000-01-01 09:00:00', '', '','" +
bindinfo.Manual + "', '', '')")
bindinfo.Manual + "', '', '', '')")
tk.MustQuery("select original_sql, default_db from mysql.bind_info where original_sql = 'select * from `spm` . `t`'").Check(testkit.Rows(
"select * from `spm` . `t` SPM",
))
Expand All @@ -237,7 +237,7 @@ func TestBaselineDBLowerCase(t *testing.T) {
internal.UtilCleanBindingEnv(tk, dom)
// Simulate existing bindings with upper case default_db.
tk.MustExec("insert into mysql.bind_info values('select * from `spm` . `t`', 'select * from `spm` . `t`', 'SPM', 'enabled', '2000-01-01 09:00:00', '2000-01-01 09:00:00', '', '','" +
bindinfo.Manual + "', '', '')")
bindinfo.Manual + "', '', '', '')")
tk.MustQuery("select original_sql, default_db from mysql.bind_info where original_sql = 'select * from `spm` . `t`'").Check(testkit.Rows(
"select * from `spm` . `t` SPM",
))
Expand Down Expand Up @@ -274,13 +274,13 @@ func TestShowGlobalBindings(t *testing.T) {
require.Len(t, rows, 0)
// Simulate existing bindings in the mysql.bind_info.
tk.MustExec("insert into mysql.bind_info values('select * from `spm` . `t`', 'select * from `spm` . `t` USE INDEX (`a`)', 'SPM', 'enabled', '2000-01-01 09:00:00', '2000-01-01 09:00:00', '', '','" +
bindinfo.Manual + "', '', '')")
bindinfo.Manual + "', '', '', '')")
tk.MustExec("insert into mysql.bind_info values('select * from `spm` . `t0`', 'select * from `spm` . `t0` USE INDEX (`a`)', 'SPM', 'enabled', '2000-01-02 09:00:00', '2000-01-02 09:00:00', '', '','" +
bindinfo.Manual + "', '', '')")
bindinfo.Manual + "', '', '', '')")
tk.MustExec("insert into mysql.bind_info values('select * from `spm` . `t`', 'select /*+ use_index(`t` `a`)*/ * from `spm` . `t`', 'SPM', 'enabled', '2000-01-03 09:00:00', '2000-01-03 09:00:00', '', '','" +
bindinfo.Manual + "', '', '')")
bindinfo.Manual + "', '', '', '')")
tk.MustExec("insert into mysql.bind_info values('select * from `spm` . `t0`', 'select /*+ use_index(`t0` `a`)*/ * from `spm` . `t0`', 'SPM', 'enabled', '2000-01-04 09:00:00', '2000-01-04 09:00:00', '', '','" +
bindinfo.Manual + "', '', '')")
bindinfo.Manual + "', '', '', '')")
tk.MustExec("admin reload bindings")
rows = tk.MustQuery("show global bindings").Rows()
require.Len(t, rows, 4)
Expand Down
12 changes: 6 additions & 6 deletions pkg/bindinfo/tests/bind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -707,13 +707,13 @@ func TestBindSQLDigest(t *testing.T) {
sql := "create global binding for " + c.origin + " using " + c.hint
tk.MustExec(sql)
res := tk.MustQuery(`show global bindings`).Rows()
require.Equal(t, len(res[0]), 11)
require.Equal(t, len(res[0]), 12)

parser4binding := parser.New()
originNode, err := parser4binding.ParseOneStmt(c.origin, "utf8mb4", "utf8mb4_general_ci")
require.NoError(t, err)
_, sqlDigestWithDB := parser.NormalizeDigestForBinding(utilparser.RestoreWithDefaultDB(originNode, "test", c.origin))
require.Equal(t, res[0][9], sqlDigestWithDB.String())
require.Equal(t, res[0][10], sqlDigestWithDB.String())
}
}

Expand Down Expand Up @@ -801,8 +801,8 @@ func TestDropBindBySQLDigest(t *testing.T) {
res := tk.MustQuery(`show global bindings`).Rows()

require.Equal(t, len(res), 1)
require.Equal(t, len(res[0]), 11)
drop := fmt.Sprintf("drop global binding for sql digest '%s'", res[0][9])
require.Equal(t, len(res[0]), 12)
drop := fmt.Sprintf("drop global binding for sql digest '%s'", res[0][10])
tk.MustExec(drop)
require.NoError(t, h.GCGlobalBinding())
h.ReloadGlobalBindings()
Expand All @@ -817,8 +817,8 @@ func TestDropBindBySQLDigest(t *testing.T) {
res := tk.MustQuery(`show bindings`).Rows()

require.Equal(t, len(res), 1)
require.Equal(t, len(res[0]), 11)
drop := fmt.Sprintf("drop binding for sql digest '%s'", res[0][9])
require.Equal(t, len(res[0]), 12)
drop := fmt.Sprintf("drop binding for sql digest '%s'", res[0][10])
tk.MustExec(drop)
require.NoError(t, h.GCGlobalBinding())
tk.MustQuery("show bindings").Check(testkit.Rows())
Expand Down
7 changes: 7 additions & 0 deletions pkg/executor/bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type SQLBindExec struct {
collation string
db string
isGlobal bool
isUniversal bool // for universal binding
bindAst ast.StmtNode
newStatus string
source string // by manual or from history, only in create stmt
Expand Down Expand Up @@ -143,6 +144,11 @@ func (e *SQLBindExec) createSQLBind() error {
e.Ctx().GetSessionVars().StmtCtx = saveStmtCtx
}()

bindingType := bindinfo.TypeNormal
if e.isUniversal {
bindingType = bindinfo.TypeUniversal
}

bindInfo := bindinfo.Binding{
BindSQL: e.bindSQL,
Charset: e.charset,
Expand All @@ -151,6 +157,7 @@ func (e *SQLBindExec) createSQLBind() error {
Source: e.source,
SQLDigest: e.sqlDigest,
PlanDigest: e.planDigest,
Type: bindingType,
}
record := &bindinfo.BindRecord{
OriginalSQL: e.normdOrigSQL,
Expand Down
1 change: 1 addition & 0 deletions pkg/executor/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -4919,6 +4919,7 @@ func (b *executorBuilder) buildSQLBindExec(v *plannercore.SQLBindPlan) exec.Exec
collation: v.Collation,
db: v.Db,
isGlobal: v.IsGlobal,
isUniversal: v.IsUniversal,
bindAst: v.BindStmt,
newStatus: v.NewStatus,
source: v.Source,
Expand Down
1 change: 1 addition & 0 deletions pkg/executor/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ func (e *ShowExec) fetchShowBind() error {
hint.Charset,
hint.Collation,
hint.Source,
hint.Type,
hint.SQLDigest,
hint.PlanDigest,
})
Expand Down
8 changes: 4 additions & 4 deletions pkg/executor/test/showtest/show_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1207,17 +1207,17 @@ func TestShowBindingDigestField(t *testing.T) {
tk.MustExec("create binding for select * from t1, t2 where t1.id = t2.id using select /*+ merge_join(t1, t2)*/ * from t1, t2 where t1.id = t2.id")
result := tk.MustQuery("show bindings;")
rows := result.Rows()[0]
require.Equal(t, len(rows), 11)
require.Equal(t, rows[9], "ac1ceb4eb5c01f7c03e29b7d0d6ab567e563f4c93164184cde218f20d07fd77c")
require.Equal(t, len(rows), 12)
require.Equal(t, rows[10], "ac1ceb4eb5c01f7c03e29b7d0d6ab567e563f4c93164184cde218f20d07fd77c")
tk.MustExec("drop binding for select * from t1, t2 where t1.id = t2.id")
result = tk.MustQuery("show bindings;")
require.Equal(t, len(result.Rows()), 0)

tk.MustExec("create global binding for select * from t1, t2 where t1.id = t2.id using select /*+ merge_join(t1, t2)*/ * from t1, t2 where t1.id = t2.id")
result = tk.MustQuery("show global bindings;")
rows = result.Rows()[0]
require.Equal(t, len(rows), 11)
require.Equal(t, rows[9], "ac1ceb4eb5c01f7c03e29b7d0d6ab567e563f4c93164184cde218f20d07fd77c")
require.Equal(t, len(rows), 12)
require.Equal(t, rows[10], "ac1ceb4eb5c01f7c03e29b7d0d6ab567e563f4c93164184cde218f20d07fd77c")
tk.MustExec("drop global binding for select * from t1, t2 where t1.id = t2.id")
result = tk.MustQuery("show global bindings;")
require.Equal(t, len(result.Rows()), 0)
Expand Down
Loading

0 comments on commit f532328

Please sign in to comment.