Skip to content

Commit 33ac418

Browse files
authored
ddl: fix the batch check for unique index (#40672) (#40702)
ref #40464, close #40592
1 parent d2fbcb7 commit 33ac418

File tree

3 files changed

+120
-18
lines changed

3 files changed

+120
-18
lines changed

ddl/index.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -1585,10 +1585,18 @@ func (w *addIndexWorker) BackfillDataInTxn(handleRange reorgBackfillTask) (taskC
15851585
return nil
15861586
})
15871587
logSlowOperations(time.Since(oprStartTime), "AddIndexBackfillDataInTxn", 3000)
1588-
1588+
failpoint.Inject("mockDMLExecution", func(val failpoint.Value) {
1589+
//nolint:forcetypeassert
1590+
if val.(bool) && MockDMLExecution != nil {
1591+
MockDMLExecution()
1592+
}
1593+
})
15891594
return
15901595
}
15911596

1597+
// MockDMLExecution is only used for test.
1598+
var MockDMLExecution func()
1599+
15921600
func (w *worker) addPhysicalTableIndex(t table.PhysicalTable, reorgInfo *reorgInfo) error {
15931601
if reorgInfo.mergingTmpIdx {
15941602
logutil.BgLogger().Info("[ddl] start to merge temp index", zap.String("job", reorgInfo.Job.String()), zap.String("reorgInfo", reorgInfo.String()))

ddl/index_merge_tmp.go

+46-17
Original file line numberDiff line numberDiff line change
@@ -49,25 +49,12 @@ func (w *mergeIndexWorker) batchCheckTemporaryUniqueKey(txn kv.Transaction, idxR
4949
return errors.Trace(err)
5050
}
5151

52-
// 1. unique-key/primary-key is duplicate and the handle is equal, skip it.
53-
// 2. unique-key/primary-key is duplicate and the handle is not equal, return duplicate error.
54-
// 3. non-unique-key is duplicate, skip it.
5552
for i, key := range w.originIdxKeys {
5653
if val, found := batchVals[string(key)]; found {
57-
if idxRecords[i].distinct && !bytes.Equal(val, idxRecords[i].vals) {
58-
return kv.ErrKeyExists
59-
}
60-
if !idxRecords[i].delete {
61-
idxRecords[i].skip = true
62-
} else {
63-
// Prevent deleting an unexpected index KV.
64-
hdInVal, err := tablecodec.DecodeHandleInUniqueIndexValue(val, w.table.Meta().IsCommonHandle)
65-
if err != nil {
66-
return errors.Trace(err)
67-
}
68-
if !idxRecords[i].handle.Equal(hdInVal) {
69-
idxRecords[i].skip = true
70-
}
54+
// Found a value in the original index key.
55+
err := checkTempIndexKey(txn, idxRecords[i], val, w.table)
56+
if err != nil {
57+
return errors.Trace(err)
7158
}
7259
} else if idxRecords[i].distinct {
7360
// The keys in w.batchCheckKeys also maybe duplicate,
@@ -78,6 +65,48 @@ func (w *mergeIndexWorker) batchCheckTemporaryUniqueKey(txn kv.Transaction, idxR
7865
return nil
7966
}
8067

68+
func checkTempIndexKey(txn kv.Transaction, tmpRec *temporaryIndexRecord, originIdxVal []byte, tblInfo table.Table) error {
69+
if !tmpRec.delete {
70+
if tmpRec.distinct && !bytes.Equal(originIdxVal, tmpRec.vals) {
71+
return kv.ErrKeyExists
72+
}
73+
// The key has been found in the original index, skip merging it.
74+
tmpRec.skip = true
75+
return nil
76+
}
77+
// Delete operation.
78+
distinct := tablecodec.IndexKVIsUnique(originIdxVal)
79+
if !distinct {
80+
// For non-distinct key, it is consist of a null value and the handle.
81+
// Same as the non-unique indexes, replay the delete operation on non-distinct keys.
82+
return nil
83+
}
84+
// For distinct index key values, prevent deleting an unexpected index KV in original index.
85+
hdInVal, err := tablecodec.DecodeHandleInUniqueIndexValue(originIdxVal, tblInfo.Meta().IsCommonHandle)
86+
if err != nil {
87+
return errors.Trace(err)
88+
}
89+
if !tmpRec.handle.Equal(hdInVal) {
90+
// The inequality means multiple modifications happened in the same key.
91+
// We use the handle in origin index value to check if the row exists.
92+
rowKey := tablecodec.EncodeRecordKey(tblInfo.RecordPrefix(), hdInVal)
93+
_, err := txn.Get(context.Background(), rowKey)
94+
if err != nil {
95+
if kv.IsErrNotFound(err) {
96+
// The row is deleted, so we can merge the delete operation to the origin index.
97+
tmpRec.skip = false
98+
return nil
99+
}
100+
// Unexpected errors.
101+
return errors.Trace(err)
102+
}
103+
// Don't delete the index key if the row exists.
104+
tmpRec.skip = true
105+
return nil
106+
}
107+
return nil
108+
}
109+
81110
// temporaryIndexRecord is the record information of an index.
82111
type temporaryIndexRecord struct {
83112
vals []byte

ddl/index_merge_tmp_test.go

+65
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"testing"
1919
"time"
2020

21+
"github.com/pingcap/failpoint"
2122
"github.com/pingcap/tidb/ddl"
2223
"github.com/pingcap/tidb/ddl/ingest"
2324
"github.com/pingcap/tidb/domain"
@@ -415,6 +416,70 @@ func TestAddIndexMergeDeleteUniqueOnWriteOnly(t *testing.T) {
415416
tk.MustExec("admin check table t;")
416417
}
417418

419+
func TestAddIndexMergeDeleteNullUnique(t *testing.T) {
420+
store := testkit.CreateMockStore(t)
421+
422+
tk := testkit.NewTestKit(t, store)
423+
tk.MustExec("use test")
424+
tk.MustExec("create table t(id int primary key, a int default 0);")
425+
tk.MustExec("insert into t values (1, 1), (2, null);")
426+
427+
tk1 := testkit.NewTestKit(t, store)
428+
tk1.MustExec("use test")
429+
430+
ddl.MockDMLExecution = func() {
431+
_, err := tk1.Exec("delete from t where id = 2;")
432+
assert.NoError(t, err)
433+
}
434+
require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/ddl/mockDMLExecution", "1*return(true)->return(false)"))
435+
tk.MustExec("alter table t add unique index idx(a);")
436+
tk.MustQuery("select count(1) from t;").Check(testkit.Rows("1"))
437+
tk.MustExec("admin check table t;")
438+
require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/ddl/mockDMLExecution"))
439+
}
440+
441+
func TestAddIndexMergeDoubleDelete(t *testing.T) {
442+
store, dom := testkit.CreateMockStoreAndDomain(t)
443+
444+
tk := testkit.NewTestKit(t, store)
445+
tk.MustExec("use test")
446+
tk.MustExec("create table t(id int primary key, a int default 0);")
447+
448+
tk1 := testkit.NewTestKit(t, store)
449+
tk1.MustExec("use test")
450+
451+
d := dom.DDL()
452+
originalCallback := d.GetHook()
453+
defer d.SetHook(originalCallback)
454+
callback := &ddl.TestDDLCallback{}
455+
onJobUpdatedExportedFunc := func(job *model.Job) {
456+
if t.Failed() {
457+
return
458+
}
459+
switch job.SchemaState {
460+
case model.StateWriteOnly:
461+
_, err := tk1.Exec("insert into t values (1, 1);")
462+
assert.NoError(t, err)
463+
}
464+
}
465+
callback.OnJobUpdatedExported.Store(&onJobUpdatedExportedFunc)
466+
d.SetHook(callback)
467+
468+
ddl.MockDMLExecution = func() {
469+
_, err := tk1.Exec("delete from t where id = 1;")
470+
assert.NoError(t, err)
471+
_, err = tk1.Exec("insert into t values (2, 1);")
472+
assert.NoError(t, err)
473+
_, err = tk1.Exec("delete from t where id = 2;")
474+
assert.NoError(t, err)
475+
}
476+
require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/ddl/mockDMLExecution", "1*return(true)->return(false)"))
477+
tk.MustExec("alter table t add unique index idx(a);")
478+
tk.MustQuery("select count(1) from t;").Check(testkit.Rows("0"))
479+
tk.MustExec("admin check table t;")
480+
require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/ddl/mockDMLExecution"))
481+
}
482+
418483
func TestAddIndexMergeConflictWithPessimistic(t *testing.T) {
419484
store, dom := testkit.CreateMockStoreAndDomain(t)
420485
tk := testkit.NewTestKit(t, store)

0 commit comments

Comments
 (0)