mirror of
https://github.com/MariaDB/server.git
synced 2025-01-17 04:22:27 +01:00
MDEV-14589 InnoDB should not lock a delete-marked record
When the transaction isolation level is SERIALIZABLE, or when a locking read is performed in the REPEATABLE READ isolation level, InnoDB must lock delete-marked records in order to prevent another transaction from inserting something. However, at READ UNCOMMITTED or READ COMMITTED isolation level or when the parameter innodb_locks_unsafe_for_binlog is set, the repeatability of the reads does not matter, and there is no need to lock any records. row_search_for_mysql(): Skip locks on delete-marked committed records upfront, instead of invoking row_unlock_for_mysql() afterwards. The unlocking never worked for secondary index records.
This commit is contained in:
parent
d8da920264
commit
35a9c90fff
4 changed files with 154 additions and 28 deletions
40
mysql-test/suite/innodb/r/lock_deleted.result
Normal file
40
mysql-test/suite/innodb/r/lock_deleted.result
Normal file
|
@ -0,0 +1,40 @@
|
|||
START TRANSACTION WITH CONSISTENT SNAPSHOT;
|
||||
CREATE TABLE t1(a INT PRIMARY KEY, b INT UNIQUE) ENGINE=InnoDB;
|
||||
INSERT INTO t1 VALUES(1,1);
|
||||
DELETE FROM t1;
|
||||
SET DEBUG_SYNC='row_ins_sec_index_unique SIGNAL inserted WAIT_FOR locked';
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES(1,1);
|
||||
SET DEBUG_SYNC='now WAIT_FOR inserted';
|
||||
SET DEBUG_SYNC='innodb_row_search_for_mysql_exit SIGNAL locked';
|
||||
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
||||
BEGIN;
|
||||
DELETE FROM t1 WHERE b=1;
|
||||
COMMIT;
|
||||
SET DEBUG_SYNC='RESET';
|
||||
ROLLBACK;
|
||||
SET DEBUG_SYNC='row_ins_sec_index_unique SIGNAL inserted WAIT_FOR locked';
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES(1,1);
|
||||
SET DEBUG_SYNC='now WAIT_FOR inserted';
|
||||
SET DEBUG_SYNC='innodb_row_search_for_mysql_exit SIGNAL locked';
|
||||
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
|
||||
BEGIN;
|
||||
DELETE FROM t1 WHERE b=1;
|
||||
COMMIT;
|
||||
SET DEBUG_SYNC='RESET';
|
||||
ROLLBACK;
|
||||
SET DEBUG_SYNC='row_ins_sec_index_unique SIGNAL inserted WAIT_FOR locked';
|
||||
BEGIN;
|
||||
SET innodb_lock_wait_timeout=1;
|
||||
INSERT INTO t1 VALUES(1,1);
|
||||
SET DEBUG_SYNC='now WAIT_FOR inserted';
|
||||
SET DEBUG_SYNC='innodb_row_search_for_mysql_exit SIGNAL locked';
|
||||
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
|
||||
BEGIN;
|
||||
DELETE FROM t1 WHERE b=1;
|
||||
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
|
||||
COMMIT;
|
||||
SET DEBUG_SYNC='RESET';
|
||||
COMMIT;
|
||||
DROP TABLE t1;
|
72
mysql-test/suite/innodb/t/lock_deleted.test
Normal file
72
mysql-test/suite/innodb/t/lock_deleted.test
Normal file
|
@ -0,0 +1,72 @@
|
|||
--source include/have_innodb.inc
|
||||
--source include/have_debug.inc
|
||||
--source include/have_debug_sync.inc
|
||||
|
||||
--source include/count_sessions.inc
|
||||
|
||||
connect(stop_purge, localhost, root,,);
|
||||
START TRANSACTION WITH CONSISTENT SNAPSHOT;
|
||||
connect(delete, localhost, root,,);
|
||||
connection default;
|
||||
|
||||
CREATE TABLE t1(a INT PRIMARY KEY, b INT UNIQUE) ENGINE=InnoDB;
|
||||
INSERT INTO t1 VALUES(1,1);
|
||||
DELETE FROM t1;
|
||||
|
||||
let $i=2;
|
||||
while ($i) {
|
||||
let $iso= `SELECT CASE $i WHEN 1 THEN 'UNCOMMITTED' ELSE 'COMMITTED' END`;
|
||||
|
||||
SET DEBUG_SYNC='row_ins_sec_index_unique SIGNAL inserted WAIT_FOR locked';
|
||||
BEGIN;
|
||||
send INSERT INTO t1 VALUES(1,1);
|
||||
|
||||
connection delete;
|
||||
SET DEBUG_SYNC='now WAIT_FOR inserted';
|
||||
SET DEBUG_SYNC='innodb_row_search_for_mysql_exit SIGNAL locked';
|
||||
eval SET SESSION TRANSACTION ISOLATION LEVEL READ $iso;
|
||||
BEGIN;
|
||||
send DELETE FROM t1 WHERE b=1;
|
||||
|
||||
connection default;
|
||||
reap;
|
||||
connection delete;
|
||||
reap;
|
||||
COMMIT;
|
||||
|
||||
connection default;
|
||||
SET DEBUG_SYNC='RESET';
|
||||
ROLLBACK;
|
||||
|
||||
dec $i;
|
||||
}
|
||||
|
||||
SET DEBUG_SYNC='row_ins_sec_index_unique SIGNAL inserted WAIT_FOR locked';
|
||||
BEGIN;
|
||||
SET innodb_lock_wait_timeout=1;
|
||||
send INSERT INTO t1 VALUES(1,1);
|
||||
|
||||
connection delete;
|
||||
SET DEBUG_SYNC='now WAIT_FOR inserted';
|
||||
SET DEBUG_SYNC='innodb_row_search_for_mysql_exit SIGNAL locked';
|
||||
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
|
||||
BEGIN;
|
||||
send DELETE FROM t1 WHERE b=1;
|
||||
|
||||
connection default;
|
||||
--error ER_LOCK_WAIT_TIMEOUT
|
||||
reap;
|
||||
COMMIT;
|
||||
SET DEBUG_SYNC='RESET';
|
||||
|
||||
connection delete;
|
||||
reap;
|
||||
COMMIT;
|
||||
|
||||
disconnect delete;
|
||||
disconnect stop_purge;
|
||||
|
||||
connection default;
|
||||
DROP TABLE t1;
|
||||
|
||||
--source include/wait_until_count_sessions.inc
|
|
@ -4656,9 +4656,27 @@ wrong_offs:
|
|||
|
||||
ulint lock_type;
|
||||
|
||||
if (srv_locks_unsafe_for_binlog
|
||||
|| trx->isolation_level <= TRX_ISO_READ_COMMITTED) {
|
||||
/* At READ COMMITTED or READ UNCOMMITTED
|
||||
isolation levels, do not lock committed
|
||||
delete-marked records. */
|
||||
if (!rec_get_deleted_flag(rec, comp)) {
|
||||
goto no_gap_lock;
|
||||
}
|
||||
if (trx_id_t trx_id = index == clust_index
|
||||
? row_get_rec_trx_id(rec, index, offsets)
|
||||
: row_vers_impl_x_locked(rec, index, offsets)) {
|
||||
if (trx_rw_is_active(trx_id, NULL)) {
|
||||
/* The record belongs to an active
|
||||
transaction. We must acquire a lock. */
|
||||
goto no_gap_lock;
|
||||
}
|
||||
}
|
||||
goto locks_ok_del_marked;
|
||||
}
|
||||
|
||||
if (!set_also_gap_locks
|
||||
|| srv_locks_unsafe_for_binlog
|
||||
|| trx->isolation_level <= TRX_ISO_READ_COMMITTED
|
||||
|| (unique_search && !rec_get_deleted_flag(rec, comp))) {
|
||||
|
||||
goto no_gap_lock;
|
||||
|
@ -4849,20 +4867,9 @@ locks_ok:
|
|||
page_rec_is_comp() cannot be used! */
|
||||
|
||||
if (rec_get_deleted_flag(rec, comp)) {
|
||||
|
||||
locks_ok_del_marked:
|
||||
/* The record is delete-marked: we can skip it */
|
||||
|
||||
if ((srv_locks_unsafe_for_binlog
|
||||
|| trx->isolation_level <= TRX_ISO_READ_COMMITTED)
|
||||
&& prebuilt->select_lock_type != LOCK_NONE
|
||||
&& !did_semi_consistent_read) {
|
||||
|
||||
/* No need to keep a lock on a delete-marked record
|
||||
if we do not want to use next-key locking. */
|
||||
|
||||
row_unlock_for_mysql(prebuilt, TRUE);
|
||||
}
|
||||
|
||||
/* This is an optimization to skip setting the next key lock
|
||||
on the record that follows this delete-marked record. This
|
||||
optimization works because of the unique search criteria
|
||||
|
|
|
@ -4665,9 +4665,27 @@ wrong_offs:
|
|||
|
||||
ulint lock_type;
|
||||
|
||||
if (srv_locks_unsafe_for_binlog
|
||||
|| trx->isolation_level <= TRX_ISO_READ_COMMITTED) {
|
||||
/* At READ COMMITTED or READ UNCOMMITTED
|
||||
isolation levels, do not lock committed
|
||||
delete-marked records. */
|
||||
if (!rec_get_deleted_flag(rec, comp)) {
|
||||
goto no_gap_lock;
|
||||
}
|
||||
if (trx_id_t trx_id = index == clust_index
|
||||
? row_get_rec_trx_id(rec, index, offsets)
|
||||
: row_vers_impl_x_locked(rec, index, offsets)) {
|
||||
if (trx_rw_is_active(trx_id, NULL)) {
|
||||
/* The record belongs to an active
|
||||
transaction. We must acquire a lock. */
|
||||
goto no_gap_lock;
|
||||
}
|
||||
}
|
||||
goto locks_ok_del_marked;
|
||||
}
|
||||
|
||||
if (!set_also_gap_locks
|
||||
|| srv_locks_unsafe_for_binlog
|
||||
|| trx->isolation_level <= TRX_ISO_READ_COMMITTED
|
||||
|| (unique_search && !rec_get_deleted_flag(rec, comp))) {
|
||||
|
||||
goto no_gap_lock;
|
||||
|
@ -4862,20 +4880,9 @@ locks_ok:
|
|||
page_rec_is_comp() cannot be used! */
|
||||
|
||||
if (rec_get_deleted_flag(rec, comp)) {
|
||||
|
||||
locks_ok_del_marked:
|
||||
/* The record is delete-marked: we can skip it */
|
||||
|
||||
if ((srv_locks_unsafe_for_binlog
|
||||
|| trx->isolation_level <= TRX_ISO_READ_COMMITTED)
|
||||
&& prebuilt->select_lock_type != LOCK_NONE
|
||||
&& !did_semi_consistent_read) {
|
||||
|
||||
/* No need to keep a lock on a delete-marked record
|
||||
if we do not want to use next-key locking. */
|
||||
|
||||
row_unlock_for_mysql(prebuilt, TRUE);
|
||||
}
|
||||
|
||||
/* This is an optimization to skip setting the next key lock
|
||||
on the record that follows this delete-marked record. This
|
||||
optimization works because of the unique search criteria
|
||||
|
|
Loading…
Reference in a new issue