diff --git a/ddl/ddl.go b/ddl/ddl.go index 52d4fe99741f5..325202037cb5b 100644 --- a/ddl/ddl.go +++ b/ddl/ddl.go @@ -249,7 +249,7 @@ type DDL interface { CreateView(ctx sessionctx.Context, stmt *ast.CreateViewStmt) error CreateTableWithLike(ctx sessionctx.Context, ident, referIdent ast.Ident, ifNotExists bool) error DropTable(ctx sessionctx.Context, tableIdent ast.Ident) (err error) - RecoverTable(ctx sessionctx.Context, tbInfo *model.TableInfo, schemaID, autoID, dropJobID int64, snapshotTS uint64) (err error) + RecoverTable(ctx sessionctx.Context, recoverInfo *RecoverInfo) (err error) DropView(ctx sessionctx.Context, tableIdent ast.Ident) (err error) CreateIndex(ctx sessionctx.Context, tableIdent ast.Ident, unique bool, indexName model.CIStr, columnNames []*ast.IndexColName, indexOption *ast.IndexOption) error @@ -659,6 +659,16 @@ func (d *ddl) GetHook() Callback { return d.mu.hook } +// RecoverInfo contains information needed by DDL.RecoverTable. +type RecoverInfo struct { + SchemaID int64 + TableInfo *model.TableInfo + DropJobID int64 + SnapshotTS uint64 + CurAutoIncID int64 + CurAutoRandID int64 +} + // DDL error codes. const ( codeInvalidWorker terror.ErrCode = 1 diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index 17e496db6fe0e..8349f6c5b957e 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -1519,8 +1519,9 @@ func (d *ddl) CreateTable(ctx sessionctx.Context, s *ast.CreateTableStmt) (err e return errors.Trace(err) } -func (d *ddl) RecoverTable(ctx sessionctx.Context, tbInfo *model.TableInfo, schemaID, autoID, dropJobID int64, snapshotTS uint64) (err error) { +func (d *ddl) RecoverTable(ctx sessionctx.Context, recoverInfo *RecoverInfo) (err error) { is := d.GetInfoSchemaWithInterceptor(ctx) + schemaID, tbInfo := recoverInfo.SchemaID, recoverInfo.TableInfo // Check schema exist. schema, ok := is.SchemaByID(schemaID) if !ok { @@ -1539,7 +1540,8 @@ func (d *ddl) RecoverTable(ctx sessionctx.Context, tbInfo *model.TableInfo, sche TableID: tbInfo.ID, Type: model.ActionRecoverTable, BinlogInfo: &model.HistoryInfo{}, - Args: []interface{}{tbInfo, autoID, dropJobID, snapshotTS, recoverTableCheckFlagNone}, + Args: []interface{}{tbInfo, recoverInfo.CurAutoIncID, recoverInfo.DropJobID, + recoverInfo.SnapshotTS, recoverTableCheckFlagNone, recoverInfo.CurAutoRandID}, } err = d.doDDLJob(ctx, job) err = d.callHookOnChanged(err) diff --git a/ddl/ddl_worker.go b/ddl/ddl_worker.go index 887b1402ea326..052afcdbe0d9e 100644 --- a/ddl/ddl_worker.go +++ b/ddl/ddl_worker.go @@ -318,9 +318,9 @@ func (w *worker) finishDDLJob(t *meta.Meta, job *model.Job) (err error) { func finishRecoverTable(w *worker, t *meta.Meta, job *model.Job) error { tbInfo := &model.TableInfo{} - var autoID, dropJobID, recoverTableCheckFlag int64 + var autoIncID, autoRandID, dropJobID, recoverTableCheckFlag int64 var snapshotTS uint64 - err := job.DecodeArgs(tbInfo, &autoID, &dropJobID, &snapshotTS, &recoverTableCheckFlag) + err := job.DecodeArgs(tbInfo, &autoIncID, &dropJobID, &snapshotTS, &recoverTableCheckFlag, &autoRandID) if err != nil { return errors.Trace(err) } diff --git a/ddl/table.go b/ddl/table.go index 3193345526485..be05743a10992 100644 --- a/ddl/table.go +++ b/ddl/table.go @@ -192,9 +192,10 @@ const ( func (w *worker) onRecoverTable(d *ddlCtx, t *meta.Meta, job *model.Job) (ver int64, err error) { schemaID := job.SchemaID tblInfo := &model.TableInfo{} - var autoID, dropJobID, recoverTableCheckFlag int64 + var autoIncID, autoRandID, dropJobID, recoverTableCheckFlag int64 var snapshotTS uint64 - if err = job.DecodeArgs(tblInfo, &autoID, &dropJobID, &snapshotTS, &recoverTableCheckFlag); err != nil { + const checkFlagIndexInJobArgs = 4 // The index of `recoverTableCheckFlag` in job arg list. + if err = job.DecodeArgs(tblInfo, &autoIncID, &dropJobID, &snapshotTS, &recoverTableCheckFlag, &autoRandID); err != nil { // Invalid arguments, cancel this job. job.State = model.JobStateCancelled return ver, errors.Trace(err) @@ -236,9 +237,9 @@ func (w *worker) onRecoverTable(d *ddlCtx, t *meta.Meta, job *model.Job) (ver in // none -> write only // check GC enable and update flag. if gcEnable { - job.Args[len(job.Args)-1] = recoverTableCheckFlagEnableGC + job.Args[checkFlagIndexInJobArgs] = recoverTableCheckFlagEnableGC } else { - job.Args[len(job.Args)-1] = recoverTableCheckFlagDisableGC + job.Args[checkFlagIndexInJobArgs] = recoverTableCheckFlagDisableGC } job.SchemaState = model.StateWriteOnly @@ -271,7 +272,7 @@ func (w *worker) onRecoverTable(d *ddlCtx, t *meta.Meta, job *model.Job) (ver in tblInfo.State = model.StatePublic tblInfo.UpdateTS = t.StartTS - err = t.CreateTableAndSetAutoID(schemaID, tblInfo, autoID) + err = t.CreateTableAndSetAutoID(schemaID, tblInfo, autoIncID, autoRandID) if err != nil { return ver, errors.Trace(err) } diff --git a/ddl/testutil/testutil.go b/ddl/testutil/testutil.go index c690b05af3108..307566f153c3e 100644 --- a/ddl/testutil/testutil.go +++ b/ddl/testutil/testutil.go @@ -18,8 +18,12 @@ import ( "github.com/pingcap/check" "github.com/pingcap/errors" + "github.com/pingcap/parser/model" + "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/session" + "github.com/pingcap/tidb/table" + "github.com/pingcap/tidb/types" ) // SessionExecInGoroutine export for testing. @@ -55,3 +59,23 @@ func ExecMultiSQLInGoroutine(c *check.C, s kv.Storage, dbName string, multiSQL [ } }() } + +// ExtractAllTableHandles extracts all handles of a given table. +func ExtractAllTableHandles(se session.Session, dbName, tbName string) ([]int64, error) { + dom := domain.GetDomain(se) + tbl, err := dom.InfoSchema().TableByName(model.NewCIStr(dbName), model.NewCIStr(tbName)) + if err != nil { + return nil, err + } + err = se.NewTxn(context.Background()) + if err != nil { + return nil, err + } + var allHandles []int64 + err = tbl.IterRecords(se, tbl.FirstKey(), nil, + func(h int64, _ []types.Datum, _ []*table.Column) (more bool, err error) { + allHandles = append(allHandles, h) + return true, nil + }) + return allHandles, err +} diff --git a/executor/ddl.go b/executor/ddl.go index 52ee432044a38..bb3fae612db45 100644 --- a/executor/ddl.go +++ b/executor/ddl.go @@ -23,6 +23,7 @@ import ( "github.com/pingcap/parser/model" "github.com/pingcap/parser/mysql" "github.com/pingcap/tidb/config" + "github.com/pingcap/tidb/ddl" "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/infoschema" "github.com/pingcap/tidb/meta" @@ -332,20 +333,42 @@ func (e *DDLExec) executeRecoverTable(s *ast.RecoverTableStmt) error { if err != nil { return err } - // Get table original autoID before table drop. - m, err := dom.GetSnapshotMeta(job.StartTS) + autoIncID, autoRandID, err := e.getTableAutoIDsFromSnapshot(job) if err != nil { return err } - autoID, err := m.GetAutoTableID(job.SchemaID, job.TableID) - if err != nil { - return errors.Errorf("recover table_id: %d, get original autoID from snapshot meta err: %s", job.TableID, err.Error()) + + recoverInfo := &ddl.RecoverInfo{ + SchemaID: job.SchemaID, + TableInfo: tblInfo, + DropJobID: job.ID, + SnapshotTS: job.StartTS, + CurAutoIncID: autoIncID, + CurAutoRandID: autoRandID, } - // Call DDL RecoverTable - err = domain.GetDomain(e.ctx).DDL().RecoverTable(e.ctx, tblInfo, job.SchemaID, autoID, job.ID, job.StartTS) + // Call DDL RecoverTable. + err = domain.GetDomain(e.ctx).DDL().RecoverTable(e.ctx, recoverInfo) return err } +func (e *DDLExec) getTableAutoIDsFromSnapshot(job *model.Job) (autoIncID, autoRandID int64, err error) { + // Get table original autoIDs before table drop. + dom := domain.GetDomain(e.ctx) + m, err := dom.GetSnapshotMeta(job.StartTS) + if err != nil { + return 0, 0, err + } + autoIncID, err = m.GetAutoTableID(job.SchemaID, job.TableID) + if err != nil { + return 0, 0, errors.Errorf("recover table_id: %d, get original autoIncID from snapshot meta err: %s", job.TableID, err.Error()) + } + autoRandID, err = m.GetAutoRandomID(job.SchemaID, job.TableID) + if err != nil { + return 0, 0, errors.Errorf("recover table_id: %d, get original autoRandID from snapshot meta err: %s", job.TableID, err.Error()) + } + return autoIncID, autoRandID, nil +} + func (e *DDLExec) getRecoverTableByJobID(s *ast.RecoverTableStmt, t *meta.Meta, dom *domain.Domain) (*model.Job, *model.TableInfo, error) { job, err := t.GetHistoryDDLJob(s.JobID) if err != nil { diff --git a/executor/ddl_test.go b/executor/ddl_test.go index 4ebef73f50a23..66e80bb0b00a4 100644 --- a/executor/ddl_test.go +++ b/executor/ddl_test.go @@ -17,7 +17,6 @@ import ( "context" "fmt" "math" - "sort" "strconv" "strings" "time" @@ -28,6 +27,7 @@ import ( "github.com/pingcap/parser/mysql" "github.com/pingcap/parser/terror" "github.com/pingcap/tidb/ddl" + ddltestutil "github.com/pingcap/tidb/ddl/testutil" ddlutil "github.com/pingcap/tidb/ddl/util" "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/infoschema" @@ -761,16 +761,7 @@ func (s *testAutoRandomSuite) TestAutoRandomBitsData(c *C) { for i := 0; i < 100; i++ { tk.MustExec("insert into t(b) values (?)", i) } - dom := domain.GetDomain(tk.Se) - tbl, err := dom.InfoSchema().TableByName(model.NewCIStr("test_auto_random_bits"), model.NewCIStr("t")) - c.Assert(err, IsNil) - c.Assert(tk.Se.NewTxn(context.Background()), IsNil) - var allHandles []int64 - // Iterate all the record. The order is not guaranteed. - err = tbl.IterRecords(tk.Se, tbl.FirstKey(), nil, func(h int64, _ []types.Datum, _ []*table.Column) (more bool, err error) { - allHandles = append(allHandles, h) - return true, nil - }) + allHandles, err := ddltestutil.ExtractAllTableHandles(tk.Se, "test_auto_random_bits", "t") c.Assert(err, IsNil) tk.MustExec("drop table t") @@ -783,11 +774,7 @@ func (s *testAutoRandomSuite) TestAutoRandomBitsData(c *C) { } c.Assert(allZero, IsFalse) // Test non-shard-bits part of auto random id is monotonic increasing and continuous. - orderedHandles := make([]int64, len(allHandles)) - for i, h := range allHandles { - orderedHandles[i] = h << 16 >> 16 - } - sort.Slice(orderedHandles, func(i, j int) bool { return orderedHandles[i] < orderedHandles[j] }) + orderedHandles := testutil.ConfigTestUtils.MaskSortHandles(allHandles, 15, mysql.TypeLonglong) size := int64(len(allHandles)) for i := int64(1); i <= size; i++ { c.Assert(i, Equals, orderedHandles[i-1]) diff --git a/executor/insert_common.go b/executor/insert_common.go index cb8b3d150c19d..67b93f4719651 100644 --- a/executor/insert_common.go +++ b/executor/insert_common.go @@ -717,6 +717,16 @@ func getAutoRecordID(d types.Datum, target *types.FieldType, isInsert bool) (int } func (e *InsertValues) adjustAutoRandomDatum(ctx context.Context, d types.Datum, hasValue bool, c *table.Column) (types.Datum, error) { + retryInfo := e.ctx.GetSessionVars().RetryInfo + if retryInfo.Retrying { + autoRandomID, err := retryInfo.GetCurrAutoRandomID() + if err != nil { + return types.Datum{}, err + } + d.SetAutoID(autoRandomID, c.Flag) + return d, nil + } + var err error var recordID int64 if !hasValue { @@ -736,6 +746,7 @@ func (e *InsertValues) adjustAutoRandomDatum(ctx context.Context, d types.Datum, } e.ctx.GetSessionVars().StmtCtx.InsertID = uint64(recordID) d.SetAutoID(recordID, c.Flag) + retryInfo.AddAutoRandomID(recordID) return d, nil } @@ -758,6 +769,7 @@ func (e *InsertValues) adjustAutoRandomDatum(ctx context.Context, d types.Datum, } d.SetAutoID(recordID, c.Flag) + retryInfo.AddAutoRandomID(recordID) casted, err := table.CastValue(e.ctx, d, c.ToInfo()) if err != nil { diff --git a/executor/seqtest/seq_executor_test.go b/executor/seqtest/seq_executor_test.go index a5888947b2cbd..bd95dfac13daa 100644 --- a/executor/seqtest/seq_executor_test.go +++ b/executor/seqtest/seq_executor_test.go @@ -36,8 +36,11 @@ import ( pb "github.com/pingcap/kvproto/pkg/kvrpcpb" "github.com/pingcap/parser" "github.com/pingcap/parser/model" + "github.com/pingcap/parser/mysql" "github.com/pingcap/parser/terror" "github.com/pingcap/tidb/config" + "github.com/pingcap/tidb/ddl" + ddltestutil "github.com/pingcap/tidb/ddl/testutil" "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/executor" "github.com/pingcap/tidb/kv" @@ -50,6 +53,7 @@ import ( "github.com/pingcap/tidb/store/mockstore/mocktikv" "github.com/pingcap/tidb/store/tikv" "github.com/pingcap/tidb/store/tikv/tikvrpc" + "github.com/pingcap/tidb/util/gcutil" "github.com/pingcap/tidb/util/logutil" "github.com/pingcap/tidb/util/mock" "github.com/pingcap/tidb/util/testkit" @@ -1057,7 +1061,7 @@ func (s *seqTestSuite1) TestCoprocessorPriority(c *C) { cli.mu.Unlock() } -func (s *seqTestSuite) TestAutoIDInRetry(c *C) { +func (s *seqTestSuite) TestAutoIncIDInRetry(c *C) { tk := testkit.NewTestKitWithInit(c, s.store) tk.MustExec("drop table if exists t;") tk.MustExec("create table t (id int not null auto_increment primary key)") @@ -1068,14 +1072,112 @@ func (s *seqTestSuite) TestAutoIDInRetry(c *C) { tk.MustExec("insert into t values (),()") tk.MustExec("insert into t values ()") - c.Assert(failpoint.Enable("github.com/pingcap/tidb/session/mockCommitRetryForAutoID", `return(true)`), IsNil) + c.Assert(failpoint.Enable("github.com/pingcap/tidb/session/mockCommitRetryForAutoIncID", `return(true)`), IsNil) tk.MustExec("commit") - c.Assert(failpoint.Disable("github.com/pingcap/tidb/session/mockCommitRetryForAutoID"), IsNil) + c.Assert(failpoint.Disable("github.com/pingcap/tidb/session/mockCommitRetryForAutoIncID"), IsNil) tk.MustExec("insert into t values ()") tk.MustQuery(`select * from t`).Check(testkit.Rows("1", "2", "3", "4", "5")) } +func (s *seqTestSuite) TestAutoRandIDRetry(c *C) { + tk := testkit.NewTestKitWithInit(c, s.store) + + testutil.ConfigTestUtils.SetupAutoRandomTestConfig() + defer testutil.ConfigTestUtils.RestoreAutoRandomTestConfig() + tk.MustExec("create database if not exists auto_random_retry") + tk.MustExec("use auto_random_retry") + tk.MustExec("drop table if exists t") + tk.MustExec("create table t (id int auto_random(3) primary key)") + + extractMaskedOrderedHandles := func() []int64 { + handles, err := ddltestutil.ExtractAllTableHandles(tk.Se, "auto_random_retry", "t") + c.Assert(err, IsNil) + return testutil.ConfigTestUtils.MaskSortHandles(handles, 3, mysql.TypeLong) + } + + tk.MustExec("set @@tidb_disable_txn_auto_retry = 0") + tk.MustExec("set @@tidb_retry_limit = 10") + tk.MustExec("begin") + tk.MustExec("insert into t values ()") + tk.MustExec("insert into t values (),()") + tk.MustExec("insert into t values ()") + + session.ResetMockAutoRandIDRetryCount(5) + fpName := "github.com/pingcap/tidb/session/mockCommitRetryForAutoRandID" + c.Assert(failpoint.Enable(fpName, `return(true)`), IsNil) + tk.MustExec("commit") + c.Assert(failpoint.Disable(fpName), IsNil) + tk.MustExec("insert into t values ()") + maskedHandles := extractMaskedOrderedHandles() + c.Assert(maskedHandles, DeepEquals, []int64{1, 2, 3, 4, 5}) + + session.ResetMockAutoRandIDRetryCount(11) + tk.MustExec("begin") + tk.MustExec("insert into t values ()") + c.Assert(failpoint.Enable(fpName, `return(true)`), IsNil) + // Insertion failure will skip the 6 in retryInfo. + _, err := tk.Exec("commit") + c.Assert(err, NotNil) + + c.Assert(failpoint.Disable(fpName), IsNil) + + tk.MustExec("insert into t values ()") + maskedHandles = extractMaskedOrderedHandles() + c.Assert(maskedHandles, DeepEquals, []int64{1, 2, 3, 4, 5, 7}) +} + +func (s *seqTestSuite) TestAutoRandRecoverTable(c *C) { + tk := testkit.NewTestKit(c, s.store) + testutil.ConfigTestUtils.SetupAutoRandomTestConfig() + defer testutil.ConfigTestUtils.RestoreAutoRandomTestConfig() + tk.MustExec("create database if not exists test_recover") + tk.MustExec("use test_recover") + tk.MustExec("drop table if exists t_recover_auto_rand") + defer func(originGC bool) { + if originGC { + ddl.EmulatorGCEnable() + } else { + ddl.EmulatorGCDisable() + } + }(ddl.IsEmulatorGCEnable()) + + // Disable emulator GC. + // Otherwise emulator GC will delete table record as soon as possible after execute drop table ddl. + ddl.EmulatorGCDisable() + gcTimeFormat := "20060102-15:04:05 -0700 MST" + timeBeforeDrop := time.Now().Add(0 - time.Duration(48*60*60*time.Second)).Format(gcTimeFormat) + safePointSQL := `INSERT HIGH_PRIORITY INTO mysql.tidb VALUES ('tikv_gc_safe_point', '%[1]s', '') + ON DUPLICATE KEY + UPDATE variable_value = '%[1]s'` + + // Set GC safe point. + tk.MustExec(fmt.Sprintf(safePointSQL, timeBeforeDrop)) + err := gcutil.EnableGC(tk.Se) + c.Assert(err, IsNil) + + c.Assert(failpoint.Enable("github.com/pingcap/tidb/meta/autoid/mockAutoIDChange", `return(true)`), IsNil) + defer func() { + c.Assert(failpoint.Disable("github.com/pingcap/tidb/meta/autoid/mockAutoIDChange"), IsNil) + }() + const autoRandIDStep = 5000 + stp := autoid.GetStep() + autoid.SetStep(autoRandIDStep) + defer autoid.SetStep(stp) + + // Check rebase auto_random id. + tk.MustExec("create table t_recover_auto_rand (a int auto_random(5) primary key);") + tk.MustExec("insert into t_recover_auto_rand values (),(),()") + tk.MustExec("drop table t_recover_auto_rand") + tk.MustExec("recover table t_recover_auto_rand") + tk.MustExec("insert into t_recover_auto_rand values (),(),()") + hs, err := ddltestutil.ExtractAllTableHandles(tk.Se, "test_recover", "t_recover_auto_rand") + c.Assert(err, IsNil) + ordered := testutil.ConfigTestUtils.MaskSortHandles(hs, 5, mysql.TypeLong) + + c.Assert(ordered, DeepEquals, []int64{1, 2, 3, autoRandIDStep + 1, autoRandIDStep + 2, autoRandIDStep + 3}) +} + func (s *seqTestSuite) TestMaxDeltaSchemaCount(c *C) { tk := testkit.NewTestKit(c, s.store) tk.MustExec("use test") diff --git a/meta/meta.go b/meta/meta.go index bc9f3e9b5ecac..0684105dd46d6 100644 --- a/meta/meta.go +++ b/meta/meta.go @@ -292,17 +292,17 @@ func (m *Meta) CreateTableOrView(dbID int64, tableInfo *model.TableInfo) error { // CreateTableAndSetAutoID creates a table with tableInfo in database, // and rebases the table autoID. -func (m *Meta) CreateTableAndSetAutoID(dbID int64, tableInfo *model.TableInfo, autoID int64) error { +func (m *Meta) CreateTableAndSetAutoID(dbID int64, tableInfo *model.TableInfo, autoIncID, autoRandID int64) error { err := m.CreateTableOrView(dbID, tableInfo) if err != nil { return errors.Trace(err) } - _, err = m.txn.HInc(m.dbKey(dbID), m.autoTableIDKey(tableInfo.ID), autoID) + _, err = m.txn.HInc(m.dbKey(dbID), m.autoTableIDKey(tableInfo.ID), autoIncID) if err != nil { return errors.Trace(err) } if tableInfo.AutoRandomBits > 0 { - _, err = m.txn.HInc(m.dbKey(dbID), m.autoRandomTableIDKey(tableInfo.ID), 0) + _, err = m.txn.HInc(m.dbKey(dbID), m.autoRandomTableIDKey(tableInfo.ID), autoRandID) if err != nil { return errors.Trace(err) } diff --git a/meta/meta_test.go b/meta/meta_test.go index f2fcbf026876d..6688cd4d05c19 100644 --- a/meta/meta_test.go +++ b/meta/meta_test.go @@ -184,7 +184,7 @@ func (s *testSuite) TestMeta(c *C) { ID: 3, Name: model.NewCIStr("tbl3"), } - err = t.CreateTableAndSetAutoID(1, tbInfo3, 123) + err = t.CreateTableAndSetAutoID(1, tbInfo3, 123, 0) c.Assert(err, IsNil) id, err := t.GetAutoTableID(1, tbInfo3.ID) c.Assert(err, IsNil) diff --git a/session/txn.go b/session/txn.go index 824f8d0f45dbc..3d1c43d4a6749 100644 --- a/session/txn.go +++ b/session/txn.go @@ -151,14 +151,30 @@ type dirtyTableOperation struct { handle int64 } -var hasMockAutoIDRetry = int64(0) +var hasMockAutoIncIDRetry = int64(0) -func enableMockAutoIDRetry() { - atomic.StoreInt64(&hasMockAutoIDRetry, 1) +func enableMockAutoIncIDRetry() { + atomic.StoreInt64(&hasMockAutoIncIDRetry, 1) } -func mockAutoIDRetry() bool { - return atomic.LoadInt64(&hasMockAutoIDRetry) == 1 +func mockAutoIncIDRetry() bool { + return atomic.LoadInt64(&hasMockAutoIncIDRetry) == 1 +} + +var mockAutoRandIDRetryCount = int64(0) + +func needMockAutoRandIDRetry() bool { + return atomic.LoadInt64(&mockAutoRandIDRetryCount) > 0 +} + +func decreaseMockAutoRandIDRetryCount() { + atomic.AddInt64(&mockAutoRandIDRetryCount, -1) +} + +// ResetMockAutoRandIDRetryCount set the number of occurrences of +// `kv.ErrTxnRetryable` when calling TxnState.Commit(). +func ResetMockAutoRandIDRetryCount(failTimes int64) { + atomic.StoreInt64(&mockAutoRandIDRetryCount, failTimes) } // Commit overrides the Transaction interface. @@ -184,10 +200,17 @@ func (st *TxnState) Commit(ctx context.Context) error { } }) - // mockCommitRetryForAutoID is used to mock an commit retry for adjustAutoIncrementDatum. - failpoint.Inject("mockCommitRetryForAutoID", func(val failpoint.Value) { - if val.(bool) && !mockAutoIDRetry() { - enableMockAutoIDRetry() + // mockCommitRetryForAutoIncID is used to mock an commit retry for adjustAutoIncrementDatum. + failpoint.Inject("mockCommitRetryForAutoIncID", func(val failpoint.Value) { + if val.(bool) && !mockAutoIncIDRetry() { + enableMockAutoIncIDRetry() + failpoint.Return(kv.ErrTxnRetryable) + } + }) + + failpoint.Inject("mockCommitRetryForAutoRandID", func(val failpoint.Value) { + if val.(bool) && needMockAutoRandIDRetry() { + decreaseMockAutoRandIDRetryCount() failpoint.Return(kv.ErrTxnRetryable) } }) diff --git a/sessionctx/variable/session.go b/sessionctx/variable/session.go index f9bda99444a96..d5c8eb8324da2 100644 --- a/sessionctx/variable/session.go +++ b/sessionctx/variable/session.go @@ -63,39 +63,68 @@ var ( type RetryInfo struct { Retrying bool DroppedPreparedStmtIDs []uint32 - currRetryOff int - autoIncrementIDs []int64 + autoIncrementIDs retryInfoAutoIDs + autoRandomIDs retryInfoAutoIDs } // Clean does some clean work. func (r *RetryInfo) Clean() { - r.currRetryOff = 0 - if len(r.autoIncrementIDs) > 0 { - r.autoIncrementIDs = r.autoIncrementIDs[:0] - } + r.autoIncrementIDs.clean() + r.autoRandomIDs.clean() + if len(r.DroppedPreparedStmtIDs) > 0 { r.DroppedPreparedStmtIDs = r.DroppedPreparedStmtIDs[:0] } } -// AddAutoIncrementID adds id to AutoIncrementIDs. -func (r *RetryInfo) AddAutoIncrementID(id int64) { - r.autoIncrementIDs = append(r.autoIncrementIDs, id) -} - // ResetOffset resets the current retry offset. func (r *RetryInfo) ResetOffset() { - r.currRetryOff = 0 + r.autoIncrementIDs.resetOffset() + r.autoRandomIDs.resetOffset() } -// GetCurrAutoIncrementID gets current AutoIncrementID. +// AddAutoIncrementID adds id to autoIncrementIDs. +func (r *RetryInfo) AddAutoIncrementID(id int64) { + r.autoIncrementIDs.autoIDs = append(r.autoIncrementIDs.autoIDs, id) +} + +// GetCurrAutoIncrementID gets current autoIncrementID. func (r *RetryInfo) GetCurrAutoIncrementID() (int64, error) { - if r.currRetryOff >= len(r.autoIncrementIDs) { - return 0, errCantGetValidID + return r.autoIncrementIDs.getCurrent() +} + +// AddAutoRandomID adds id to autoRandomIDs. +func (r *RetryInfo) AddAutoRandomID(id int64) { + r.autoRandomIDs.autoIDs = append(r.autoRandomIDs.autoIDs, id) +} + +// GetCurrAutoRandomID gets current AutoRandomID. +func (r *RetryInfo) GetCurrAutoRandomID() (int64, error) { + return r.autoRandomIDs.getCurrent() +} + +type retryInfoAutoIDs struct { + currentOffset int + autoIDs []int64 +} + +func (r *retryInfoAutoIDs) resetOffset() { + r.currentOffset = 0 +} + +func (r *retryInfoAutoIDs) clean() { + r.currentOffset = 0 + if len(r.autoIDs) > 0 { + r.autoIDs = r.autoIDs[:0] } - id := r.autoIncrementIDs[r.currRetryOff] - r.currRetryOff++ +} +func (r *retryInfoAutoIDs) getCurrent() (int64, error) { + if r.currentOffset >= len(r.autoIDs) { + return 0, errCantGetValidID + } + id := r.autoIDs[r.currentOffset] + r.currentOffset++ return id, nil } diff --git a/util/testutil/testutil.go b/util/testutil/testutil.go index 2eeedfabe482e..8f4ae92479549 100644 --- a/util/testutil/testutil.go +++ b/util/testutil/testutil.go @@ -24,10 +24,12 @@ import ( "reflect" "regexp" "runtime" + "sort" "strings" "github.com/pingcap/check" "github.com/pingcap/errors" + "github.com/pingcap/parser/mysql" "github.com/pingcap/tidb/config" "github.com/pingcap/tidb/sessionctx/stmtctx" "github.com/pingcap/tidb/types" @@ -320,18 +322,31 @@ type autoRandom struct { // SetupAutoRandomTestConfig set alter-primary-key to false, and set allow-auto-random to true and save their origin values. // This method should only be used for the tests in SerialSuite. -func (c *configTestUtils) SetupAutoRandomTestConfig() { +func (a *autoRandom) SetupAutoRandomTestConfig() { globalCfg := config.GetGlobalConfig() - c.originAllowAutoRandom = globalCfg.Experimental.AllowAutoRandom - c.originAlterPrimaryKey = globalCfg.AlterPrimaryKey + a.originAllowAutoRandom = globalCfg.Experimental.AllowAutoRandom + a.originAlterPrimaryKey = globalCfg.AlterPrimaryKey globalCfg.AlterPrimaryKey = false globalCfg.Experimental.AllowAutoRandom = true } // RestoreAutoRandomTestConfig restore the values had been saved in SetupTestConfig. // This method should only be used for the tests in SerialSuite. -func (c *configTestUtils) RestoreAutoRandomTestConfig() { +func (a *autoRandom) RestoreAutoRandomTestConfig() { globalCfg := config.GetGlobalConfig() - globalCfg.Experimental.AllowAutoRandom = c.originAllowAutoRandom - globalCfg.AlterPrimaryKey = c.originAlterPrimaryKey + globalCfg.Experimental.AllowAutoRandom = a.originAllowAutoRandom + globalCfg.AlterPrimaryKey = a.originAlterPrimaryKey +} + +// MaskSortHandles masks highest shard_bits numbers of table handles and sort it. +func (a *autoRandom) MaskSortHandles(handles []int64, shardBitsCount int, fieldType byte) []int64 { + typeBitsLength := mysql.DefaultLengthOfMysqlTypes[fieldType] * 8 + const signBitCount = 1 + shiftBitsCount := 64 - typeBitsLength + shardBitsCount + signBitCount + ordered := make([]int64, len(handles)) + for i, h := range handles { + ordered[i] = h << shiftBitsCount >> shiftBitsCount + } + sort.Slice(ordered, func(i, j int) bool { return ordered[i] < ordered[j] }) + return ordered }