Pessimistic retry of foreign key DML don't lock row keys
- Dominant language
- Go
- Stars
- 40.5k
- Forks
- 6.2k
- PR merge metrics
- PR metrics pending
Description
## Bug Report
Please answer these questions before submitting your issue. Thanks!
### 1. Minimal reproduce step (Required)
Build TiDB with sleep 1s injection after `Exec` in retrying statement.
https://github.com/pingcap/tidb/blob/53b74edcbc15c98ea2dd837b803581400851cad5/pkg/session/session.go#L1174
Run [stmtflow](https://github.com/zyguan/tidb-test-util).
```sql
/* init */ drop table if exists t1,t2;
/* init */ create table t1 (id int primary key, v int);
/* init */ create table t2 (id int primary key, pid int, foreign key (pid) references t1(id) on delete cascade on update cascade);
/* init */ insert into t1 values (1, 1);
/* init */ insert into t2 values (1, 1);
/* t1 */ begin pessimistic;
/* t1 */ update t1 set v = 2 where id = 1;
/* t2 */ delete from t1 where v <= 3;
/* t1 */ commit;
/* t3 */ begin pessimistic;
/* t3 */ select sleep(0.5); -- wait t2 to acquire lock
/* t3 */ update t1 set v = 3 where id = 1;
/* t3 */ commit;
/* t2 */ select * from t1;
/* t2 */ select @@tidb_last_txn_info info;
/* t2 */ select connection_id();
```
```bash
./bin/stmtflow play ./lock-row-key.sql --block-time=3s
```
### 2. What did you expect to see? (Required)
The t2 is executed before t3 because the acquired pessimistic locks should block t3 even it's not committed yet.
The result of t1 should be empty.
### 3. What did you see instead (Required)
The t2 didn't lock row key and block t3, so the t3 is executed before t2. Because [the constraint check is skipped](https://github.com/tikv/client-go/blob/1a0daf3ee77f560debe0a386e760f2ae7164b6a5/txnkv/transaction/prewrite.go#L107), t2 didn't fail.
```sql
/* init */ drop table if exists t1,t2;
-- init >> 0 rows affected
/* init */ create table t1 (id int primary key, v int);
-- init >> 0 rows affected
/* init */ create table t2 (id int primary key, pid int, foreign key (pid) references t1(id) on delete cascade on update cascade);
-- init >> 0 rows affected
/* init */ insert into t1 values (1, 1);
-- init >> 1 rows affected
/* init */ insert into t2 values (1, 1);
-- init >> 1 rows affected
/* t1 */ begin pessimistic;
-- t1 >> 0 rows affected
/* t1 */ update t1 set v = 2 where id = 1;
-- t1 >> 1 rows affected
/* t2 */ delete from t1 where v <= 3;
-- t2 >> blocked
/* t1 */ commit;
-- t1 >> 0 rows affected
/* t3 */ begin pessimistic;
-- t3 >> 0 rows affected
/* t3 */ select sleep(0.5); -- wait t2 to acquire lock
-- t3 >> +------------+
-- t3 | sleep(0.5) |
-- t3 +------------+
-- t3 | 0 |
-- t3 +------------+
/* t3 */ update t1 set v = 3 where id = 1;
-- t3 >> 1 rows affected
/* t3 */ commit;
-- t3 >> 0 rows affected
-- t2 >> resumed
-- t2 >> 1 rows affected
/* t2 */ select * from t1;
-- t2 >> +----+---+
-- t2 | id | v |
-- t2 +----+---+
-- t2 +----+---+
/* t2 */ select @@tidb_last_txn_info info;
-- t2 >> +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-- t2 | info |
-- t2 +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-- t2 | {"txn_scope":"global","start_ts":460111544108449794,"commit_ts":0,"txn_commit_mode":"","async_commit_fallback":false,"one_pc_fallback":false,"pipelined":false,"flush_wait_ms":0} |
-- t2 +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
/* t2 */ select connection_id();
-- t2 >> +-----------------+
-- t2 | connection_id() |
-- t2 +-----------------+
-- t2 | 2927624198 |
-- t2 +-----------------+
```
Since t3 waits for half second to let t2 acquire pessimistic lock in pessimistic retry, t3's update statement should be blocked.
And if t2 acquires the pessimistic lock as expected, the retry count of t2 should be only once, but it retried twice (`retryCnt=2`).
```log
[2025/08/14 23:44:08.229 +08:00] [WARN] [session.go:1165] [retrying] [conn=2927624198] [session_alias=] [schemaVersion=59] [retryCnt=2] [queryNum=0]
```
### 4. What is your TiDB version? (Required)
nightly
Contributor guide
Assessment
This issue has not been assessed yet.