Skip to content
This repository was archived by the owner on Mar 1, 2026. It is now read-only.

Commit ba2a42e

Browse files
yizhang82Herman Lee
authored andcommitted
Support SELECT FOR UPDATE SKIP LOCKED / NOWAIT
Summary: SKIP LOCKED is very similar with TTL / ICP that we need to skip locked rows and proceed to the next one. 5.6 didn't have correct implementation even for TTL in many cases (index_last / index_first / index_read_map_impl) but in 8.0 Manuel's refactoring made things much easier. Note that when skipping locked rows, we also skip rows that can cause deadlock and snapshot conflict in the spirit of SKIP LOCKED in order to lock the rows that we can update later. NOWAIT is quite straight-forward - just set timeout to 0 and restore it after we are done. We still return ER_LOCK_WAIT_TIMEOUT instead of ER_LOCK_NOWAIT (in InnoDB) if we couldn't take the lock and personally I don't think it makes much difference. Reviewed By: lth Differential Revision: D28922476
1 parent ccf04ee commit ba2a42e

10 files changed

Lines changed: 511 additions & 359 deletions
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
#
2+
# wl#8919 Implement NOWAIT and SKIP LOCKED
3+
#
4+
5+
# needed by start_transaction_high_prio.inc
6+
--source include/have_debug.inc
7+
8+
--source include/count_sessions.inc
9+
10+
connect (con1,localhost,root,,);
11+
if ($engine=="INNODB")
12+
{
13+
SET SESSION innodb_lock_wait_timeout=1;
14+
}
15+
if ($engine=="ROCKSDB")
16+
{
17+
SET SESSION rocksdb_lock_wait_timeout=1;
18+
}
19+
20+
connection default;
21+
if ($engine=="INNODB")
22+
{
23+
SET SESSION innodb_lock_wait_timeout=1;
24+
}
25+
if ($engine=="ROCKSDB")
26+
{
27+
SET SESSION rocksdb_lock_wait_timeout=1;
28+
}
29+
30+
--echo # Case 1: Test primary index
31+
eval CREATE TABLE t1(
32+
seat_id INT,
33+
state INT,
34+
PRIMARY KEY(seat_id)
35+
) ENGINE=$engine;
36+
37+
INSERT INTO t1 VALUES(1,0), (2,0), (3,0), (4,0);
38+
39+
BEGIN;
40+
SELECT * FROM t1 WHERE state = 0 LIMIT 2 FOR SHARE;
41+
42+
connection con1;
43+
BEGIN;
44+
45+
SELECT * FROM t1 WHERE state = 0 LIMIT 2 LOCK IN SHARE MODE;
46+
SELECT * FROM t1 WHERE state = 0 LIMIT 2 FOR SHARE;
47+
48+
SELECT * FROM t1 WHERE state = 0 LIMIT 2 FOR SHARE NOWAIT;
49+
SELECT * FROM t1 WHERE state = 0 LIMIT 2 FOR SHARE SKIP LOCKED;
50+
51+
--error ER_LOCK_WAIT_TIMEOUT
52+
SELECT * FROM t1 WHERE state = 0 LIMIT 2 FOR UPDATE;
53+
54+
--error ER_LOCK_NOWAIT, ER_LOCK_WAIT_TIMEOUT
55+
SELECT * FROM t1 WHERE state = 0 LIMIT 2 FOR UPDATE NOWAIT;
56+
SELECT * FROM t1 WHERE state = 0 LIMIT 2 FOR UPDATE SKIP LOCKED;
57+
58+
--error ER_LOCK_NOWAIT, ER_LOCK_WAIT_TIMEOUT
59+
SELECT * FROM t1 WHERE seat_id > 0 LIMIT 2 FOR UPDATE NOWAIT;
60+
SELECT * FROM t1 WHERE seat_id > 0 LIMIT 2 FOR UPDATE SKIP LOCKED;
61+
62+
COMMIT;
63+
64+
connection default;
65+
SELECT * FROM t1 WHERE state = 0 LIMIT 2 FOR UPDATE;
66+
67+
connection con1;
68+
--error ER_LOCK_WAIT_TIMEOUT
69+
SELECT * FROM t1 WHERE state = 0 LIMIT 2 LOCK IN SHARE MODE;
70+
71+
--error ER_LOCK_WAIT_TIMEOUT
72+
SELECT * FROM t1 WHERE state = 0 LIMIT 2 FOR SHARE;
73+
74+
--error ER_LOCK_NOWAIT, ER_LOCK_WAIT_TIMEOUT
75+
SELECT * FROM t1 WHERE state = 0 LIMIT 2 FOR SHARE NOWAIT;
76+
SELECT * FROM t1 WHERE state = 0 LIMIT 2 FOR SHARE SKIP LOCKED;
77+
78+
--error ER_LOCK_WAIT_TIMEOUT
79+
SELECT * FROM t1 WHERE state = 0 LIMIT 2 FOR UPDATE;
80+
81+
--error ER_LOCK_NOWAIT, ER_LOCK_WAIT_TIMEOUT
82+
SELECT * FROM t1 WHERE state = 0 LIMIT 2 FOR UPDATE NOWAIT;
83+
SELECT * FROM t1 WHERE state = 0 LIMIT 2 FOR UPDATE SKIP LOCKED;
84+
85+
--error ER_LOCK_NOWAIT, ER_LOCK_WAIT_TIMEOUT
86+
SELECT * FROM t1 WHERE seat_id > 0 LIMIT 2 FOR UPDATE NOWAIT;
87+
SELECT * FROM t1 WHERE seat_id > 0 LIMIT 2 FOR UPDATE SKIP LOCKED;
88+
89+
COMMIT;
90+
91+
connection default;
92+
COMMIT;
93+
94+
DROP TABLE t1;
95+
96+
--echo # Case 2: Test primary index & secondary index
97+
eval CREATE TABLE t1(
98+
seat_id INT,
99+
row_id INT,
100+
state INT,
101+
PRIMARY KEY(seat_id),
102+
KEY(row_id)
103+
) ENGINE=$engine;
104+
105+
INSERT INTO t1 VALUES(1,1,0), (2,1,0), (3,2,0), (4,2,0);
106+
107+
--echo # Test secondary key
108+
# Case 2a: secondary blocks secondary/primary
109+
BEGIN;
110+
SELECT * FROM t1 WHERE state = 0 AND row_id = 1 LIMIT 1 FOR UPDATE NOWAIT;
111+
112+
connection con1;
113+
BEGIN;
114+
--error ER_LOCK_NOWAIT, ER_LOCK_WAIT_TIMEOUT
115+
SELECT * FROM t1 WHERE state = 0 AND row_id = 1 LIMIT 1 FOR UPDATE NOWAIT;
116+
SELECT * FROM t1 WHERE state = 0 AND row_id = 1 LIMIT 1 FOR UPDATE SKIP LOCKED;
117+
118+
--error ER_LOCK_NOWAIT, ER_LOCK_WAIT_TIMEOUT
119+
SELECT * FROM t1 WHERE state = 0 AND row_id > 0 LIMIT 1 FOR UPDATE NOWAIT;
120+
SELECT * FROM t1 WHERE state = 0 AND row_id > 0 LIMIT 1 FOR UPDATE SKIP LOCKED;
121+
122+
--error ER_LOCK_NOWAIT, ER_LOCK_WAIT_TIMEOUT
123+
SELECT * FROM t1 WHERE state = 0 FOR UPDATE NOWAIT;
124+
SELECT * FROM t1 WHERE state = 0 FOR UPDATE SKIP LOCKED;
125+
126+
COMMIT;
127+
128+
connection default;
129+
COMMIT;
130+
131+
# Case 2b: primary blocks secondary/primary
132+
BEGIN;
133+
SELECT * FROM t1 WHERE seat_id = 1 FOR UPDATE NOWAIT;
134+
135+
connection con1;
136+
BEGIN;
137+
--error ER_LOCK_NOWAIT, ER_LOCK_WAIT_TIMEOUT
138+
SELECT * FROM t1 WHERE state = 0 AND row_id = 1 LIMIT 1 FOR UPDATE NOWAIT;
139+
SELECT * FROM t1 WHERE state = 0 AND row_id = 1 LIMIT 1 FOR UPDATE SKIP LOCKED;
140+
141+
--error ER_LOCK_NOWAIT, ER_LOCK_WAIT_TIMEOUT
142+
SELECT * FROM t1 WHERE state = 0 FOR UPDATE NOWAIT;
143+
SELECT * FROM t1 WHERE state = 0 FOR UPDATE SKIP LOCKED;
144+
145+
COMMIT;
146+
147+
connection default;
148+
COMMIT;
149+
150+
DROP TABLE t1;
151+
152+
if ($engine=='INNODB')
153+
{
154+
--echo # Case 3: Test primary index & spatial index
155+
eval CREATE TABLE t1(
156+
seat_id INT,
157+
pos POINT NOT NULL SRID 0,
158+
state INT,
159+
PRIMARY KEY(seat_id),
160+
SPATIAL KEY(pos)
161+
) ENGINE=$engine;
162+
163+
INSERT INTO t1 VALUES
164+
(1,ST_PointFromText('POINT(1 0)'),0),
165+
(2,ST_PointFromText('POINT(1 1)'),0),
166+
(3,ST_PointFromText('POINT(2 0)'),0),
167+
(4,ST_PointFromText('POINT(2 1)'),0),
168+
(5,ST_PointFromText('POINT(3 0)'),0),
169+
(6,ST_PointFromText('POINT(3 1)'),0);
170+
171+
# Case 3a: secondary blocks secondary/primary
172+
BEGIN;
173+
SET @g = ST_GeomFromText('POLYGON((0 0,0 2,2 2,0 2,0 0))');
174+
# the first 4 records in the rtree index page are locked
175+
SELECT seat_id, state, ST_AsText(pos) FROM t1 FORCE INDEX (pos)
176+
WHERE state = 0 AND MBRWithin(pos, @g) FOR UPDATE NOWAIT;
177+
178+
connection con1;
179+
BEGIN;
180+
SET @g = ST_GeomFromText('POLYGON((0 0,0 4,4 4,0 4,0 0))');
181+
--error ER_LOCK_NOWAIT, ER_LOCK_WAIT_TIMEOUT
182+
SELECT seat_id, state, ST_AsText(pos) FROM t1 FORCE INDEX (pos)
183+
WHERE state = 0 AND MBRWithin(pos, @g) FOR UPDATE NOWAIT;
184+
185+
SELECT seat_id, state, ST_AsText(pos) FROM t1 FORCE INDEX (pos)
186+
WHERE state = 0 AND MBRWithin(pos, @g) FOR UPDATE SKIP LOCKED;
187+
188+
--error ER_LOCK_NOWAIT, ER_LOCK_WAIT_TIMEOUT
189+
SELECT seat_id, state, ST_AsText(pos) FROM t1
190+
WHERE state = 0 FOR UPDATE NOWAIT;
191+
192+
SELECT seat_id, state, ST_AsText(pos) FROM t1
193+
WHERE state = 0 FOR UPDATE SKIP LOCKED;
194+
195+
COMMIT;
196+
197+
connection default;
198+
COMMIT;
199+
200+
# Case 3b: primary blockes secondary/primary
201+
connection con1;
202+
SET @g = ST_GeomFromText('POLYGON((0 0,0 3,3 3,0 3,0 0))');
203+
SELECT seat_id, state, ST_AsText(pos) FROM t1 FORCE INDEX (pos)
204+
WHERE state = 0 AND MBRWithin(pos, @g) FOR UPDATE;
205+
206+
connection default;
207+
BEGIN;
208+
SELECT seat_id, state, ST_AsText(pos) FROM t1
209+
WHERE seat_id = 4 FOR UPDATE NOWAIT;
210+
211+
connection con1;
212+
--error ER_LOCK_NOWAIT, ER_LOCK_WAIT_TIMEOUT
213+
SELECT seat_id, state, ST_AsText(pos) FROM t1 FORCE INDEX (pos)
214+
WHERE state = 0 AND MBRWithin(pos, @g) FOR UPDATE NOWAIT;
215+
216+
SELECT seat_id, state, ST_AsText(pos) FROM t1 FORCE INDEX (pos)
217+
WHERE state = 0 AND MBRWithin(pos, @g) FOR UPDATE SKIP LOCKED;
218+
219+
--error ER_LOCK_NOWAIT, ER_LOCK_WAIT_TIMEOUT
220+
SELECT seat_id, state, ST_AsText(pos) FROM t1
221+
WHERE state = 0 FOR UPDATE NOWAIT;
222+
223+
SELECT seat_id, state, ST_AsText(pos) FROM t1
224+
WHERE state = 0 FOR UPDATE SKIP LOCKED;
225+
226+
connection default;
227+
COMMIT;
228+
229+
DROP TABLE t1;
230+
231+
--echo # Case 4: Test high priority transaction
232+
eval CREATE TABLE t1(
233+
seat_id INT,
234+
state INT,
235+
PRIMARY KEY(seat_id)
236+
) ENGINE=$engine;
237+
238+
INSERT INTO t1 VALUES(1,0), (2,0), (3,0), (4,0);
239+
240+
BEGIN;
241+
SELECT * FROM t1 WHERE state = 0 LIMIT 2 FOR UPDATE;
242+
243+
connection con1;
244+
245+
--source include/start_transaction_high_prio.inc
246+
247+
SELECT * FROM t1 WHERE seat_id = 1 AND state = 0 FOR UPDATE NOWAIT;
248+
249+
SELECT * FROM t1 WHERE state = 0 LIMIT 2 FOR UPDATE SKIP LOCKED;
250+
251+
COMMIT;
252+
253+
connection default;
254+
--error ER_ERROR_DURING_COMMIT
255+
COMMIT;
256+
ROLLBACK;
257+
258+
DROP TABLE t1;
259+
}
260+
261+
disconnect con1;
262+
263+
--source include/wait_until_count_sessions.inc

0 commit comments

Comments
 (0)