Commit 35a9c90f authored by Marko Mäkelä's avatar Marko Mäkelä

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.
parent d8da9202
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;
--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 @@ row_search_for_mysql(
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 @@ row_search_for_mysql(
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 @@ row_search_for_mysql(
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 @@ row_search_for_mysql(
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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment