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:
committed by
Daniel Black
parent
5dabf1c52d
commit
412d8fa525
116
mysql-test/suite/innodb/r/skip_locked_wait_granted.result
Normal file
116
mysql-test/suite/innodb/r/skip_locked_wait_granted.result
Normal 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
|
||||
128
mysql-test/suite/innodb/t/skip_locked_wait_granted.test
Normal file
128
mysql-test/suite/innodb/t/skip_locked_wait_granted.test
Normal 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
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user