[txn] Pessimistic RC retry reuses a stale scalar-subquery constant and commits mixed-attempt data
- Dominant language
- Go
- Stars
- 40.5k
- Forks
- 6.2k
- PR merge metrics
- PR metrics pending
Description
## Bug Report
> Evidence status: confirmed.
### 1. Minimal reproduce step (Required)
Use a pessimistic READ COMMITTED transaction to UPDATE a target through a configuration-table join while assigning another column from a non-correlated scalar aggregate over the target. Let a concurrent allocator claim the first attempt unique value, advance the configuration value, and add a row included by the aggregate before the first attempt reaches pessimistic locking. The natural unique-key write conflict makes TiDB retry the statement. MDL stays ON; pessimistic mode and the default max-retry-count of 256 remain unchanged; READ COMMITTED is the only common non-default setting. SLEEP in the deterministic probe only replaces batch scan or storage latency.
### 2. What did you expect to see? (Required)
Under READ COMMITTED, one attempt uses one statement TS for both the scalar read and the DML source. The only coherent rows are old scalar/old source if the allocator commits after that TS, or new scalar/new source if it commits before the TS. The successful retry must persist the new/new state or expose the conflict.
### 3. What did you see instead? (Required)
On a one-TiDB/three-TiKV testbed, UPDATE affected two rows, COMMIT succeeded, and ADMIN CHECK passed, but the target was (1,100,31),(2,300,32),(3,200,999) while a one-shot execution from the successful-attempt state was (1,100,1030),(2,300,1031),(3,200,999). A no-retry RC control with the publisher committing after statement start produced old/old, not the mixed result.
### 4. What is your TiDB version? (Required)
TiDB 531e40c local and d573e28 real TiKV; TiKV 67fccdb; MDL ON; common READ COMMITTED
Likely root cause and fix direction
The expression rewriter evaluates the non-correlated scalar subquery during planning and embeds its row as expression.Constant values carrying SubqueryRefID. Pessimistic RC retry refreshes the statement TS, but handlePessimisticLockError rebuilds only the executor from the existing ExecStmt.Plan. The constant therefore retains the failed-attempt generation while ordinary plan reads use the retry generation.
Contributor guide
Research direction
Trace the expression rewriter, handlePessimisticLockError, and the existing ExecStmt.Plan rebuild path. Reproduce the pessimistic READ COMMITTED retry with the deterministic probe, then compare scalar-subquery constants and ordinary plan reads across attempts. Done means a successful retry uses one coherent statement generation, persists new/new or exposes the conflict, and passes the reported consistency checks.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, sql
- Domain
- databases, distributed-systems
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100