mariadb/mysql-test/suite/innodb/t/autoinc_debug.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

164 lines
4.1 KiB
Text

--source include/have_innodb.inc
--source include/have_debug.inc
--source include/have_debug_sync.inc
--source include/not_embedded.inc
--disable_query_log
call mtr.add_suppression("InnoDB: Transaction was aborted due to ");
--enable_query_log
# Two parallel connection with autoinc column after restart.
CREATE TABLE t1 (id INT AUTO_INCREMENT PRIMARY KEY)ENGINE=INNODB;
--echo # SETTING auto_increment_increment IN CONNECTION DEFAULT
SET AUTO_INCREMENT_INCREMENT = 1;
--echo # MDEV-24348 InnoDB shutdown hang with innodb_flush_sync=0
SET GLOBAL innodb_flush_sync=OFF;
--echo # For the server to hang, we must have pages for temporary tables
--echo # (and the bug depended on MDEV-12227 not being fixed).
CREATE TEMPORARY TABLE t (id SERIAL) ENGINE=InnoDB;
SET debug_dbug= '+d,ib_log_flush_ahead';
INSERT INTO t1 VALUES(NULL);
SELECT * FROM t1;
SHOW CREATE TABLE t1;
--source include/restart_mysqld.inc
--echo # SETTING auto_increment_increment IN CONNECTION1
SET AUTO_INCREMENT_INCREMENT = 2;
SET DEBUG_SYNC= 'ib_after_row_insert SIGNAL opened WAIT_FOR flushed1';
SEND INSERT INTO t1 VALUES(NULL);
connect(con1, localhost, root,,);
SET AUTO_INCREMENT_INCREMENT = 2;
SET DEBUG_SYNC= 'now WAIT_FOR opened';
SET DEBUG_SYNC= 'ib_after_row_insert_step SIGNAL flushed1 WAIT_FOR opened1';
send insert into t1 values(NULL);
connection default;
reap;
SELECT * FROM t1;
SHOW CREATE TABLE t1;
SET DEBUG_SYNC= 'now SIGNAL opened1';
connection con1;
reap;
SELECT * FROM t1;
SHOW CREATE TABLE t1;
connection default;
disconnect con1;
DROP TABLE t1;
# Two parallel connection with autoinc column without restart.
CREATE TABLE t1(id INT AUTO_INCREMENT PRIMARY KEY)ENGINE=INNODB;
--echo # SETTING auto_increment_increment IN CONNECTION DEFAULT
SET AUTO_INCREMENT_INCREMENT = 1;
INSERT INTO t1 VALUES(NULL);
SELECT * FROM t1;
SHOW CREATE TABLE t1;
SET DEBUG_SYNC = 'now SIGNAL flushed';
connect(con1, localhost, root,,);
--echo # SETTING auto_increment_increment in connection1
SET AUTO_INCREMENT_INCREMENT = 2;
SET DEBUG_SYNC= 'now WAIT_FOR flushed';
SET DEBUG_SYNC= 'ib_after_row_insert SIGNAL opened WAIT_FOR flushed1';
send INSERT INTO t1 values(NULL);
connection default;
SET DEBUG_SYNC= 'now WAIT_FOR opened';
SET DEBUG_SYNC= 'ib_after_row_insert_step SIGNAL flushed1 WAIT_FOR opened1';
send INSERT INTO t1 VALUES(NULL);
connection con1;
reap;
SELECT * FROM t1;
SHOW CREATE TABLE t1;
SET DEBUG_SYNC= 'now SIGNAL opened1';
disconnect con1;
connection default;
reap;
SELECT * FROM t1;
SHOW CREATE TABLE t1;
DROP TABLE t1;
SET DEBUG_SYNC='RESET';
--echo #
--echo # MDEV-33593: Auto increment deadlock error causes ASSERT in subsequent save point
--echo #
CREATE TABLE t1(col1 INT PRIMARY KEY AUTO_INCREMENT, col2 INT) ENGINE=InnoDB;
CREATE TABLE t2(col1 INT PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO t1(col2) values(100);
--connect(con1, localhost, root,,)
START TRANSACTION;
--echo # T1: Acquiring Row X lock on table t2
INSERT INTO t2 values(100);
--connect(con2, localhost, root,,)
START TRANSACTION;
--echo # T2: Wait for (T1) row lock on t2 after acquiring GAP Lock on t1
UPDATE t1 SET col2 = 20 where col1 = 10;
SET DEBUG_SYNC='lock_wait_before_suspend SIGNAL t2_waiting';
--send INSERT INTO t2 values(100)
--connection default
SET DEBUG_SYNC='now WAIT_FOR t2_waiting';
--echo # T3: Wait for (T2) II row Lock on t1 after acquiring Auto Increment Lock on t1
SET DEBUG_SYNC='lock_wait_before_suspend SIGNAL t3_waiting';
--send INSERT INTO t1(col2) SELECT col2 from t1
--connection con1
SAVEPOINT s1;
SET DEBUG_SYNC='now WAIT_FOR t3_waiting';
--echo # T1: Wait for (T3) auto increment lock on t1 causing T1 -> T3 -> T2 -> T1 deadlock
SET debug_dbug = '+d,innodb_deadlock_victim_self';
--error ER_AUTOINC_READ_FAILED
INSERT INTO t1(col2) VALUES(200);
--echo # The transaction should have been rolled back
SELECT * FROM t1;
SELECT * FROM t2;
--echo # Release the previous savepoint using the same name
SAVEPOINT s1;
COMMIT;
--connection con2
--reap
COMMIT;
--connection default
--reap
COMMIT;
--disconnect con1
--disconnect con2
--echo # Cleanup
SELECT * FROM t1;
DROP TABLE t1;
SELECT * FROM t2;
DROP TABLE t2;
SET DEBUG_SYNC='RESET';
--echo #
--echo # End of 10.5 tests
--echo #