Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

planner: support creating universal bindings #49426

Merged
merged 22 commits into from
Dec 14, 2023
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
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 @@ -4928,6 +4928,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
1 change: 1 addition & 0 deletions pkg/planner/core/common_plans.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ type SQLBindPlan struct {
NormdOrigSQL string
BindSQL string
IsGlobal bool
IsUniversal bool // for universal binding
BindStmt ast.StmtNode
Db string
Charset string
Expand Down
19 changes: 14 additions & 5 deletions pkg/planner/core/planbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -1207,14 +1207,23 @@ func (b *PlanBuilder) buildCreateBindPlan(v *ast.CreateBindingStmt) (Plan, error
return nil, err
}

normdOrigSQL, sqlDigestWithDB := parser.NormalizeDigestForBinding(utilparser.RestoreWithDefaultDB(v.OriginNode, b.ctx.GetSessionVars().CurrentDB, v.OriginNode.Text()))
restoredSQL := utilparser.RestoreWithDefaultDB(v.OriginNode, b.ctx.GetSessionVars().CurrentDB, v.OriginNode.Text())
bindSQL := utilparser.RestoreWithDefaultDB(v.HintedNode, b.ctx.GetSessionVars().CurrentDB, v.HintedNode.Text())
db := utilparser.GetDefaultDB(v.OriginNode, b.ctx.GetSessionVars().CurrentDB)
if v.IsUniversal { // hide schema name if it's universal binding
restoredSQL = utilparser.RestoreWithoutDB(v.OriginNode)
bindSQL = utilparser.RestoreWithoutDB(v.HintedNode)
db = ""
}
normdOrigSQL, sqlDigestWithDB := parser.NormalizeDigestForBinding(restoredSQL)
p := &SQLBindPlan{
SQLBindOp: OpSQLBindCreate,
NormdOrigSQL: normdOrigSQL,
BindSQL: utilparser.RestoreWithDefaultDB(v.HintedNode, b.ctx.GetSessionVars().CurrentDB, v.HintedNode.Text()),
BindSQL: bindSQL,
IsGlobal: v.GlobalScope,
IsUniversal: v.IsUniversal,
BindStmt: v.HintedNode,
Db: utilparser.GetDefaultDB(v.OriginNode, b.ctx.GetSessionVars().CurrentDB),
Db: db,
Charset: charSet,
Collation: collation,
Source: bindinfo.Manual,
Expand Down Expand Up @@ -5522,8 +5531,8 @@ func buildShowSchema(s *ast.ShowStmt, isView bool, isSequence bool) (schema *exp
names = []string{"Privilege", "Context", "Comment"}
ftypes = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar}
case ast.ShowBindings:
names = []string{"Original_sql", "Bind_sql", "Default_db", "Status", "Create_time", "Update_time", "Charset", "Collation", "Source", "Sql_digest", "Plan_digest"}
ftypes = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeDatetime, mysql.TypeDatetime, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar}
names = []string{"Original_sql", "Bind_sql", "Default_db", "Status", "Create_time", "Update_time", "Charset", "Collation", "Source", "Type", "Sql_digest", "Plan_digest"}
ftypes = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeDatetime, mysql.TypeDatetime, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar}
case ast.ShowBindingCacheStatus:
names = []string{"bindings_in_cache", "bindings_in_table", "memory_usage", "memory_quota"}
ftypes = []byte{mysql.TypeLonglong, mysql.TypeLonglong, mysql.TypeVarchar, mysql.TypeVarchar}
Expand Down
15 changes: 14 additions & 1 deletion pkg/session/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ const (
source VARCHAR(10) NOT NULL DEFAULT 'unknown',
sql_digest varchar(64),
plan_digest varchar(64),
type varchar(64) NOT NULL DEFAULT '',
INDEX sql_index(original_sql(700),default_db(68)) COMMENT "accelerate the speed when add global binding query",
INDEX time_index(update_time) COMMENT "accelerate the speed when querying with last update time"
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;`
Expand Down Expand Up @@ -1038,11 +1039,15 @@ const (
// add concurrency/priority/create_time/end_time to `mysql.tidb_background_subtask`/`mysql.tidb_background_subtask_history`
// add idx_exec_id(exec_id) to `mysql.tidb_background_subtask`
version180 = 180

// version 181
// add a new `type` column on mysql.bind_info, which is for universal binding #48875.
version181
)

// currentBootstrapVersion is defined as a variable, so we can modify its value for testing.
// please make sure this is the largest version
var currentBootstrapVersion int64 = version180
var currentBootstrapVersion int64 = version181

// DDL owner key's expired time is ManagerSessionTTL seconds, we should wait the time and give more time to have a chance to finish it.
var internalSQLTimeout = owner.ManagerSessionTTL + 15
Expand Down Expand Up @@ -1198,6 +1203,7 @@ var (
upgradeToVer178,
upgradeToVer179,
upgradeToVer180,
upgradeToVer181,
}
)

Expand Down Expand Up @@ -2933,6 +2939,13 @@ func upgradeToVer180(s sessiontypes.Session, ver int64) {
doReentrantDDL(s, "ALTER TABLE mysql.tidb_background_subtask ADD INDEX idx_exec_id(exec_id)", dbterror.ErrDupKeyName)
}

func upgradeToVer181(s sessiontypes.Session, ver int64) {
if ver >= version181 {
return
}
doReentrantDDL(s, "ALTER TABLE mysql.bind_info ADD COLUMN `type` VARCHAR(64) NOT NULL DEFAULT ''", infoschema.ErrColumnExists)
qw4990 marked this conversation as resolved.
Show resolved Hide resolved
}

func writeOOMAction(s sessiontypes.Session) {
comment := "oom-action is `log` by default in v3.0.x, `cancel` by default in v4.0.11+"
mustExecute(s, `INSERT HIGH_PRIORITY INTO %n.%n VALUES (%?, %?, %?) ON DUPLICATE KEY UPDATE VARIABLE_VALUE= %?`,
Expand Down
Loading