diff --git a/bindinfo/bind_test.go b/bindinfo/bind_test.go index d044ac37a636a..2b9735f38931e 100644 --- a/bindinfo/bind_test.go +++ b/bindinfo/bind_test.go @@ -120,8 +120,9 @@ func (s *testSuite) TestBindParse(c *C) { status := "using" charset := "utf8mb4" collation := "utf8mb4_bin" - sql := fmt.Sprintf(`INSERT INTO mysql.bind_info(original_sql,bind_sql,default_db,status,create_time,update_time,charset,collation) VALUES ('%s', '%s', '%s', '%s', NOW(), NOW(),'%s', '%s')`, - originSQL, bindSQL, defaultDb, status, charset, collation) + source := bindinfo.Manual + sql := fmt.Sprintf(`INSERT INTO mysql.bind_info(original_sql,bind_sql,default_db,status,create_time,update_time,charset,collation,source) VALUES ('%s', '%s', '%s', '%s', NOW(), NOW(),'%s', '%s', '%s')`, + originSQL, bindSQL, defaultDb, status, charset, collation, source) tk.MustExec(sql) bindHandle := bindinfo.NewBindHandle(tk.Se) err := bindHandle.Update(true) @@ -822,7 +823,8 @@ func (s *testSuite) TestEvolveInvalidBindings(c *C) { tk.MustExec("create table t(a int, b int, index idx_a(a))") tk.MustExec("create global binding for select * from t where a > 10 using select /*+ USE_INDEX(t) */ * from t where a > 10") // Manufacture a rejected binding by hacking mysql.bind_info. - tk.MustExec("insert into mysql.bind_info values('select * from t where a > ?', 'select /*+ USE_INDEX(t,idx_a) */ * from t where a > 10', 'test', 'rejected', '2000-01-01 09:00:00', '2000-01-01 09:00:00', '', '')") + tk.MustExec("insert into mysql.bind_info values('select * from t where a > ?', 'select /*+ USE_INDEX(t,idx_a) */ * from t where a > 10', 'test', 'rejected', '2000-01-01 09:00:00', '2000-01-01 09:00:00', '', '','" + + bindinfo.Manual + "')") tk.MustQuery("select bind_sql, status from mysql.bind_info").Sort().Check(testkit.Rows( "select /*+ USE_INDEX(t) */ * from t where a > 10 using", "select /*+ USE_INDEX(t,idx_a) */ * from t where a > 10 rejected", @@ -1096,3 +1098,55 @@ func (s *testSuite) TestReCreateBindAfterEvolvePlan(c *C) { tk.MustQuery("select * from t where a >= 4 and b >= 1") c.Assert(tk.Se.GetSessionVars().StmtCtx.IndexNames[0], Equals, "t:idx_b") } + +func (s *testSuite) TestbindingSource(c *C) { + tk := testkit.NewTestKit(c, s.store) + s.cleanBindingEnv(tk) + tk.MustExec("use test") + tk.MustExec("drop table if exists t") + tk.MustExec("create table t(a int, index idx_a(a))") + + // Test Source for SQL created sql + tk.MustExec("create global binding for select * from t where a > 10 using select * from t ignore index(idx_a) where a > 10") + bindHandle := s.domain.BindHandle() + sql, hash := parser.NormalizeDigest("select * from t where a > ?") + bindData := bindHandle.GetBindRecord(hash, sql, "test") + c.Check(bindData, NotNil) + c.Check(bindData.OriginalSQL, Equals, "select * from t where a > ?") + c.Assert(len(bindData.Bindings), Equals, 1) + bind := bindData.Bindings[0] + c.Assert(bind.Source, Equals, bindinfo.Manual) + + // Test Source for evolved sql + tk.MustExec("set @@tidb_evolve_plan_baselines=1") + tk.MustQuery("select * from t where a > 10") + bindHandle.SaveEvolveTasksToStore() + sql, hash = parser.NormalizeDigest("select * from t where a > ?") + bindData = bindHandle.GetBindRecord(hash, sql, "test") + c.Check(bindData, NotNil) + c.Check(bindData.OriginalSQL, Equals, "select * from t where a > ?") + c.Assert(len(bindData.Bindings), Equals, 2) + bind = bindData.Bindings[1] + c.Assert(bind.Source, Equals, bindinfo.Evolve) + tk.MustExec("set @@tidb_evolve_plan_baselines=0") + + // Test Source for captured sqls + stmtsummary.StmtSummaryByDigestMap.Clear() + tk.MustExec("set @@tidb_capture_plan_baselines = on") + defer func() { + tk.MustExec("set @@tidb_capture_plan_baselines = off") + }() + tk.MustExec("use test") + c.Assert(tk.Se.Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil), IsTrue) + tk.MustExec("select * from t ignore index(idx_a) where a < 10") + tk.MustExec("select * from t ignore index(idx_a) where a < 10") + tk.MustExec("admin capture bindings") + bindHandle.CaptureBaselines() + sql, hash = parser.NormalizeDigest("select * from t where a < ?") + bindData = bindHandle.GetBindRecord(hash, sql, "test") + c.Check(bindData, NotNil) + c.Check(bindData.OriginalSQL, Equals, "select * from t where a < ?") + c.Assert(len(bindData.Bindings), Equals, 1) + bind = bindData.Bindings[0] + c.Assert(bind.Source, Equals, bindinfo.Capture) +} diff --git a/bindinfo/cache.go b/bindinfo/cache.go index 7fd44b934e892..f4af01b12e5f7 100644 --- a/bindinfo/cache.go +++ b/bindinfo/cache.go @@ -36,6 +36,12 @@ const ( // Rejected means that the bind has been rejected after verify process. // We can retry it after certain time has passed. Rejected = "rejected" + // Manual indicates the binding is created by SQL like "create binding for ...". + Manual = "manual" + // Capture indicates the binding is captured by TiDB automatically. + Capture = "capture" + // Evolve indicates the binding is evolved by TiDB from old bindings. + Evolve = "evolve" ) // Binding stores the basic bind hint info. @@ -47,6 +53,7 @@ type Binding struct { Status string CreateTime types.Time UpdateTime types.Time + Source string Charset string Collation string // Hint is the parsed hints, it is used to bind hints to stmt node. diff --git a/bindinfo/handle.go b/bindinfo/handle.go index 7401effdcc857..c60e3c9fe931b 100644 --- a/bindinfo/handle.go +++ b/bindinfo/handle.go @@ -125,7 +125,7 @@ func (h *BindHandle) Update(fullLoad bool) (err error) { lastUpdateTime := h.bindInfo.lastUpdateTime h.bindInfo.Unlock() - sql := "select original_sql, bind_sql, default_db, status, create_time, update_time, charset, collation from mysql.bind_info" + sql := "select original_sql, bind_sql, default_db, status, create_time, update_time, charset, collation, source from mysql.bind_info" if !fullLoad { sql += " where update_time > \"" + lastUpdateTime.String() + "\"" } @@ -462,6 +462,7 @@ func (h *BindHandle) newBindRecord(row chunk.Row) (string, *BindRecord, error) { UpdateTime: row.GetTime(5), Charset: row.GetString(6), Collation: row.GetString(7), + Source: row.GetString(8), } bindRecord := &BindRecord{ OriginalSQL: row.GetString(0), @@ -567,7 +568,7 @@ func (h *BindHandle) deleteBindInfoSQL(normdOrigSQL, db, bindSQL string) string } func (h *BindHandle) insertBindInfoSQL(orignalSQL string, db string, info Binding) string { - return fmt.Sprintf(`INSERT INTO mysql.bind_info VALUES (%s, %s, %s, %s, %s, %s, %s, %s)`, + return fmt.Sprintf(`INSERT INTO mysql.bind_info VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)`, expression.Quote(orignalSQL), expression.Quote(info.BindSQL), expression.Quote(db), @@ -576,6 +577,7 @@ func (h *BindHandle) insertBindInfoSQL(orignalSQL string, db string, info Bindin expression.Quote(info.UpdateTime.String()), expression.Quote(info.Charset), expression.Quote(info.Collation), + expression.Quote(info.Source), ) } @@ -628,6 +630,7 @@ func (h *BindHandle) CaptureBaselines() { Status: Using, Charset: charset, Collation: collation, + Source: Capture, } // We don't need to pass the `sctx` because the BindSQL has been validated already. err = h.AddBindRecord(nil, &BindRecord{OriginalSQL: normalizedSQL, Db: dbName, Bindings: []Binding{binding}}) diff --git a/executor/bind.go b/executor/bind.go index e4c84d0dcb0d6..4b66d46415316 100644 --- a/executor/bind.go +++ b/executor/bind.go @@ -82,6 +82,7 @@ func (e *SQLBindExec) createSQLBind() error { Charset: e.charset, Collation: e.collation, Status: bindinfo.Using, + Source: bindinfo.Manual, } record := &bindinfo.BindRecord{ OriginalSQL: e.normdOrigSQL, diff --git a/executor/show.go b/executor/show.go index be0325ab3a894..0e050babbb6ac 100644 --- a/executor/show.go +++ b/executor/show.go @@ -276,6 +276,7 @@ func (e *ShowExec) fetchShowBind() error { hint.UpdateTime, hint.Charset, hint.Collation, + hint.Source, }) } } diff --git a/infoschema/tables_test.go b/infoschema/tables_test.go index 67407e8f60a87..b6045c0f1f4e8 100644 --- a/infoschema/tables_test.go +++ b/infoschema/tables_test.go @@ -1198,7 +1198,7 @@ func (s *testTableSuite) TestStmtSummaryInternalQuery(c *C) { tk.MustQuery(`select exec_count, digest_text from information_schema.statements_summary where digest_text like "select original_sql , bind_sql , default_db , status%"`).Check(testkit.Rows( - "1 select original_sql , bind_sql , default_db , status , create_time , update_time , charset , collation from mysql . bind_info" + + "1 select original_sql , bind_sql , default_db , status , create_time , update_time , charset , collation , source from mysql . bind_info" + " where update_time > ? order by update_time")) } diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index 43a7462cf6648..71e50c0c96bd9 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -3098,8 +3098,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"} - ftypes = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeDatetime, mysql.TypeDatetime, mysql.TypeVarchar, mysql.TypeVarchar} + names = []string{"Original_sql", "Bind_sql", "Default_db", "Status", "Create_time", "Update_time", "Charset", "Collation", "Source"} + ftypes = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeDatetime, mysql.TypeDatetime, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar} case ast.ShowAnalyzeStatus: names = []string{"Table_schema", "Table_name", "Partition_name", "Job_info", "Processed_rows", "Start_time", "State"} ftypes = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong, mysql.TypeDatetime, mysql.TypeVarchar} diff --git a/planner/optimize.go b/planner/optimize.go index da469643a1dad..cb097abd26266 100644 --- a/planner/optimize.go +++ b/planner/optimize.go @@ -266,6 +266,7 @@ func handleEvolveTasks(ctx context.Context, sctx sessionctx.Context, br *bindinf Status: bindinfo.PendingVerify, Charset: charset, Collation: collation, + Source: bindinfo.Evolve, } globalHandle := domain.GetDomain(sctx).BindHandle() globalHandle.AddEvolvePlanTask(br.OriginalSQL, br.Db, binding) diff --git a/session/bootstrap.go b/session/bootstrap.go index b32e7de3b4be8..d9d0d9faa5cc7 100644 --- a/session/bootstrap.go +++ b/session/bootstrap.go @@ -240,6 +240,7 @@ const ( update_time timestamp(3) NOT NULL, charset text NOT NULL, collation text NOT NULL, + source varchar(10) NOT NULL default 'unknown', INDEX sql_index(original_sql(1024),default_db(1024)) 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;` @@ -386,6 +387,8 @@ const ( version45 = 45 // version46 fix a bug in v3.1.1. version46 = 46 + // version47 add Source to bindings to indicate the way binding created. + version47 = 47 ) var ( @@ -435,6 +438,7 @@ var ( upgradeToVer44, upgradeToVer45, upgradeToVer46, + upgradeToVer47, } ) @@ -1060,6 +1064,13 @@ func upgradeToVer46(s Session, ver int64) { mustExecute(s, "UPDATE HIGH_PRIORITY mysql.user SET File_priv='Y' where Super_priv='Y'") } +func upgradeToVer47(s Session, ver int64) { + if ver >= version47 { + return + } + doReentrantDDL(s, "ALTER TABLE mysql.bind_info ADD COLUMN `source` varchar(10) NOT NULL default 'unknown'", infoschema.ErrColumnExists) +} + // updateBootstrapVer updates bootstrap version variable in mysql.TiDB table. func updateBootstrapVer(s Session) { // Update bootstrap version. diff --git a/session/session.go b/session/session.go index ce2b720404b1c..2f8f1659b1f20 100644 --- a/session/session.go +++ b/session/session.go @@ -1824,7 +1824,7 @@ func CreateSessionWithDomain(store kv.Storage, dom *domain.Domain) (*session, er const ( notBootstrapped = 0 - currentBootstrapVersion = version46 + currentBootstrapVersion = version47 ) func getStoreBootstrapVersion(store kv.Storage) int64 {