mirror of
https://github.com/MariaDB/server.git
synced 2025-02-05 13:22:17 +01:00
ddd7d5d8e3
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
117 lines
4.5 KiB
Text
117 lines
4.5 KiB
Text
--source include/have_innodb.inc
|
|
--source include/have_debug_sync.inc
|
|
--source include/count_sessions.inc
|
|
|
|
--disable_query_log
|
|
call mtr.add_suppression("InnoDB: Transaction was aborted due to ");
|
|
--enable_query_log
|
|
|
|
--connect(cancel_purge,localhost,root,,)
|
|
# Purge can cause deadlock in the test, requesting page's RW_X_LATCH for trx
|
|
# ids reseting, after trx 2 acqured RW_S_LATCH and suspended in debug sync point
|
|
# lock_trx_handle_wait_enter, waiting for upd_cont signal, which must be
|
|
# emitted after the last SELECT in this test. The last SELECT will hang waiting
|
|
# for purge RW_X_LATCH releasing, and trx 2 will be rolled back by timeout.
|
|
# The last SELECT will then be successfully executed instead of finishing by
|
|
# lock wait timeout.
|
|
START TRANSACTION WITH CONSISTENT SNAPSHOT;
|
|
|
|
--connection default
|
|
CREATE TABLE t (a int PRIMARY KEY, b int) engine = InnoDB;
|
|
CREATE TABLE t2 (a int PRIMARY KEY) engine = InnoDB;
|
|
|
|
INSERT INTO t VALUES (10, 10), (20, 20), (30, 30);
|
|
INSERT INTO t2 VALUES (10), (20), (30);
|
|
|
|
BEGIN; # trx 1
|
|
SELECT * FROM t WHERE a = 20 FOR UPDATE;
|
|
# Locking order:
|
|
# (10,10) (20,20) (30,30)
|
|
# ^
|
|
# trx 1
|
|
|
|
--connect(con_2,localhost,root,,)
|
|
# RC is neccessary to do semi-consistent read
|
|
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
|
BEGIN; # trx 2
|
|
# The first time it will be hit on trying to lock (20,20), the second hit
|
|
# will be on (30,30).
|
|
SET DEBUG_SYNC = 'lock_trx_handle_wait_enter SIGNAL upd_locked WAIT_FOR upd_cont EXECUTE 2';
|
|
# We must not modify primary key fields to cause rr_sequential() read record
|
|
# function choosing in mysql_update(), i.e. both query_plan.using_filesort and
|
|
# query_plan.using_io_buffer must be false during init_read_record() call.
|
|
--send UPDATE t SET b = 100
|
|
|
|
--connect(con_3,localhost,root,,)
|
|
BEGIN; # trx 3
|
|
# 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 t2 SET a = a + 100;
|
|
SELECT * FROM t WHERE a = 30 FOR UPDATE;
|
|
SET DEBUG_SYNC='now WAIT_FOR upd_locked';
|
|
# Locking queue:
|
|
# (10,10) (20,20) (30,30)
|
|
# ^ ^ ^
|
|
# trx 2 trx 1 trx 3
|
|
# trx 2 (waiting for 1)
|
|
|
|
SET DEBUG_SYNC = 'lock_wait_start SIGNAL sel_locked';
|
|
--send SELECT * FROM t WHERE a = 20 FOR UPDATE
|
|
--connection default
|
|
SET DEBUG_SYNC='now WAIT_FOR sel_locked';
|
|
# Locking queue:
|
|
# (10,10) (20,20) (30,30)
|
|
# ^ ^ ^
|
|
# trx 2 trx 1 trx 3
|
|
# trx 2 (waiting for 1)
|
|
# trx 3 (waiting for 1)
|
|
#
|
|
# Note trx 1 must grant lock to trx2 before trx 2 checks the lock state in
|
|
# lock_trx_handle_wait(), i.e. the function must return DB_SUCCESS, that's why
|
|
# the following ROLLBACK must be executed before sending upd_cont signal.
|
|
ROLLBACK;
|
|
SET DEBUG_SYNC='now SIGNAL upd_cont';
|
|
|
|
SET DEBUG_SYNC="now WAIT_FOR upd_locked";
|
|
# Locking queue:
|
|
# (10,10) (20,20) (30,30)
|
|
# ^ ^ ^
|
|
# trx 2 trx 2 trx 3
|
|
# trx 3 (waiting for 2) trx 2 (waiting for 3)
|
|
#
|
|
# Deadlock happened after trx 1 granted lock to trx 2, and trx2 continued
|
|
# sequential read (with rr_sequential() read record function), and requested
|
|
# lock on (30,30). But the deadlock has not been determined yet.
|
|
|
|
SET SESSION innodb_lock_wait_timeout=1;
|
|
--error ER_LOCK_WAIT_TIMEOUT
|
|
# The deadlock will be determined in lock_wait() after lock wait timeout
|
|
# expired.
|
|
SELECT * FROM t WHERE a = 10 FOR UPDATE;
|
|
SET DEBUG_SYNC="now SIGNAL upd_cont";
|
|
|
|
--connection con_3
|
|
--reap
|
|
|
|
--connection con_2
|
|
# As lock_trx_handle_wait() wrongly returned DB_SUCCESS instead of
|
|
# DB_DEADLOCK, row_search_mvcc() of trx 2 behaves so as if (30,30) was locked.
|
|
# But the waiting(for trx 3) lock was cancelled by deadlock checker after
|
|
# trx 2 was choosen as a victim (see lock_cancel_waiting_and_release() call
|
|
# from Deadlock::report() for details). The try to update non-locked record
|
|
# will cause assertion if the bug is not fixed.
|
|
--error ER_LOCK_DEADLOCK
|
|
--reap
|
|
|
|
--disconnect con_3
|
|
--disconnect con_2
|
|
|
|
--connection default
|
|
SET DEBUG_SYNC = 'RESET';
|
|
DROP TABLE t;
|
|
DROP TABLE t2;
|
|
--disconnect cancel_purge
|
|
--source include/wait_until_count_sessions.inc
|