Skip to content

Commit b8bcf9c

Browse files
coocoodshenli
authored andcommitted
tidb: always rebuild plan for retry. (#5219)
Fixs record and index inconsistency bug.
1 parent 039cf7a commit b8bcf9c

File tree

9 files changed

+74
-40
lines changed

9 files changed

+74
-40
lines changed

ast/ast.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,9 @@ type Statement interface {
179179

180180
// IsReadOnly returns if the statement is read only. For example: SelectStmt without lock.
181181
IsReadOnly() bool
182+
183+
// RebuildPlan rebuilds the plan of the statement.
184+
RebuildPlan() error
182185
}
183186

184187
// Visitor visits a Node.

ast/misc.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ type ExecuteStmt struct {
168168

169169
Name string
170170
UsingVars []ExprNode
171+
ExecID uint32
171172
}
172173

173174
// Accept implements Node Accept interface.

executor/adapter.go

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func (a *recordSet) Fields() ([]*ast.ResultField, error) {
5151
for _, col := range a.executor.Schema().Columns {
5252
dbName := col.DBName.O
5353
if dbName == "" && col.TblName.L != "" {
54-
dbName = a.stmt.ctx.GetSessionVars().CurrentDB
54+
dbName = a.stmt.Ctx.GetSessionVars().CurrentDB
5555
}
5656
rf := &ast.ResultField{
5757
ColumnAsName: col.ColName,
@@ -77,13 +77,13 @@ func (a *recordSet) Next() (*ast.Row, error) {
7777
}
7878
if row == nil {
7979
if a.stmt != nil {
80-
a.stmt.ctx.GetSessionVars().LastFoundRows = a.stmt.ctx.GetSessionVars().StmtCtx.FoundRows()
80+
a.stmt.Ctx.GetSessionVars().LastFoundRows = a.stmt.Ctx.GetSessionVars().StmtCtx.FoundRows()
8181
}
8282
return nil, nil
8383
}
8484

8585
if a.stmt != nil {
86-
a.stmt.ctx.GetSessionVars().StmtCtx.AddFoundRows(1)
86+
a.stmt.Ctx.GetSessionVars().StmtCtx.AddFoundRows(1)
8787
}
8888
return &ast.Row{Data: row}, nil
8989
}
@@ -110,7 +110,9 @@ type ExecStmt struct {
110110
// Text represents the origin query text.
111111
Text string
112112

113-
ctx context.Context
113+
StmtNode ast.StmtNode
114+
115+
Ctx context.Context
114116
startTime time.Time
115117
isPreparedStmt bool
116118

@@ -133,13 +135,28 @@ func (a *ExecStmt) IsReadOnly() bool {
133135
return a.ReadOnly
134136
}
135137

138+
// RebuildPlan implements ast.Statement interface.
139+
func (a *ExecStmt) RebuildPlan() error {
140+
is := GetInfoSchema(a.Ctx)
141+
a.InfoSchema = is
142+
if err := plan.Preprocess(a.StmtNode, is, a.Ctx); err != nil {
143+
return errors.Trace(err)
144+
}
145+
p, err := plan.Optimize(a.Ctx, a.StmtNode, is)
146+
if err != nil {
147+
return errors.Trace(err)
148+
}
149+
a.Plan = p
150+
return nil
151+
}
152+
136153
// Exec implements the ast.Statement Exec interface.
137154
// This function builds an Executor from a plan. If the Executor doesn't return result,
138155
// like the INSERT, UPDATE statements, it executes in this function, if the Executor returns
139156
// result, execution is done after this function returns, in the returned ast.RecordSet Next method.
140157
func (a *ExecStmt) Exec(ctx context.Context) (ast.RecordSet, error) {
141158
a.startTime = time.Now()
142-
a.ctx = ctx
159+
a.Ctx = ctx
143160

144161
if _, ok := a.Plan.(*plan.Analyze); ok && ctx.GetSessionVars().InRestrictedSQL {
145162
oriStats := ctx.GetSessionVars().Systems[variable.TiDBBuildStatsConcurrency]
@@ -288,8 +305,8 @@ func (a *ExecStmt) logSlowQuery(succ bool) {
288305
if len(sql) > cfg.Log.QueryLogMaxLen {
289306
sql = sql[:cfg.Log.QueryLogMaxLen] + fmt.Sprintf("(len:%d)", len(sql))
290307
}
291-
connID := a.ctx.GetSessionVars().ConnectionID
292-
currentDB := a.ctx.GetSessionVars().CurrentDB
308+
connID := a.Ctx.GetSessionVars().ConnectionID
309+
currentDB := a.Ctx.GetSessionVars().CurrentDB
293310
logEntry := log.NewEntry(logutil.SlowQueryLogger)
294311
logEntry.Data = log.Fields{
295312
"connectionId": connID,

executor/compiler.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ func (c *Compiler) Compile(ctx context.Context, stmtNode ast.StmtNode) (*ExecStm
6767
Cacheable: plan.Cacheable(stmtNode),
6868
Text: stmtNode.Text(),
6969
ReadOnly: readOnly,
70+
Ctx: ctx,
71+
StmtNode: stmtNode,
7072
}, nil
7173
}
7274

executor/prepared.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,12 @@ func (e *DeallocateExec) Open() error {
312312

313313
// CompileExecutePreparedStmt compiles a session Execute command to a stmt.Statement.
314314
func CompileExecutePreparedStmt(ctx context.Context, ID uint32, args ...interface{}) ast.Statement {
315+
execStmtNode := &ast.ExecuteStmt{ExecID: ID}
316+
execStmtNode.UsingVars = make([]ast.ExprNode, len(args))
317+
for i, val := range args {
318+
execStmtNode.UsingVars[i] = ast.NewValueExpr(val)
319+
}
320+
315321
execPlan := &plan.Execute{ExecID: ID}
316322
execPlan.UsingVars = make([]expression.Expression, len(args))
317323
for i, val := range args {
@@ -323,6 +329,8 @@ func CompileExecutePreparedStmt(ctx context.Context, ID uint32, args ...interfac
323329
InfoSchema: GetInfoSchema(ctx),
324330
Plan: execPlan,
325331
ReadOnly: false,
332+
Ctx: ctx,
333+
StmtNode: execStmtNode,
326334
}
327335

328336
if prepared, ok := ctx.GetSessionVars().PreparedStmts[ID].(*Prepared); ok {

executor/prepared_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@ func (s *testSuite) TestPrepared(c *C) {
6767
stmt := executor.CompileExecutePreparedStmt(tk.Se, stmtId, 1)
6868
c.Assert(stmt.OriginText(), Equals, query)
6969

70+
// Check that rebuild plan works.
71+
tk.Se.PrepareTxnCtx()
72+
err = stmt.RebuildPlan()
73+
c.Assert(err, IsNil)
74+
rs, err := stmt.Exec(tk.Se)
75+
c.Assert(err, IsNil)
76+
_, err = rs.Next()
77+
c.Assert(err, IsNil)
78+
c.Assert(rs.Close(), IsNil)
79+
7080
// Make schema change.
7181
tk.Exec("create table prepare2 (a int)")
7282

new_session_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,3 +1506,20 @@ func (s *testSchemaSuite) TestRetrySchemaChange(c *C) {
15061506
c.Assert(err, IsNil)
15071507
tk.MustQuery("select * from t where t.b = 5").Check(testkit.Rows("1 5"))
15081508
}
1509+
1510+
func (s *testSchemaSuite) TestRetryMissingUnionScan(c *C) {
1511+
tk := testkit.NewTestKitWithInit(c, s.store)
1512+
tk1 := testkit.NewTestKitWithInit(c, s.store)
1513+
tk.MustExec("create table t (a int primary key, b int unique, c int)")
1514+
tk.MustExec("insert into t values (1, 1, 1)")
1515+
1516+
tk1.MustExec("begin")
1517+
tk1.MustExec("update t set b = 1, c = 2 where b = 2")
1518+
tk1.MustExec("update t set b = 1, c = 2 where a = 1")
1519+
1520+
// Create a conflict to reproduces the bug that the second update statement in retry
1521+
// has a dirty table but doesn't use UnionScan.
1522+
tk.MustExec("update t set b = 2 where a = 1")
1523+
1524+
tk1.MustExec("commit")
1525+
}

plan/planbuilder.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ func (b *planBuilder) buildExecute(v *ast.ExecuteStmt) Plan {
186186
}
187187
vars = append(vars, newExpr)
188188
}
189-
exe := &Execute{Name: v.Name, UsingVars: vars}
189+
exe := &Execute{Name: v.Name, ExecID: v.ExecID, UsingVars: vars}
190190
exe.SetSchema(expression.NewSchema())
191191
return exe
192192
}

session.go

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ func (s *session) doCommitWithRetry() error {
304304
// We make larger transactions retry less times to prevent cluster resource outage.
305305
txnSizeRate := float64(txnSize) / float64(kv.TxnTotalSizeLimit)
306306
maxRetryCount := commitRetryLimit - int(float64(commitRetryLimit-1)*txnSizeRate)
307-
err = s.retry(maxRetryCount, domain.ErrInfoSchemaChanged.Equal(err))
307+
err = s.retry(maxRetryCount)
308308
}
309309
}
310310
s.cleanRetryInfo()
@@ -387,7 +387,7 @@ func (s *session) isRetryableError(err error) bool {
387387
return kv.IsRetryableError(err) || domain.ErrInfoSchemaChanged.Equal(err)
388388
}
389389

390-
func (s *session) retry(maxCnt int, infoSchemaChanged bool) error {
390+
func (s *session) retry(maxCnt int) error {
391391
connID := s.sessionVars.ConnectionID
392392
if s.sessionVars.TxnCtx.ForUpdate {
393393
return errors.Errorf("[%d] can not retry select for update statement", connID)
@@ -411,19 +411,15 @@ func (s *session) retry(maxCnt int, infoSchemaChanged bool) error {
411411
if st.IsReadOnly() {
412412
continue
413413
}
414-
txt := st.OriginText()
415-
if infoSchemaChanged {
416-
st, err = updateStatement(st, s, txt)
417-
if err != nil {
418-
return errors.Trace(err)
419-
}
420-
nh.history[i].st = st
414+
err = st.RebuildPlan()
415+
if err != nil {
416+
return errors.Trace(err)
421417
}
422418

423419
if retryCnt == 0 {
424420
// We do not have to log the query every time.
425421
// We print the queries at the first try only.
426-
log.Warnf("[%d] Retry [%d] query [%d] %s", connID, retryCnt, i, sqlForLog(txt))
422+
log.Warnf("[%d] Retry [%d] query [%d] %s", connID, retryCnt, i, sqlForLog(st.OriginText()))
427423
} else {
428424
log.Warnf("[%d] Retry [%d] query [%d]", connID, retryCnt, i)
429425
}
@@ -449,7 +445,6 @@ func (s *session) retry(maxCnt int, infoSchemaChanged bool) error {
449445
return errors.Trace(err)
450446
}
451447
retryCnt++
452-
infoSchemaChanged = domain.ErrInfoSchemaChanged.Equal(err)
453448
if retryCnt >= maxCnt {
454449
log.Warnf("[%d] Retry reached max count %d", connID, retryCnt)
455450
return errors.Trace(err)
@@ -462,27 +457,6 @@ func (s *session) retry(maxCnt int, infoSchemaChanged bool) error {
462457
return err
463458
}
464459

465-
func updateStatement(st ast.Statement, s *session, txt string) (ast.Statement, error) {
466-
// statement maybe stale because of infoschema changed, this function will return the updated one.
467-
if st.IsPrepared() {
468-
// TODO: Rebuild plan if infoschema changed, reuse the statement otherwise.
469-
} else {
470-
// Rebuild plan if infoschema changed, reuse the statement otherwise.
471-
charset, collation := s.sessionVars.GetCharsetInfo()
472-
stmt, err := s.parser.ParseOneStmt(txt, charset, collation)
473-
if err != nil {
474-
return st, errors.Trace(err)
475-
}
476-
st, err = Compile(s, stmt)
477-
if err != nil {
478-
// If a txn is inserting data when DDL is dropping column,
479-
// it would fail to commit and retry, and run here then.
480-
return st, errors.Trace(err)
481-
}
482-
}
483-
return st, nil
484-
}
485-
486460
func sqlForLog(sql string) string {
487461
if len(sql) > sqlLogMaxLen {
488462
return sql[:sqlLogMaxLen] + fmt.Sprintf("(len:%d)", len(sql))
@@ -709,6 +683,8 @@ func (s *session) Execute(sql string) (recordSets []ast.RecordSet, err error) {
709683
Expensive: cacheValue.(*cache.SQLCacheValue).Expensive,
710684
Text: stmtNode.Text(),
711685
ReadOnly: ast.IsReadOnly(stmtNode),
686+
Ctx: s,
687+
StmtNode: stmtNode,
712688
}
713689

714690
s.PrepareTxnCtx()

0 commit comments

Comments
 (0)