mirror of
https://github.com/MariaDB/server.git
synced 2025-02-07 06:12:18 +01:00
![Marko Mäkelä](/assets/img/avatar_default.png)
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
85 lines
2.6 KiB
Text
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
|