1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00

MDEV-40123 Assertion failure after lock wait is unblocked in SKIP LOCKED

A locking read with SKIP LOCKED through a secondary index calls
row_sel_get_clust_rec_for_mysql() to lock the clustered index record.
On a lock conflict, that function returned DB_LOCK_WAIT without
setting the output record, and row_search_mvcc() then called
lock_trx_handle_wait() to cancel the wait. If the conflicting
transaction had committed in the meantime, the waiting lock was
already granted and lock_trx_handle_wait() returned DB_SUCCESS.
row_search_mvcc() then found clust_rec == NULL for a locking read:
debug builds failed an assertion, and release builds skipped a row
whose lock the transaction now held.

Row_sel_get_clust_rec_for_mysql::operator()(): Call
lock_trx_handle_wait() here instead of in row_search_mvcc(). When
the lock was granted meanwhile, return the clustered index record
and store the cursor position as for any granted lock, like
row_search_mvcc() does with "goto locks_ok" for the record that it
is positioned on.
This commit is contained in:
Alessandro Vetere
2026-10-14 11:26:00 +02:00
committed by Daniel Black
parent 5dabf1c52d
commit 412d8fa525
3 changed files with 251 additions and 3 deletions

View File

@@ -0,0 +1,116 @@
#
# MDEV-40123 Assertion failure after lock wait is unblocked in SKIP LOCKED
#
CREATE TABLE t1 (pk INT PRIMARY KEY, a INT, b INT, KEY(a)) ENGINE=InnoDB;
INSERT INTO t1 VALUES (1,1,1),(2,2,2),(3,3,3);
connect con1,localhost,root;
connect con2,localhost,root;
# The lock on the clustered index record is granted after the wait
connection con1;
BEGIN;
SELECT b FROM t1 WHERE pk=1 FOR UPDATE;
b
1
connection con2;
BEGIN;
SET DEBUG_SYNC='lock_trx_handle_wait_enter SIGNAL waiting WAIT_FOR go';
SELECT b FROM t1 FORCE INDEX (a) ORDER BY a FOR UPDATE SKIP LOCKED;
connection con1;
SET DEBUG_SYNC='now WAIT_FOR waiting';
COMMIT;
SET DEBUG_SYNC='now SIGNAL go';
connection con2;
b
1
2
3
connection con1;
SELECT pk FROM t1 WHERE pk=1 FOR UPDATE SKIP LOCKED;
pk
connection con2;
COMMIT;
# The granted lock covers a row whose non-key column was updated
connection con1;
BEGIN;
UPDATE t1 SET b=10 WHERE pk=1;
connection con2;
BEGIN;
SET DEBUG_SYNC='lock_trx_handle_wait_enter SIGNAL waiting WAIT_FOR go';
SELECT b FROM t1 FORCE INDEX (a) ORDER BY a FOR UPDATE SKIP LOCKED;
connection con1;
SET DEBUG_SYNC='now WAIT_FOR waiting';
COMMIT;
SET DEBUG_SYNC='now SIGNAL go';
connection con2;
b
10
2
3
connection con1;
SELECT pk FROM t1 WHERE pk=1 FOR UPDATE SKIP LOCKED;
pk
connection con2;
COMMIT;
# The lock on the delete-marked secondary index record is granted
connection con1;
BEGIN;
DELETE FROM t1 WHERE pk=1;
connection con2;
BEGIN;
SET DEBUG_SYNC='lock_trx_handle_wait_enter SIGNAL waiting WAIT_FOR go';
SELECT b FROM t1 FORCE INDEX (a) ORDER BY a FOR UPDATE SKIP LOCKED;
connection con1;
SET DEBUG_SYNC='now WAIT_FOR waiting';
COMMIT;
SET DEBUG_SYNC='now SIGNAL go';
connection con2;
b
2
3
COMMIT;
# The lock is still held: the record is skipped
connection con1;
BEGIN;
SELECT b FROM t1 WHERE pk=2 FOR UPDATE;
b
2
connection con2;
BEGIN;
SELECT b FROM t1 FORCE INDEX (a) ORDER BY a FOR UPDATE SKIP LOCKED;
b
3
COMMIT;
connection con1;
COMMIT;
# A deadlock is reported while waiting for the lock
CREATE TABLE t2 (x INT) ENGINE=InnoDB;
connection con1;
BEGIN;
INSERT INTO t2 VALUES (1),(2),(3),(4),(5);
SELECT b FROM t1 WHERE pk=2 FOR UPDATE;
b
2
connection con2;
BEGIN;
SET DEBUG_SYNC='lock_trx_handle_wait_enter SIGNAL waiting WAIT_FOR go';
SELECT b FROM t1 FORCE INDEX (a) ORDER BY a FOR UPDATE SKIP LOCKED;
connection con1;
SET DEBUG_SYNC='now WAIT_FOR waiting';
SET DEBUG_SYNC='deadlock_report_before_lock_releasing SIGNAL deadlock';
SELECT pk FROM t1 FORCE INDEX (a) WHERE a=2 FOR UPDATE;
connection default;
SET DEBUG_SYNC='now WAIT_FOR deadlock';
SET DEBUG_SYNC='now SIGNAL go';
connection con2;
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
connection con1;
pk
2
disconnect con2;
connection con1;
COMMIT;
disconnect con1;
connection default;
SET DEBUG_SYNC='RESET';
DROP TABLE t1, t2;
# End of 11.4 tests

View File

@@ -0,0 +1,128 @@
--source include/have_innodb.inc
--source include/have_debug_sync.inc
--echo #
--echo # MDEV-40123 Assertion failure after lock wait is unblocked in SKIP LOCKED
--echo #
CREATE TABLE t1 (pk INT PRIMARY KEY, a INT, b INT, KEY(a)) ENGINE=InnoDB;
INSERT INTO t1 VALUES (1,1,1),(2,2,2),(3,3,3);
connect con1,localhost,root;
connect con2,localhost,root;
--echo # The lock on the clustered index record is granted after the wait
connection con1;
BEGIN;
SELECT b FROM t1 WHERE pk=1 FOR UPDATE;
connection con2;
BEGIN;
SET DEBUG_SYNC='lock_trx_handle_wait_enter SIGNAL waiting WAIT_FOR go';
send SELECT b FROM t1 FORCE INDEX (a) ORDER BY a FOR UPDATE SKIP LOCKED;
connection con1;
SET DEBUG_SYNC='now WAIT_FOR waiting';
COMMIT;
SET DEBUG_SYNC='now SIGNAL go';
connection con2;
reap;
connection con1;
SELECT pk FROM t1 WHERE pk=1 FOR UPDATE SKIP LOCKED;
connection con2;
COMMIT;
--echo # The granted lock covers a row whose non-key column was updated
connection con1;
BEGIN;
UPDATE t1 SET b=10 WHERE pk=1;
connection con2;
BEGIN;
SET DEBUG_SYNC='lock_trx_handle_wait_enter SIGNAL waiting WAIT_FOR go';
send SELECT b FROM t1 FORCE INDEX (a) ORDER BY a FOR UPDATE SKIP LOCKED;
connection con1;
SET DEBUG_SYNC='now WAIT_FOR waiting';
COMMIT;
SET DEBUG_SYNC='now SIGNAL go';
connection con2;
reap;
connection con1;
SELECT pk FROM t1 WHERE pk=1 FOR UPDATE SKIP LOCKED;
connection con2;
COMMIT;
--echo # The lock on the delete-marked secondary index record is granted
connection con1;
BEGIN;
DELETE FROM t1 WHERE pk=1;
connection con2;
BEGIN;
SET DEBUG_SYNC='lock_trx_handle_wait_enter SIGNAL waiting WAIT_FOR go';
send SELECT b FROM t1 FORCE INDEX (a) ORDER BY a FOR UPDATE SKIP LOCKED;
connection con1;
SET DEBUG_SYNC='now WAIT_FOR waiting';
COMMIT;
SET DEBUG_SYNC='now SIGNAL go';
connection con2;
reap;
COMMIT;
--echo # The lock is still held: the record is skipped
connection con1;
BEGIN;
SELECT b FROM t1 WHERE pk=2 FOR UPDATE;
connection con2;
BEGIN;
SELECT b FROM t1 FORCE INDEX (a) ORDER BY a FOR UPDATE SKIP LOCKED;
COMMIT;
connection con1;
COMMIT;
--echo # A deadlock is reported while waiting for the lock
CREATE TABLE t2 (x INT) ENGINE=InnoDB;
connection con1;
BEGIN;
# Make con1 heavier than con2, so that con2 is chosen as the deadlock victim
INSERT INTO t2 VALUES (1),(2),(3),(4),(5);
SELECT b FROM t1 WHERE pk=2 FOR UPDATE;
connection con2;
BEGIN;
SET DEBUG_SYNC='lock_trx_handle_wait_enter SIGNAL waiting WAIT_FOR go';
send SELECT b FROM t1 FORCE INDEX (a) ORDER BY a FOR UPDATE SKIP LOCKED;
connection con1;
SET DEBUG_SYNC='now WAIT_FOR waiting';
SET DEBUG_SYNC='deadlock_report_before_lock_releasing SIGNAL deadlock';
send SELECT pk FROM t1 FORCE INDEX (a) WHERE a=2 FOR UPDATE;
connection default;
SET DEBUG_SYNC='now WAIT_FOR deadlock';
SET DEBUG_SYNC='now SIGNAL go';
connection con2;
--error ER_LOCK_DEADLOCK
reap;
connection con1;
reap;
disconnect con2;
connection con1;
COMMIT;
disconnect con1;
connection default;
SET DEBUG_SYNC='RESET';
DROP TABLE t1, t2;
--echo # End of 11.4 tests

View File

@@ -3518,6 +3518,13 @@ Row_sel_get_clust_rec_for_mysql::operator()(
LOCK_REC_NOT_GAP,
thr);
if (err == DB_LOCK_WAIT && prebuilt->skip_locked) {
/* Cancel the lock wait, unless the lock was granted
meanwhile; then continue with a normal locking read
of clust_rec. */
err = lock_trx_handle_wait(trx);
}
switch (err) {
case DB_SUCCESS:
case DB_SUCCESS_LOCKED_REC:
@@ -5555,9 +5562,6 @@ requires_clust_rec:
&offsets, &heap,
need_vrow ? &vrow : NULL,
&mtr);
if (err == DB_LOCK_WAIT && prebuilt->skip_locked) {
err = lock_trx_handle_wait(trx);
}
switch (err) {
case DB_SUCCESS:
if (clust_rec == NULL) {