Skip to content

Commit a5265c1

Browse files
authored
ddl: fix unexpected duplicate entry error when adding unique index (#56162) (#56264)
close #56161
1 parent 115461e commit a5265c1

File tree

3 files changed

+46
-13
lines changed

3 files changed

+46
-13
lines changed

pkg/ddl/index_merge_tmp.go

+22-12
Original file line numberDiff line numberDiff line change
@@ -49,44 +49,54 @@ func (w *mergeIndexWorker) batchCheckTemporaryUniqueKey(
4949
}
5050

5151
for i, key := range w.originIdxKeys {
52-
if val, found := batchVals[string(key)]; found {
52+
keyStr := string(key)
53+
if val, found := batchVals[keyStr]; found {
5354
// Found a value in the original index key.
54-
err := checkTempIndexKey(txn, idxRecords[i], val, w.table)
55+
matchDeleted, err := checkTempIndexKey(txn, idxRecords[i], val, w.table)
5556
if err != nil {
5657
if kv.ErrKeyExists.Equal(err) {
5758
return driver.ExtractKeyExistsErrFromIndex(key, val, w.table.Meta(), w.currentIndex.ID)
5859
}
5960
return errors.Trace(err)
6061
}
62+
if matchDeleted {
63+
// Delete from batchVals to prevent false-positive duplicate detection.
64+
delete(batchVals, keyStr)
65+
}
6166
} else if idxRecords[i].distinct {
6267
// The keys in w.batchCheckKeys also maybe duplicate,
6368
// so we need to backfill the not found key into `batchVals` map.
64-
batchVals[string(key)] = idxRecords[i].vals
69+
batchVals[keyStr] = idxRecords[i].vals
6570
}
6671
}
6772
return nil
6873
}
6974

70-
func checkTempIndexKey(txn kv.Transaction, tmpRec *temporaryIndexRecord, originIdxVal []byte, tblInfo table.Table) error {
75+
// checkTempIndexKey determines whether there is a duplicated index key entry according to value of temp index.
76+
// For non-delete temp record, if the index values mismatch, it is duplicated.
77+
// For delete temp record, we decode the handle from the origin index value and temp index value.
78+
// - if the handles matche, we can delete the index key.
79+
// - otherwise, we further check if the row exists in the table.
80+
func checkTempIndexKey(txn kv.Transaction, tmpRec *temporaryIndexRecord, originIdxVal []byte, tblInfo table.Table) (matchDelete bool, err error) {
7181
if !tmpRec.delete {
7282
if tmpRec.distinct && !bytes.Equal(originIdxVal, tmpRec.vals) {
73-
return kv.ErrKeyExists
83+
return false, kv.ErrKeyExists
7484
}
7585
// The key has been found in the original index, skip merging it.
7686
tmpRec.skip = true
77-
return nil
87+
return false, nil
7888
}
7989
// Delete operation.
8090
distinct := tablecodec.IndexKVIsUnique(originIdxVal)
8191
if !distinct {
8292
// For non-distinct key, it is consist of a null value and the handle.
8393
// Same as the non-unique indexes, replay the delete operation on non-distinct keys.
84-
return nil
94+
return false, nil
8595
}
8696
// For distinct index key values, prevent deleting an unexpected index KV in original index.
8797
hdInVal, err := tablecodec.DecodeHandleInUniqueIndexValue(originIdxVal, tblInfo.Meta().IsCommonHandle)
8898
if err != nil {
89-
return errors.Trace(err)
99+
return false, errors.Trace(err)
90100
}
91101
if !tmpRec.handle.Equal(hdInVal) {
92102
// The inequality means multiple modifications happened in the same key.
@@ -97,16 +107,16 @@ func checkTempIndexKey(txn kv.Transaction, tmpRec *temporaryIndexRecord, originI
97107
if kv.IsErrNotFound(err) {
98108
// The row is deleted, so we can merge the delete operation to the origin index.
99109
tmpRec.skip = false
100-
return nil
110+
return false, nil
101111
}
102112
// Unexpected errors.
103-
return errors.Trace(err)
113+
return false, errors.Trace(err)
104114
}
105115
// Don't delete the index key if the row exists.
106116
tmpRec.skip = true
107-
return nil
117+
return false, nil
108118
}
109-
return nil
119+
return true, nil
110120
}
111121

112122
// temporaryIndexRecord is the record information of an index.

pkg/ddl/tests/indexmerge/BUILD.bazel

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ go_test(
99
],
1010
flaky = True,
1111
race = "on",
12-
shard_count = 20,
12+
shard_count = 21,
1313
deps = [
1414
"//pkg/config",
1515
"//pkg/ddl",

pkg/ddl/tests/indexmerge/merge_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -936,3 +936,26 @@ func TestAddIndexUpdateUntouchedValues(t *testing.T) {
936936
tk.MustExec("admin check table t;")
937937
tk.MustQuery("select * from t;").Check(testkit.Rows("1 1 2", "2 1 2"))
938938
}
939+
940+
func TestAddUniqueIndexFalsePositiveDuplicate(t *testing.T) {
941+
store := testkit.CreateMockStore(t)
942+
943+
tk := testkit.NewTestKit(t, store)
944+
tk.MustExec("use test")
945+
tk.MustExec(`create table t(a bigint DEFAULT '-13202',
946+
b varchar(221) NOT NULL DEFAULT 'dup',
947+
unique key exist_idx(b),
948+
PRIMARY KEY (a));`)
949+
tk.MustExec("INSERT INTO t VALUES (1,'1'), (2,'dup');")
950+
951+
tk1 := testkit.NewTestKit(t, store)
952+
tk1.MustExec("use test")
953+
ddl.MockDMLExecution = func() {
954+
_, err := tk1.Exec("replace into `t` values (3, 'dup');")
955+
assert.NoError(t, err)
956+
}
957+
require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/pkg/ddl/mockDMLExecution", "1*return(true)->return(false)"))
958+
defer require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/pkg/ddl/mockDMLExecution"))
959+
tk.MustExec("alter table t add unique index idx(b);")
960+
tk.MustExec("admin check table t;")
961+
}

0 commit comments

Comments
 (0)