mirror of
https://github.com/MariaDB/server.git
synced 2025-08-07 00:04:31 +03:00
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
90 lines
2.8 KiB
Plaintext
90 lines
2.8 KiB
Plaintext
--source include/have_innodb.inc
|
|
--source include/master-slave.inc
|
|
|
|
call mtr.add_suppression("Deadlock found when trying to get lock; try restarting transaction");
|
|
--disable_query_log
|
|
call mtr.add_suppression("InnoDB: Transaction was aborted due to ");
|
|
--enable_query_log
|
|
|
|
--echo *** Provoke a deadlock on the slave, check that transaction retry succeeds. ***
|
|
--connection master
|
|
CREATE TABLE t1 (a INT PRIMARY KEY, b INT) ENGINE=InnoDB;
|
|
CREATE TABLE t2 (a INT) ENGINE=InnoDB;
|
|
INSERT INTO t1(a) VALUES (1), (2), (3), (4), (5);
|
|
|
|
--sync_slave_with_master
|
|
SELECT * FROM t1 ORDER BY a;
|
|
# Use MyISAM for t2 on the slave, so we have a way to see how far the
|
|
# slave replication thread has proceeded in the transaction.
|
|
SET sql_log_bin=0;
|
|
ALTER TABLE t2 ENGINE=MyISAM;
|
|
SET sql_log_bin=1;
|
|
let $old_retry= query_get_value(SHOW STATUS LIKE 'Slave_retried_transactions', Value, 1);
|
|
|
|
# Setup a separate connection that can deadlock with the replication thread.
|
|
# Docs say that InnoDB will try to roll back the smaller transaction. So
|
|
# let us make this transaction a big one, so the one in the replication
|
|
# thread will be selected for rollback and retry.
|
|
--connect (con_temp1,127.0.0.1,root,,test,$SERVER_MYPORT_2,)
|
|
--connection con_temp1
|
|
BEGIN;
|
|
UPDATE t1 SET b=2 WHERE a=4;
|
|
--disable_query_log
|
|
--let $count=200
|
|
while ($count)
|
|
{
|
|
eval INSERT INTO t1(a) VALUES ($count + 10);
|
|
dec $count;
|
|
}
|
|
--enable_query_log
|
|
# Note that InnoDB also (undocumented?) tries to avoid rolling back a
|
|
# "transaction" that modified non-transactional tables. So be sure to also
|
|
# touch the MyISAM table in this transaction.
|
|
INSERT INTO t2 VALUES (2);
|
|
DELETE FROM t2 WHERE a=2;
|
|
|
|
# Create the transaction that should participate in the deadlock on the slave.
|
|
--connection master
|
|
BEGIN;
|
|
UPDATE t1 SET b=1 WHERE a=2;
|
|
INSERT INTO t2 VALUES (1);
|
|
UPDATE t1 SET b=1 WHERE a=4;
|
|
COMMIT;
|
|
--save_master_pos
|
|
|
|
--connection slave
|
|
# Wait until replication thread has gone to wait on the a=4 row lock.
|
|
--let $wait_condition= SELECT COUNT(*) = 1 FROM t2 WHERE a=1
|
|
--source include/wait_condition.inc
|
|
|
|
# Now provoke the deadlock by waiting on the a=2 row lock while the
|
|
# other thread is waiting for our a=4 row lock.
|
|
--connection con_temp1
|
|
UPDATE t1 SET b=2 WHERE a=2;
|
|
SELECT * FROM t1 WHERE a<10 ORDER BY a;
|
|
ROLLBACK;
|
|
|
|
--connection slave
|
|
--sync_with_master
|
|
SELECT * FROM t1 ORDER BY a;
|
|
if (!$ignored_db_deadlock)
|
|
{
|
|
--echo * There will be two rows in t2 due to the retry.
|
|
}
|
|
if ($ignored_db_deadlock)
|
|
{
|
|
--echo * There will be one row in t2 because the ignored deadlock does not retry.
|
|
}
|
|
SELECT * FROM t2 ORDER BY a;
|
|
let $new_retry= query_get_value(SHOW STATUS LIKE 'Slave_retried_transactions', Value, 1);
|
|
--disable_query_log
|
|
eval SELECT $new_retry - $old_retry AS retries;
|
|
--enable_query_log
|
|
--let $status_items= Last_SQL_Errno, Last_SQL_Error
|
|
--source include/show_slave_status.inc
|
|
|
|
--connection master
|
|
DROP TABLE t1;
|
|
DROP TABLE t2;
|
|
--source include/rpl_end.inc
|