diff --git a/ddl/db_test.go b/ddl/db_test.go index 942cb01223257..2ec42634d50ed 100644 --- a/ddl/db_test.go +++ b/ddl/db_test.go @@ -28,6 +28,7 @@ import ( "github.com/pingcap/failpoint" "github.com/pingcap/tidb/config" "github.com/pingcap/tidb/ddl" + "github.com/pingcap/tidb/ddl/testutil" ddlutil "github.com/pingcap/tidb/ddl/util" "github.com/pingcap/tidb/ddl/util/callback" "github.com/pingcap/tidb/domain" @@ -52,6 +53,7 @@ import ( "github.com/pingcap/tidb/util/dbterror" "github.com/pingcap/tidb/util/mock" "github.com/pingcap/tidb/util/sqlexec" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tikv/client-go/v2/oracle" "github.com/tikv/client-go/v2/tikv" @@ -1622,6 +1624,43 @@ func TestMDLTruncateTable(t *testing.T) { require.True(t, timetk3.After(timeMain)) } +func TestInsertIgnore(t *testing.T) { + store, dom := testkit.CreateMockStoreAndDomain(t) + + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("create table t(a smallint(6) DEFAULT '-13202', b varchar(221) NOT NULL DEFAULT 'duplicatevalue', " + + "c tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (c, b));") + + tk1 := testkit.NewTestKit(t, store) + tk1.MustExec("use test") + + d := dom.DDL() + originalCallback := d.GetHook() + defer d.SetHook(originalCallback) + callback := &callback.TestDDLCallback{} + + onJobUpdatedExportedFunc := func(job *model.Job) { + switch job.SchemaState { + case model.StateDeleteOnly: + _, err := tk1.Exec("INSERT INTO t VALUES (-18585,'aaa',1), (-18585,'0',1), (-18585,'1',1), (-18585,'duplicatevalue',1);") + assert.NoError(t, err) + case model.StateWriteReorganization: + idx := testutil.FindIdxInfo(dom, "test", "t", "idx") + if idx.BackfillState == model.BackfillStateReadyToMerge { + _, err := tk1.Exec("insert ignore into `t` values ( 234,'duplicatevalue',-2028 );") + assert.NoError(t, err) + return + } + } + } + callback.OnJobUpdatedExported.Store(&onJobUpdatedExportedFunc) + d.SetHook(callback) + + tk.MustExec("alter table t add unique index idx(b);") + tk.MustExec("admin check table t;") +} + func TestDDLJobErrEntrySizeTooLarge(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) diff --git a/executor/insert_common.go b/executor/insert_common.go index 8baf10c09fb5d..cb5237785dbdb 100644 --- a/executor/insert_common.go +++ b/executor/insert_common.go @@ -1171,6 +1171,28 @@ func (e *InsertValues) collectRuntimeStatsEnabled() bool { return false } +func (e *InsertValues) handleDuplicateKey(ctx context.Context, txn kv.Transaction, uk *keyValueWithDupInfo, replace bool, r toBeCheckedRow) (bool, error) { + if !replace { + e.ctx.GetSessionVars().StmtCtx.AppendWarning(uk.dupErr) + if txnCtx := e.ctx.GetSessionVars().TxnCtx; txnCtx.IsPessimistic && e.ctx.GetSessionVars().LockUnchangedKeys { + txnCtx.AddUnchangedKeyForLock(uk.newKey) + } + return true, nil + } + _, handle, err := tables.FetchDuplicatedHandle(ctx, uk.newKey, true, txn, e.Table.Meta().ID, uk.commonHandle) + if err != nil { + return false, err + } + if handle == nil { + return false, nil + } + _, err = e.removeRow(ctx, txn, handle, r, true) + if err != nil { + return false, err + } + return false, nil +} + // batchCheckAndInsert checks rows with duplicate errors. // All duplicate rows will be ignored and appended as duplicate warnings. func (e *InsertValues) batchCheckAndInsert( @@ -1221,7 +1243,6 @@ func (e *InsertValues) batchCheckAndInsert( } // append warnings and get no duplicated error rows -CheckAndInsert: for i, r := range toBeCheckedRows { if r.ignored { continue @@ -1258,43 +1279,44 @@ CheckAndInsert: } } + rowInserted := false for _, uk := range r.uniqueKeys { _, err := txn.Get(ctx, uk.newKey) + if err != nil && !kv.IsErrNotFound(err) { + return err + } if err == nil { - if replace { - _, handle, err := tables.FetchDuplicatedHandle( - ctx, - uk.newKey, - true, - txn, - e.Table.Meta().ID, - uk.commonHandle, - ) - if err != nil { - return err - } - if handle == nil { - continue - } - _, err = e.removeRow(ctx, txn, handle, r, true) + rowInserted, err = e.handleDuplicateKey(ctx, txn, uk, replace, r) + if err != nil { + return err + } + if rowInserted { + break + } + continue + } + if tablecodec.IsTempIndexKey(uk.newKey) { + tablecodec.TempIndexKey2IndexKey(uk.newKey) + _, err = txn.Get(ctx, uk.newKey) + if err != nil && !kv.IsErrNotFound(err) { + return err + } + if err == nil { + rowInserted, err = e.handleDuplicateKey(ctx, txn, uk, replace, r) if err != nil { return err } - } else { - // If duplicate keys were found in BatchGet, mark row = nil. - e.ctx.GetSessionVars().StmtCtx.AppendWarning(uk.dupErr) - if txnCtx := e.ctx.GetSessionVars().TxnCtx; txnCtx.IsPessimistic && - e.ctx.GetSessionVars().LockUnchangedKeys { - // lock duplicated unique key on insert-ignore - txnCtx.AddUnchangedKeyForLock(uk.newKey) + if rowInserted { + break } - continue CheckAndInsert } - } else if !kv.IsErrNotFound(err) { - return err } } + if rowInserted { + continue + } + // If row was checked with no duplicate keys, // it should be added to values map for the further row check. // There may be duplicate keys inside the insert statement.