mariadb/mysql-test/suite/innodb/t/deadlock_in_subqueries_join.test
Marko Mäkelä ddd7d5d8e3 MDEV-24035 Failing assertion: UT_LIST_GET_LEN(lock.trx_locks) == 0 causing disruption and replication failure
Under unknown circumstances, the SQL layer may wrongly disregard an
invocation of thd_mark_transaction_to_rollback() when an InnoDB
transaction had been aborted (rolled back) due to one of the following errors:
* HA_ERR_LOCK_DEADLOCK
* HA_ERR_RECORD_CHANGED (if innodb_snapshot_isolation=ON)
* HA_ERR_LOCK_WAIT_TIMEOUT (if innodb_rollback_on_timeout=ON)

Such an error used to cause a crash of InnoDB during transaction commit.
These changes aim to catch and report the error earlier, so that not only
this crash can be avoided but also the original root cause be found and
fixed more easily later.

The idea of this fix is from Michael 'Monty' Widenius.

HA_ERR_ROLLBACK: A new error code that will be translated into
ER_ROLLBACK_ONLY, signalling that the current transaction
has been aborted and the only allowed action is ROLLBACK.

trx_t::state: Add TRX_STATE_ABORTED that is like
TRX_STATE_NOT_STARTED, but noting that the transaction had been
rolled back and aborted.

trx_t::is_started(): Replaces trx_is_started().

ha_innobase: Check the transaction state in various places.
Simplify the logic around SAVEPOINT.

ha_innobase::is_valid_trx(): Replaces ha_innobase::is_read_only().

The InnoDB logic around transaction savepoints, commit, and rollback
was unnecessarily complex and might have contributed to this
inconsistency. So, we are simplifying that logic as well.

trx_savept_t: Replace with const undo_no_t*. When we rollback to
a savepoint, all we need to know is the number of undo log records
that must survive.

trx_named_savept_t, DB_NO_SAVEPOINT: Remove. We can store undo_no_t
directly in the space allocated at innobase_hton->savepoint_offset.

fts_trx_create(): Do not copy previous savepoints.

fts_savepoint_rollback(): If a savepoint was not found, roll back
everything after the default savepoint of fts_trx_create().
The test innodb_fts.savepoint is extended to cover this code.

Reviewed by: Vladislav Lesin
Tested by: Matthias Leich
2024-12-12 18:02:00 +02:00

85 lines
2.6 KiB
Text

--source include/have_innodb.inc
--source include/count_sessions.inc
--disable_query_log
call mtr.add_suppression("InnoDB: Transaction was aborted due to ");
--enable_query_log
CREATE TABLE t1 (
pkey int NOT NULL PRIMARY KEY,
c int
) ENGINE=InnoDB;
INSERT INTO t1 VALUES(1,1);
CREATE TABLE t2 (
pkey int NOT NULL PRIMARY KEY,
c int
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO t2 VALUES (2, NULL);
# The following table is to increase tansaction weight on deadlock resolution
CREATE TABLE t3 (c int) engine = InnoDB;
INSERT INTO t3 VALUES (10), (20), (30), (40), (50);
--let $i= 2
--let $delete= 2
--let $update= 1
--connect(con1, localhost,root,,)
while($i) {
--connection default
START TRANSACTION; # trx 1
# The following update is necessary to increase the transaction weight, which is
# calculated as the number of locks + the number of undo records during deadlock
# report. Victim's transaction should have minimum weight. We need trx 2 to be
# choosen as victim, that's why we need to increase the current transaction
# weight.
UPDATE t3 SET c=c+1000;
SELECT * FROM t1 FOR UPDATE;
--connection con1
START TRANSACTION; # trx 2
# 1) read record from t2, lock it
# 2) check if the read record should be deleted, i.e. read record from t1,
# as the record from t1 is locked by trx 1, the subselect will be suspended.
# see 'while' loop in mysql_delete() or mysql_update() and
# select->skip_record(thd) call for details.
if ($i == $delete) {
--send DELETE FROM t2 WHERE c NOT IN (SELECT ref_0.pkey FROM t1 AS ref_0 INNER JOIN t1 AS ref_1 ON ref_0.c = ref_0.pkey)
}
if ($i == $update) {
--send UPDATE t2 SET pkey=pkey+10 WHERE c NOT IN (SELECT ref_0.pkey FROM t1 AS ref_0 INNER JOIN t1 AS ref_1 ON ref_0.c = ref_0.pkey)
}
--connection default
let $wait_condition=
SELECT count(*) = 1 FROM information_schema.processlist
WHERE (state = 'Sending data' OR state = "Updating")
AND (info LIKE 'delete from t2 where%' OR
info LIKE 'UPDATE t2 SET pkey=pkey+10 WHERE%');
--source include/wait_condition.inc
# The record from t2 is locked by the previous delete, so trx 2 is waiting for
# trx 1, and trx 1 will be blocked by trx 2 with the following SELECT. So we
# have deadlock here. And trx 2 is chosen as deadlock victim as trx 1 has
# greater weight.
SELECT * FROM t2 FOR UPDATE;
COMMIT;
--connection con1
# If the bug is not fixed, there will be assertion failure as
# mysql_delete()/mysql_update() will continue execution despite its subselect
# got deadlock error
--error ER_LOCK_DEADLOCK
--reap
COMMIT;
--dec $i
}
--disconnect con1
--connection default
DROP TABLE t1,t2,t3;
--source include/wait_until_count_sessions.inc