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: fix update panic when update in prepare and execute #26759

Merged
merged 35 commits into from
Aug 5, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ccf66fd
fix update panic when update in prepare and execute
AilinKid Jul 30, 2021
e66e134
Merge branch 'master' into fix-update-in-prepare-execute
AilinKid Jul 30, 2021
0b94fdc
fmt
AilinKid Jul 30, 2021
47b7c9a
Merge branch 'master' into fix-update-in-prepare-execute
AilinKid Aug 2, 2021
843fabe
remove risky code
AilinKid Aug 2, 2021
0644de4
Merge branch 'master' into fix-update-in-prepare-execute
AilinKid Aug 2, 2021
9ba22b5
remote unnecessary code
AilinKid Aug 2, 2021
91127a1
Merge branch 'master' into fix-update-in-prepare-execute
AilinKid Aug 2, 2021
39bdf6f
fmt
AilinKid Aug 2, 2021
7f75aac
resolve point get exit condition
AilinKid Aug 2, 2021
d04b0dd
Merge branch 'master' into fix-update-in-prepare-execute
AilinKid Aug 2, 2021
dbe84bf
remove controversial load schema in Execute
AilinKid Aug 2, 2021
525a740
Merge branch 'master' into fix-update-in-prepare-execute
AilinKid Aug 2, 2021
b358909
fix test
AilinKid Aug 3, 2021
f5d7f0a
Merge branch 'master' into fix-update-in-prepare-execute
AilinKid Aug 3, 2021
1d5e335
fix test
AilinKid Aug 3, 2021
13e0a6f
fix test
AilinKid Aug 3, 2021
d6ec84b
Merge branch 'master' into fix-update-in-prepare-execute
AilinKid Aug 3, 2021
4548083
fix the text-protocol execute statement could't see schema change
AilinKid Aug 3, 2021
44146c4
make fmt
AilinKid Aug 3, 2021
3be9a6f
Merge branch 'master' into fix-update-in-prepare-execute
AilinKid Aug 3, 2021
3f0de0e
comment
AilinKid Aug 3, 2021
e9227ed
import
AilinKid Aug 3, 2021
2959ab5
refactor the logic into process
AilinKid Aug 4, 2021
48b66e4
Merge branch 'master' into fix-update-in-prepare-execute
AilinKid Aug 4, 2021
4dd7843
refactor the logic into process
AilinKid Aug 4, 2021
8086a16
Merge branch 'master' into fix-update-in-prepare-execute
AilinKid Aug 4, 2021
42b381b
make fmt
AilinKid Aug 4, 2021
d41aa35
Merge branch 'master' into fix-update-in-prepare-execute
AilinKid Aug 4, 2021
5a20e18
Merge branch 'master' into fix-update-in-prepare-execute
AilinKid Aug 4, 2021
2e1d6da
remove the TypeExecute enum type
AilinKid Aug 4, 2021
52f774d
Merge branch 'master' into fix-update-in-prepare-execute
AilinKid Aug 4, 2021
963f737
Merge branch 'master' into fix-update-in-prepare-execute
AilinKid Aug 5, 2021
ac14ea0
rename func
AilinKid Aug 5, 2021
da070d7
Merge branch 'master' into fix-update-in-prepare-execute
AilinKid Aug 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -1685,6 +1685,17 @@ func ResetContextOfStmt(ctx sessionctx.Context, s ast.StmtNode) (err error) {
return err
}
s = prepareStmt.PreparedAst.Stmt
is := ctx.GetInfoSchema()
if prepareStmt.PreparedAst.SchemaVersion != is.SchemaMetaVersion() {
prepareStmt.PreparedAst.CachedPlan = nil
prepareStmt.Executor = nil
ret := &plannercore.PreprocessorReturn{InfoSchema: is.(infoschema.InfoSchema)}
err := plannercore.Preprocess(ctx, s, plannercore.InPrepare, plannercore.WithPreprocessorReturn(ret))
if err != nil {
return plannercore.ErrSchemaChanged.GenWithStack("Schema change caused error: %s", err.Error())
}
prepareStmt.PreparedAst.SchemaVersion = is.SchemaMetaVersion()
}
AilinKid marked this conversation as resolved.
Show resolved Hide resolved
sc.InitSQLDigest(prepareStmt.NormalizedSQL, prepareStmt.SQLDigest)
// For `execute stmt` SQL, should reset the SQL digest with the prepare SQL digest.
goCtx := context.Background()
Expand Down
3 changes: 3 additions & 0 deletions executor/prepared.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,9 @@ func CompileExecutePreparedStmt(ctx context.Context, sctx sessionctx.Context,
if err != nil {
return nil, false, false, err
}
if is.SchemaMetaVersion() != sctx.GetInfoSchema().SchemaMetaVersion() {
is = sctx.GetInfoSchema().(infoschema.InfoSchema)
}
AilinKid marked this conversation as resolved.
Show resolved Hide resolved

stmt := &ExecStmt{
GoCtx: ctx,
Expand Down
4 changes: 4 additions & 0 deletions planner/core/common_plans.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@ func (e *Execute) OptimizePreparedPlan(ctx context.Context, sctx sessionctx.Cont
preparedObj.Executor = nil
// If the schema version has changed we need to preprocess it again,
// if this time it failed, the real reason for the error is schema changed.
// Example:
// When running update in prepared statement's schema version distinguished from the one of execute statement
// We should reset the tableRefs in the prepared update statements, otherwise, the ast nodes still hold the old
// tableRefs columnInfo which will cause chaos in logic of trying point get plan. (should ban non-public column)
ret := &PreprocessorReturn{InfoSchema: is}
err := Preprocess(sctx, prepared.Stmt, InPrepare, WithPreprocessorReturn(ret))
if err != nil {
Expand Down
16 changes: 15 additions & 1 deletion planner/core/find_best_task.go
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,20 @@ func (ds *DataSource) skylinePruning(prop *property.PhysicalProperty) []*candida
return candidates
}

func (ds *DataSource) isPointGetConvertableSchema() bool {
for _, col := range ds.Columns {
// Do not handle generated columns.
if col.IsGenerated() {
return false
}
// Only handle tables that all columns are public.
if col.State != model.StatePublic {
return false
}
}
return true
}

// findBestTask implements the PhysicalPlan interface.
// It will enumerate all the available indices and choose a plan with least cost.
func (ds *DataSource) findBestTask(prop *property.PhysicalProperty, planCounter *PlanCounterTp) (t task, cntPlan int64, err error) {
Expand Down Expand Up @@ -704,7 +718,7 @@ func (ds *DataSource) findBestTask(prop *property.PhysicalProperty, planCounter
p: dual,
}, cntPlan, nil
}
canConvertPointGet := len(path.Ranges) > 0 && path.StoreType == kv.TiKV
canConvertPointGet := len(path.Ranges) > 0 && path.StoreType == kv.TiKV && ds.isPointGetConvertableSchema()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if executing normal statements without prepare-execute model? It affects those statements now.

Copy link
Contributor Author

@AilinKid AilinKid Aug 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's ok, it's is a normal limitation.

if canConvertPointGet && !path.IsIntHandlePath {
// We simply do not build [batch] point get for prefix indexes. This can be optimized.
canConvertPointGet = path.Index.Unique && !path.Index.HasPrefixIndex()
Expand Down
7 changes: 7 additions & 0 deletions planner/core/logical_plan_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -4661,7 +4661,14 @@ func CheckUpdateList(assignFlags []int, updt *Update) error {
tbl := updt.tblID2Table[content.TblID]
flags := assignFlags[content.Start:content.End]
var update, updatePK bool
// step1: prepare s1 from "update t set a=? where b=?" // use the schema before the alter table column
// step2: execute s1 using @a, @b // use the schema after the alter table column
// Since the prepare and execute use the difference schema, the step2 will see one more writable column
// --- changing column from ctc, which will lead the flag array out of range here.
for i, col := range tbl.WritableCols() {
//if col.ChangeStateInfo != nil {
// continue
//}
AilinKid marked this conversation as resolved.
Show resolved Hide resolved
if flags[i] >= 0 && col.State != model.StatePublic {
return ErrUnknownColumn.GenWithStackByArgs(col.Name, clauseMsg[fieldList])
}
Expand Down
14 changes: 14 additions & 0 deletions planner/optimize.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,21 @@ func optimize(ctx context.Context, sctx sessionctx.Context, node ast.Node, is in
}
if preparedPointer, ok := sctx.GetSessionVars().PreparedStmts[execID]; ok {
if preparedObj, ok := preparedPointer.(*core.CachedPrepareStmt); ok && preparedObj.ForUpdateRead {
// Actually, `IS` is read only for all the travel path around optimize and exec. The `IS` and `TxnCtn.IS`
// should always be the same, otherwise, it will cause some check fails. For example. `checkUpdateList`
// panics in the plan building phase.
//
// ...
// optimize(is) -> OptimizePreparedPlan(is) -> ... `is` changed
// runstmt(is)
// ...
// The modification of `is` is in the deep call chain. once func the backtracking to the outer, since
// the `is` struct value copy model, the outer `is` is still the old one before calling the runstmt.
//
// while the session TxnCtx hold the new `IS`, we can substitute the outer `is` once we found the schema
// version difference before calling the runstmt
is = domain.GetDomain(sctx).InfoSchema()
sctx.GetSessionVars().TxnCtx.InfoSchema = is
Copy link
Contributor

@xhebox xhebox Jul 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetInfoSchema() is not equavalent to sctx.GetSessionVars().TxnCtx.InfoSchema, e.g. snapshot and stale infoschema. And it is intended that the schema of session is not always the global latest, it should be session-latest except some special cases like forUpdate. In fact, I found 2pc relied on this behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found 2pc relied on this behavior. I don't find this? where is it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only find it works on the resolveAccessPaths

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are right. these two information can be different! may be for this sort of forUpdateRead case, they should be the same.

Copy link
Contributor

@xhebox xhebox Aug 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2pc relied on schema checker and amender. Changing infoschema between optimize and execute may bypass the checker and fail the correctness of amender(for most cases? I don't know why forUpdateRead will ask for the latest exactly, seems it will cause inconsistency frequently if it is not the latest in some parallel cases)

You reminds me that I've suggested to remove these lines, because I have moved the logic of choosing correct infoschema to the outside https://github.com/pingcap/tidb/pull/26759/files#r679868151: these lines should be stub, the correct infoschema has been chosen. But got refused for risks, because the transaction reviewer seems can not track the whole execution path, too.

I've looked more closely, server/conn will dispatch prepare and execute into the one in session, so yes, the correct infoschema will be passed down from session down to here. And chasing a even newer infoschema between two closely coupled optimize and execute is non-sense for me.

Copy link
Contributor

@xhebox xhebox Aug 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a whole new proposal to define the behavior of concurrent DDL, because sync the latest = use the global latest. So all sessions will share the same infoschema as long as possible. It is been a gray area for a long time. I believe a talk to the transaction and runtime team is must.

}
}
}
Expand Down
6 changes: 6 additions & 0 deletions session/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,10 @@ type session struct {
cache [1]ast.StmtNode

builtinFunctionUsage telemetry.BuiltinFunctionsUsage

debug map[string]string

cnt int
AilinKid marked this conversation as resolved.
Show resolved Hide resolved
}

var parserPool = &sync.Pool{New: func() interface{} { return parser.New() }}
Expand Down Expand Up @@ -2047,6 +2051,8 @@ func (s *session) ExecutePreparedStmt(ctx context.Context, stmtID uint32, args [
} else {
is = s.GetInfoSchema().(infoschema.InfoSchema)
}
// sync the new information schema with the one in TxnCtx of sessionVar.
s.sessionVars.TxnCtx.InfoSchema = is
AilinKid marked this conversation as resolved.
Show resolved Hide resolved
if ok {
return s.cachedPlanExec(ctx, is, snapshotTS, stmtID, preparedStmt, args)
}
Expand Down