pingcap / pingcap/tidb

autocommit SELECT ... FOR UPDATE does not check row locks in TiDB

Open
#70,996 0 comments 0 reactions 0 assignees View on GitHub
affects-9.0 component/executor found-by-ai may-affects-25.10 may-affects-26.3 may-affects-7.5 may-affects-8.1 may-affects-8.5 severity/major sig/transaction type/bug
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)

```sql
-- Session A:
BEGIN;
SELECT v FROM t WHERE id=1 FOR UPDATE;
SELECT SLEEP(5);
COMMIT;

-- Session B, about one second later, autocommit=1:
SET SESSION innodb_lock_wait_timeout=1;
SELECT v FROM t WHERE id=1 FOR UPDATE;
```

Probe output:

```text
MYSQL_B_RC=1
MYSQL_B_OUT='1'
MYSQL_B_ERR=ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MYSQL_B_DURATION=1.03
MYSQL_AUTOCOMMIT_FOR_UPDATE_LOCKS

TIDB_RC=0
TIDB_OUT='1 | 10'
TIDB_AUTOCOMMIT_FOR_UPDATE_ACCEPTED

TIDB_B_RC=0
TIDB_B_OUT='1 | 10'
TIDB_B_DURATION=0.01
TIDB_AUTOCOMMIT_FOR_UPDATE_IGNORED
```

MySQL blocks the autocommit locking read; TiDB returns the locked row immediately.

### 2. What did you expect to see? (Required)

An autocommit `SELECT ... FOR UPDATE` must check/acquire the row lock for the statement and wait for a conflicting transaction up to `innodb_lock_wait_timeout`, matching MySQL. If TiDB intentionally does not lock in autocommit, the clause must be rejected or documented as a no-lock read instead of silently succeeding.

### 3. What did you see instead (Required)

MySQL: session A FOR UPDATE; session B autocommit FOR UPDATE with innodb_lock_wait_timeout=1 -> ERROR 1205 after 1.03s. TiDB: same sequence -> returns 10 in 0.01s.

### 4. What is your TiDB version? (Required)

```text
Release Version: v8.4.0-this-is-a-placeholder
Edition: Community
Git Commit Hash: None
Git Branch: None
UTC Build Time: None
GoVersion: go1.25.12
Race Enabled: false
Check Table Before Drop: false
Store: unistore
Kernel Type: Classic
```

Built from source commit `a514a92784c9654502686e6ee6efc9e0aeda8afa` (pingcap/tidb master, 2026-09-07).

### 5. Root cause (optional)

- pkg/sessionctx/variable/session.go:3955-3968 PessimisticLockEligible returns false for autocommit non-DML statements
- pkg/planner/core/point_get_plan.go:142-160 getLockWaitTime depends on that eligibility
- pkg/planner/core/planbuilder.go:1254-1265 isForUpdateReadSelectLock does not force locking in autocommit

Contributor guide

Open the contributing guide

Research direction

Start with the autocommit eligibility logic in pkg/sessionctx/variable/session.go:3955-3968, then trace getLockWaitTime in pkg/planner/core/point_get_plan.go:142-160 and isForUpdateReadSelectLock in pkg/planner/core/planbuilder.go:1254-1265. Run the issue's two-session SQL reproduction with innodb_lock_wait_timeout=1. Done means autocommit SELECT ... FOR UPDATE waits for the conflicting row lock and reports the expected timeout, or is explicitly rejected/documented as a no-lock read.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, mysql, sql
Domain
databases, distributed-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.