diff --git a/DEPS.bzl b/DEPS.bzl index dd0933260633b..704315093a456 100644 --- a/DEPS.bzl +++ b/DEPS.bzl @@ -2796,8 +2796,8 @@ def go_deps(): name = "com_github_pingcap_kvproto", build_file_proto_mode = "disable_global", importpath = "github.com/pingcap/kvproto", - sum = "h1:5q7Ns0R7q6Uj+fpa3lDTijrcqgId4lNdGa2AG7izB5c=", - version = "v0.0.0-20220906053631-2e37953b2b43", + sum = "h1:HfAWnlVF7P1nNJvXP4ew1Lcnng/BnAVQ40AsUHKR5EA=", + version = "v0.0.0-20220908075542-7c004f4daf21", ) go_repository( name = "com_github_pingcap_log", @@ -3400,8 +3400,8 @@ def go_deps(): name = "com_github_tikv_client_go_v2", build_file_proto_mode = "disable_global", importpath = "github.com/tikv/client-go/v2", - sum = "h1:wjRWmUl4QmJF7V0aUskjT8EjjpfWxi5o9SQR5S1nNWA=", - version = "v2.0.1-0.20220906094532-f867f498456f", + sum = "h1:p8XInTnkUlLabBT7bDS3aZCeemO6tJ/7b5mHN8WbSIE=", + version = "v2.0.1-0.20220913051514-ffaaf7131a8d", ) go_repository( name = "com_github_tikv_pd_client", diff --git a/errno/errname.go b/errno/errname.go index 913473ad4c3f5..08af8dd586f64 100644 --- a/errno/errname.go +++ b/errno/errname.go @@ -1097,7 +1097,7 @@ var MySQLErrName = map[uint16]*mysql.ErrMessage{ ErrResolveLockTimeout: mysql.Message("Resolve lock timeout", nil), ErrRegionUnavailable: mysql.Message("Region is unavailable", nil), ErrGCTooEarly: mysql.Message("GC life time is shorter than transaction duration, transaction starts at %v, GC safe point is %v", nil), - ErrWriteConflict: mysql.Message("Write conflict, txnStartTS=%d, conflictStartTS=%d, conflictCommitTS=%d, key=%s", []int{3}), + ErrWriteConflict: mysql.Message("Write conflict, txnStartTS=%d, conflictStartTS=%d, conflictCommitTS=%d, key=%s, reason=%s", []int{3}), ErrTiKVStoreLimit: mysql.Message("Store token is up to the limit, store id = %d", nil), ErrPrometheusAddrIsNotSet: mysql.Message("Prometheus address is not set in PD and etcd", nil), ErrTiKVStaleCommand: mysql.Message("TiKV server reports stale command", nil), diff --git a/errors.toml b/errors.toml index e498e44d6b26e..78998be942373 100644 --- a/errors.toml +++ b/errors.toml @@ -1688,7 +1688,7 @@ not implemented ["kv:9007"] error = ''' -Write conflict, txnStartTS=%d, conflictStartTS=%d, conflictCommitTS=%d, key=%s [try again later] +Write conflict, txnStartTS=%d, conflictStartTS=%d, conflictCommitTS=%d, key=%s, reason=%s [try again later] ''' ["meta:1007"] diff --git a/go.mod b/go.mod index adc205ecc18cd..ffeebf442a8c6 100644 --- a/go.mod +++ b/go.mod @@ -63,7 +63,7 @@ require ( github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c github.com/pingcap/failpoint v0.0.0-20220423142525-ae43b7f4e5c3 github.com/pingcap/fn v0.0.0-20200306044125-d5540d389059 - github.com/pingcap/kvproto v0.0.0-20220906053631-2e37953b2b43 + github.com/pingcap/kvproto v0.0.0-20220908075542-7c004f4daf21 github.com/pingcap/log v1.1.0 github.com/pingcap/sysutil v0.0.0-20220114020952-ea68d2dbf5b4 github.com/pingcap/tidb/parser v0.0.0-20211011031125-9b13dc409c5e @@ -81,7 +81,7 @@ require ( github.com/stretchr/testify v1.8.0 github.com/tdakkota/asciicheck v0.1.1 github.com/tiancaiamao/appdash v0.0.0-20181126055449-889f96f722a2 - github.com/tikv/client-go/v2 v2.0.1-0.20220906094532-f867f498456f + github.com/tikv/client-go/v2 v2.0.1-0.20220913051514-ffaaf7131a8d github.com/tikv/pd/client v0.0.0-20220725055910-7187a7ab72db github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 github.com/twmb/murmur3 v1.1.3 diff --git a/go.sum b/go.sum index f262a47800a11..d8391416c0310 100644 --- a/go.sum +++ b/go.sum @@ -758,8 +758,8 @@ github.com/pingcap/goleveldb v0.0.0-20191226122134-f82aafb29989 h1:surzm05a8C9dN github.com/pingcap/goleveldb v0.0.0-20191226122134-f82aafb29989/go.mod h1:O17XtbryoCJhkKGbT62+L2OlrniwqiGLSqrmdHCMzZw= github.com/pingcap/kvproto v0.0.0-20191211054548-3c6b38ea5107/go.mod h1:WWLmULLO7l8IOcQG+t+ItJ3fEcrL5FxF0Wu+HrMy26w= github.com/pingcap/kvproto v0.0.0-20220510035547-0e2f26c0a46a/go.mod h1:OYtxs0786qojVTmkVeufx93xe+jUgm56GUYRIKnmaGI= -github.com/pingcap/kvproto v0.0.0-20220906053631-2e37953b2b43 h1:5q7Ns0R7q6Uj+fpa3lDTijrcqgId4lNdGa2AG7izB5c= -github.com/pingcap/kvproto v0.0.0-20220906053631-2e37953b2b43/go.mod h1:OYtxs0786qojVTmkVeufx93xe+jUgm56GUYRIKnmaGI= +github.com/pingcap/kvproto v0.0.0-20220908075542-7c004f4daf21 h1:HfAWnlVF7P1nNJvXP4ew1Lcnng/BnAVQ40AsUHKR5EA= +github.com/pingcap/kvproto v0.0.0-20220908075542-7c004f4daf21/go.mod h1:OYtxs0786qojVTmkVeufx93xe+jUgm56GUYRIKnmaGI= github.com/pingcap/log v0.0.0-20191012051959-b742a5d432e9/go.mod h1:4rbK1p9ILyIfb6hU7OG2CiWSqMXnp3JMbiaVJ6mvoY8= github.com/pingcap/log v0.0.0-20200511115504-543df19646ad/go.mod h1:4rbK1p9ILyIfb6hU7OG2CiWSqMXnp3JMbiaVJ6mvoY8= github.com/pingcap/log v0.0.0-20210625125904-98ed8e2eb1c7/go.mod h1:8AanEdAHATuRurdGxZXBz0At+9avep+ub7U1AGYLIMM= @@ -912,8 +912,8 @@ github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpR github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/tiancaiamao/appdash v0.0.0-20181126055449-889f96f722a2 h1:mbAskLJ0oJfDRtkanvQPiooDH8HvJ2FBh+iKT/OmiQQ= github.com/tiancaiamao/appdash v0.0.0-20181126055449-889f96f722a2/go.mod h1:2PfKggNGDuadAa0LElHrByyrz4JPZ9fFx6Gs7nx7ZZU= -github.com/tikv/client-go/v2 v2.0.1-0.20220906094532-f867f498456f h1:wjRWmUl4QmJF7V0aUskjT8EjjpfWxi5o9SQR5S1nNWA= -github.com/tikv/client-go/v2 v2.0.1-0.20220906094532-f867f498456f/go.mod h1:tkKDJ88lryb16v7FfCh8pvvfwwCkh4aGeSOqHviPaaE= +github.com/tikv/client-go/v2 v2.0.1-0.20220913051514-ffaaf7131a8d h1:p8XInTnkUlLabBT7bDS3aZCeemO6tJ/7b5mHN8WbSIE= +github.com/tikv/client-go/v2 v2.0.1-0.20220913051514-ffaaf7131a8d/go.mod h1:6pedLz7wiINLHXwCT1+yMZmzuG42+ubtBkkfcwoukIo= github.com/tikv/pd/client v0.0.0-20220725055910-7187a7ab72db h1:r1eMh9Rny3hfWuBuxOnbsCRrR4FhthiNxLQ5rAUtaww= github.com/tikv/pd/client v0.0.0-20220725055910-7187a7ab72db/go.mod h1:ew8kS0yIcEaSetuuywkTLIUBR+sz3J5XvAYRae11qwc= github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 h1:kl4KhGNsJIbDHS9/4U9yQo1UcPQM0kOMJHn29EoH/Ro= diff --git a/store/driver/txn/driver_test.go b/store/driver/txn/driver_test.go index bb3f477eeda27..e3a6d3428b9c1 100644 --- a/store/driver/txn/driver_test.go +++ b/store/driver/txn/driver_test.go @@ -36,12 +36,13 @@ func TestWriteConflictPrettyFormat(t *testing.T) { ConflictCommitTs: 399402937719455773, Key: []byte{116, 128, 0, 0, 0, 0, 0, 1, 155, 95, 105, 128, 0, 0, 0, 0, 0, 0, 1, 1, 82, 87, 48, 49, 0, 0, 0, 0, 251, 1, 55, 54, 56, 50, 50, 49, 49, 48, 255, 57, 0, 0, 0, 0, 0, 0, 0, 248, 1, 0, 0, 0, 0, 0, 0, 0, 0, 247}, Primary: []byte{116, 128, 0, 0, 0, 0, 0, 1, 155, 95, 105, 128, 0, 0, 0, 0, 0, 0, 1, 1, 82, 87, 48, 49, 0, 0, 0, 0, 251, 1, 55, 54, 56, 50, 50, 49, 49, 48, 255, 57, 0, 0, 0, 0, 0, 0, 0, 248, 1, 0, 0, 0, 0, 0, 0, 0, 0, 247}, + Reason: kvrpcpb.WriteConflict_Unknown, } expectedStr := "[kv:9007]Write conflict, " + "txnStartTS=399402937522847774, conflictStartTS=399402937719455772, conflictCommitTS=399402937719455773, " + "key={tableID=411, indexID=1, indexValues={RW01, 768221109, , }} " + - "primary={tableID=411, indexID=1, indexValues={RW01, 768221109, , }} " + + "primary={tableID=411, indexID=1, indexValues={RW01, 768221109, , }}, reason=Unknown " + kv.TxnRetryableMark require.EqualError(t, newWriteConflictError(conflict), expectedStr) @@ -51,11 +52,12 @@ func TestWriteConflictPrettyFormat(t *testing.T) { ConflictCommitTs: 399402937719455773, Key: []byte{0x6d, 0x44, 0x42, 0x3a, 0x35, 0x36, 0x0, 0x0, 0x0, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x68, 0x54, 0x49, 0x44, 0x3a, 0x31, 0x30, 0x38, 0x0, 0xfe}, Primary: []byte{0x6d, 0x44, 0x42, 0x3a, 0x35, 0x36, 0x0, 0x0, 0x0, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x68, 0x54, 0x49, 0x44, 0x3a, 0x31, 0x30, 0x38, 0x0, 0xfe}, + Reason: kvrpcpb.WriteConflict_Optimistic, } expectedStr = "[kv:9007]Write conflict, " + "txnStartTS=399402937522847774, conflictStartTS=399402937719455772, conflictCommitTS=399402937719455773, " + "key={metaKey=true, key=DB:56, field=TID:108} " + - "primary={metaKey=true, key=DB:56, field=TID:108} " + + "primary={metaKey=true, key=DB:56, field=TID:108}, reason=Optimistic " + kv.TxnRetryableMark require.EqualError(t, newWriteConflictError(conflict), expectedStr) } diff --git a/store/driver/txn/error.go b/store/driver/txn/error.go index 3f3a769180945..3113c81c329fc 100644 --- a/store/driver/txn/error.go +++ b/store/driver/txn/error.go @@ -162,7 +162,7 @@ func newWriteConflictError(conflict *kvrpcpb.WriteConflict) error { prettyWriteKey(&buf, conflict.Key) buf.WriteString(" primary=") prettyWriteKey(&buf, conflict.Primary) - return kv.ErrWriteConflict.FastGenByArgs(conflict.StartTs, conflict.ConflictTs, conflict.ConflictCommitTs, buf.String()) + return kv.ErrWriteConflict.FastGenByArgs(conflict.StartTs, conflict.ConflictTs, conflict.ConflictCommitTs, buf.String(), conflict.Reason.String()) } func prettyWriteKey(buf *bytes.Buffer, key []byte) { diff --git a/store/mockstore/unistore/tikv/dbreader/db_reader.go b/store/mockstore/unistore/tikv/dbreader/db_reader.go index 23de917f66252..e88099b4a21fc 100644 --- a/store/mockstore/unistore/tikv/dbreader/db_reader.go +++ b/store/mockstore/unistore/tikv/dbreader/db_reader.go @@ -332,6 +332,7 @@ func (r *DBReader) CheckWriteItemForRcCheckTSRead(readTS uint64, item *badger.It StartTS: readTS, ConflictTS: userMeta.StartTS(), ConflictCommitTS: userMeta.CommitTS(), + Reason: kvrpcpb.WriteConflict_RcCheckTs, } } return nil diff --git a/store/mockstore/unistore/tikv/kverrors/errors.go b/store/mockstore/unistore/tikv/kverrors/errors.go index abaa681ed5a89..22054aab567f7 100644 --- a/store/mockstore/unistore/tikv/kverrors/errors.go +++ b/store/mockstore/unistore/tikv/kverrors/errors.go @@ -106,6 +106,7 @@ type ErrConflict struct { ConflictTS uint64 ConflictCommitTS uint64 Key []byte + Reason kvrpcpb.WriteConflict_Reason } func (*ErrConflict) Error() string { diff --git a/store/mockstore/unistore/tikv/mvcc.go b/store/mockstore/unistore/tikv/mvcc.go index cf113415c92bd..20c6d533c6a3c 100644 --- a/store/mockstore/unistore/tikv/mvcc.go +++ b/store/mockstore/unistore/tikv/mvcc.go @@ -580,6 +580,7 @@ func (store *MVCCStore) buildPessimisticLock(m *kvrpcpb.Mutation, item *badger.I ConflictTS: userMeta.StartTS(), ConflictCommitTS: userMeta.CommitTS(), Key: item.KeyCopy(nil), + Reason: kvrpcpb.WriteConflict_PessimisticRetry, } } } @@ -668,6 +669,7 @@ func (store *MVCCStore) prewriteOptimistic(reqCtx *requestCtx, mutations []*kvrp ConflictTS: userMeta.StartTS(), ConflictCommitTS: userMeta.CommitTS(), Key: item.KeyCopy(nil), + Reason: kvrpcpb.WriteConflict_Optimistic, } } } @@ -729,6 +731,7 @@ func (store *MVCCStore) prewritePessimistic(reqCtx *requestCtx, mutations []*kvr ConflictTS: userMeta.StartTS(), ConflictCommitTS: userMeta.CommitTS(), Key: item.KeyCopy(nil), + Reason: kvrpcpb.WriteConflict_LazyUniquenessCheck, } } } @@ -1200,6 +1203,7 @@ func checkLockForRcCheckTS(lock mvcc.Lock, key []byte, startTS uint64, resolved StartTS: startTS, ConflictTS: lock.StartTS, Key: safeCopy(key), + Reason: kvrpcpb.WriteConflict_RcCheckTs, } } diff --git a/store/mockstore/unistore/tikv/server.go b/store/mockstore/unistore/tikv/server.go index eb5a0c22eff58..01ba2053acfa0 100644 --- a/store/mockstore/unistore/tikv/server.go +++ b/store/mockstore/unistore/tikv/server.go @@ -1036,6 +1036,7 @@ func convertToKeyError(err error) *kvrpcpb.KeyError { ConflictTs: x.ConflictTS, ConflictCommitTs: x.ConflictCommitTS, Key: x.Key, + Reason: x.Reason, }, } case *kverrors.ErrDeadlock: diff --git a/tests/realtikvtest/pessimistictest/pessimistic_test.go b/tests/realtikvtest/pessimistictest/pessimistic_test.go index 2dd7021e3021e..fb70e077a3045 100644 --- a/tests/realtikvtest/pessimistictest/pessimistic_test.go +++ b/tests/realtikvtest/pessimistictest/pessimistic_test.go @@ -3155,8 +3155,7 @@ func TestLazyUniquenessCheckForSimpleInserts(t *testing.T) { tk2.MustExec("insert into t values (1, 1)") tk2.MustExec("commit") _, err := tk.Exec("commit") - require.NotNil(t, err) - require.Contains(t, err.Error(), "[kv:9007]Write conflict") + require.ErrorContains(t, err, "[kv:9007]Write conflict") tk.MustQuery("select * from t").Check(testkit.Rows("1 1")) tk.MustExec("admin check table t") @@ -3168,8 +3167,7 @@ func TestLazyUniquenessCheckForSimpleInserts(t *testing.T) { tk2.MustExec("insert into t2 values (2, 0)") tk2.MustExec("commit") _, err = tk.Exec("commit") - require.NotNil(t, err) - require.Contains(t, err.Error(), "[kv:9007]Write conflict") + require.ErrorContains(t, err, "[kv:9007]Write conflict") tk.MustQuery("select * from t2").Check(testkit.Rows("2 0")) tk.MustExec("admin check table t2") } @@ -3205,22 +3203,8 @@ func TestLazyUniquenessCheck(t *testing.T) { tk.MustExec("commit") tk.MustExec("admin check table t") - // case: constraint check failure - tk.MustExec("create table t2 (id int primary key, uk int, unique key i1(uk))") - tk.MustExec("insert into t2 values (1, 1)") - tk.MustExec("begin pessimistic") - tk.MustExec("insert into t2 values (2, 1), (3, 3)") - // NOTE: this read breaks constraint, but we are not able to return an error here. - // We can only guarantee the txn should not commit - tk.MustQuery("select * from t2 use index(primary) for update").Check(testkit.Rows("1 1", "2 1", "3 3")) - err := tk.ExecToErr("commit") - require.Error(t, err) - require.Contains(t, err.Error(), "Duplicate entry '1' for key 'i1'") - tk.MustQuery("select * from t2 use index(primary)").Check(testkit.Rows("1 1")) - tk.MustExec("admin check table t2") - // case: a modification of a lazy-checked key will compensate the lock - tk.MustExec("truncate table t2") + tk.MustExec("create table t2 (id int primary key, uk int, unique key i1(uk))") tk.MustExec("begin pessimistic") tk.MustExec("insert into t2 values (1, 1)") // skip lock tk.MustExec("update t2 set uk = uk + 1") // compensate the lock @@ -3232,19 +3216,19 @@ func TestLazyUniquenessCheck(t *testing.T) { }() time.Sleep(500 * time.Millisecond) tk.MustExec("commit") - err = <-ch + err := <-ch require.NoError(t, err) tk.MustQuery("select * from t2").Check(testkit.Rows("1 12")) tk.MustExec("admin check table t") - // case: conflict check failure + // case: conflict check failure, it doesn't commit in the optimistic way though there is no lock acquired in the txn tk.MustExec("create table t3 (id int primary key, sk int, key i1(sk))") tk.MustExec("begin pessimistic") tk.MustExec("insert into t3 values (1, 1)") tk2.MustExec("insert into t3 values (1, 2)") err = tk.ExecToErr("commit") - require.Error(t, err) - require.Contains(t, err.Error(), "[kv:9007]Write conflict") + require.ErrorContains(t, err, "[kv:9007]Write conflict") + require.ErrorContains(t, err, "reason=LazyUniquenessCheck") // case: DML returns error => abort txn tk.MustExec("create table t4 (id int primary key, v int, key i1(v))") @@ -3253,8 +3237,7 @@ func TestLazyUniquenessCheck(t *testing.T) { tk.MustExec("insert into t4 values (1, 2), (2, 2)") tk.MustQuery("select * from t4 order by id").Check(testkit.Rows("1 2", "2 2")) err = tk.ExecToErr("delete from t4 where id = 1") - require.Error(t, err) - require.Contains(t, err.Error(), "transaction aborted because lazy uniqueness check is enabled and an error occurred: [kv:1062]Duplicate entry '1' for key 'PRIMARY'") + require.ErrorContains(t, err, "transaction aborted because lazy uniqueness check is enabled and an error occurred: [kv:1062]Duplicate entry '1' for key 'PRIMARY'") tk.MustExec("commit") tk.MustExec("admin check table t4") tk.MustQuery("select * from t4 order by id").Check(testkit.Rows("1 1")) @@ -3267,7 +3250,7 @@ func TestLazyUniquenessCheck(t *testing.T) { tk2.MustExec("delete from t5 where uk = 2") tk.MustExec("select * from t5 for update") err = tk.ExecToErr("commit") - require.Contains(t, err.Error(), "[kv:9007]Write conflict") + require.ErrorContains(t, err, "[kv:9007]Write conflict") // case: delete your own insert that should've returned error tk.MustExec("truncate table t5") @@ -3275,8 +3258,7 @@ func TestLazyUniquenessCheck(t *testing.T) { tk.MustExec("begin pessimistic") tk.MustExec("insert into t5 values (2, 1)") err = tk.ExecToErr("delete from t5") - require.Error(t, err) - require.Contains(t, err.Error(), "transaction aborted because lazy uniqueness check is enabled and an error occurred: [kv:1062]Duplicate entry '1' for key 'i1'") + require.ErrorContains(t, err, "transaction aborted because lazy uniqueness check is enabled and an error occurred: [kv:1062]Duplicate entry '1' for key 'i1'") require.False(t, tk.Session().GetSessionVars().InTxn()) // case: update unique key, but conflict exists before the txn @@ -3285,8 +3267,7 @@ func TestLazyUniquenessCheck(t *testing.T) { tk.MustExec("begin pessimistic") tk.MustExec("update t5 set uk = 3 where id = 1") err = tk.ExecToErr("commit") - require.Error(t, err) - require.Contains(t, err.Error(), "Duplicate entry '3' for key 'i1'") + require.ErrorContains(t, err, "Duplicate entry '3' for key 'i1'") tk.MustExec("admin check table t5") // case: update unique key, but conflict with concurrent write @@ -3296,8 +3277,7 @@ func TestLazyUniquenessCheck(t *testing.T) { tk.MustExec("update t5 set uk = 3 where id = 1") tk2.MustExec("insert into t5 values (2, 3)") err = tk.ExecToErr("commit") - require.Error(t, err) - require.Contains(t, err.Error(), "[kv:9007]Write conflict") + require.ErrorContains(t, err, "[kv:9007]Write conflict") tk.MustExec("admin check table t5") // case: insert on duplicate update unique key, but conflict exists before the txn @@ -3306,8 +3286,7 @@ func TestLazyUniquenessCheck(t *testing.T) { tk.MustExec("begin pessimistic") tk.MustExec("insert into t5 values (3, 1) on duplicate key update uk = 3") err = tk.ExecToErr("commit") - require.Error(t, err) - require.Contains(t, err.Error(), "Duplicate entry '3' for key 'i1'") + require.ErrorContains(t, err, "Duplicate entry '3' for key 'i1'") tk.MustExec("admin check table t5") // case: insert on duplicate update unique key, but conflict with concurrent write @@ -3317,12 +3296,12 @@ func TestLazyUniquenessCheck(t *testing.T) { tk.MustExec("insert into t5 values (3, 1) on duplicate key update uk = 3") tk2.MustExec("insert into t5 values (2, 3)") err = tk.ExecToErr("commit") - require.Error(t, err) - require.Contains(t, err.Error(), "[kv:9007]Write conflict") + require.ErrorContains(t, err, "[kv:9007]Write conflict") tk.MustExec("admin check table t5") } func TestLazyUniquenessCheckForInsertIgnore(t *testing.T) { + // lazy uniqueness check doesn't affect INSERT IGNORE store := realtikvtest.CreateMockStoreAndSetup(t) tk := testkit.NewTestKit(t, store) tk.MustExec("use test") @@ -3364,8 +3343,8 @@ func TestLazyUniquenessCheckWithStatementRetry(t *testing.T) { tk.MustExec("insert into t5 values (3, 3)") // skip handle=3, uk=3 tk2.MustExec("insert into t5 values (2, 3)") err := tk.ExecToErr("update t5 set id = 10 where uk = 3") // write conflict -> unset PresumeKNE -> retry - require.Error(t, err) - require.Contains(t, err.Error(), "Duplicate entry '3' for key 'i1'") + require.ErrorContains(t, err, "transaction aborted because lazy uniqueness") + require.ErrorContains(t, err, "Duplicate entry '3' for key 'i1'") require.False(t, tk.Session().GetSessionVars().InTxn()) tk.MustExec("admin check table t5") @@ -3376,8 +3355,42 @@ func TestLazyUniquenessCheckWithStatementRetry(t *testing.T) { tk.MustExec("insert into t5 values (3, 3)") // skip handle=3, uk=3 tk2.MustExec("insert into t5 values (2, 3)") err = tk.ExecToErr("update t5 set id = id + 10") // write conflict -> unset PresumeKNE -> retry - require.Error(t, err) - require.Contains(t, err.Error(), "Duplicate entry '3' for key 'i1'") + require.ErrorContains(t, err, "Duplicate entry '3' for key 'i1'") require.False(t, tk.Session().GetSessionVars().InTxn()) tk.MustExec("admin check table t5") } + +func TestLazyUniquenessCheckWithInconsistentReadResult(t *testing.T) { + // If any read breaks constraint, we guarantee the txn cannot commit + store := realtikvtest.CreateMockStoreAndSetup(t) + tk := testkit.NewTestKit(t, store) + tk2 := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk2.MustExec("use test") + tk.MustExec("set @@tidb_constraint_check_in_place_pessimistic=0") + // TiKV will perform a constraint check before reporting assertion failure. + // And constraint violation precedes assertion failure. + if !*realtikvtest.WithRealTiKV { + tk.MustExec("set @@tidb_txn_assertion_level=off") + } + + // case: conflict data has been there before current txn + tk.MustExec("create table t2 (id int primary key, uk int, unique key i1(uk))") + tk.MustExec("insert into t2 values (1, 1)") + tk.MustExec("begin pessimistic") + tk.MustExec("insert into t2 values (2, 1), (3, 3)") + tk.MustQuery("select * from t2 use index(primary) for update").Check(testkit.Rows("1 1", "2 1", "3 3")) + err := tk.ExecToErr("commit") + require.ErrorContains(t, err, "Duplicate entry '1' for key 'i1'") + tk.MustQuery("select * from t2 use index(primary)").Check(testkit.Rows("1 1")) + tk.MustExec("admin check table t2") + + // case: conflict data is written concurrently + tk.MustExec("truncate table t2") + tk.MustExec("begin pessimistic") + tk.MustExec("insert into t2 values (1, 1)") + tk2.MustExec("insert into t2 values (2, 1)") + tk.MustQuery("select * from t2 use index(primary) for update").Check(testkit.Rows("1 1", "2 1")) + err = tk.ExecToErr("commit") + require.ErrorContains(t, err, "reason=LazyUniquenessCheck") +} diff --git a/tests/realtikvtest/txntest/txn_test.go b/tests/realtikvtest/txntest/txn_test.go index e282f3d84fada..6f858e787dfcf 100644 --- a/tests/realtikvtest/txntest/txn_test.go +++ b/tests/realtikvtest/txntest/txn_test.go @@ -177,3 +177,19 @@ func TestStatementErrorInTransaction(t *testing.T) { tk.MustExec("rollback") tk.MustQuery("select * from test where a = 1 and b = 11").Check(testkit.Rows()) } + +func TestWriteConflictReason(t *testing.T) { + store := realtikvtest.CreateMockStoreAndSetup(t) + tk := testkit.NewTestKit(t, store) + tk2 := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk2.MustExec("use test") + tk.MustExec("drop table if exists t") + tk.MustExec("create table t (c int primary key)") + tk.MustExec("begin optimistic") + tk2.MustExec("insert into t values (1)") + tk.MustExec("insert into t values (1)") + err := tk.ExecToErr("commit") + require.Contains(t, err.Error(), "Write conflict") + require.Contains(t, err.Error(), "reason=Optimistic") +}