1
0
mirror of https://github.com/MariaDB/server.git synced 2025-05-11 13:21:44 +03:00
mariadb/mysql-test/suite/rpl/t/rpl_temporary_error2.test
Brandon Nesterenko 0ad52e4d6a MDEV-27512: Assertion !thd->transaction_rollback_request failed in rows_event_stmt_cleanup
If replicating an event in ROW format, and InnoDB detects a deadlock
while searching for a row, the row event will error and rollback in
InnoDB and indicate that the binlog cache also needs to be cleared,
i.e. by marking thd->transaction_rollback_request. In the normal
case, this will trigger an error in Rows_log_event::do_apply_event()
and cause a rollback. During the Rows_log_event::do_apply_event()
cleanup of a successful event application, there is a DBUG_ASSERT in
log_event_server.cc::rows_event_stmt_cleanup(), which sets the
expectation that thd->transaction_rollback_request cannot be set
because the general rollback (i.e. not the InnoDB rollback) should
have happened already. However, if the replica is configured to skip
deadlock errors, the rows event logic will clear the error and
continue on, as if no error happened. This results in
thd->transaction_rollback_request being set while in
rows_event_stmt_cleanup(), thereby triggering the assertion.

This patch fixes this in the following ways:
 1) The assertion is invalid, and thereby removed.
 2) The rollback case is forced in rows_event_stmt_cleanup() if
transaction_rollback_request is set.

Note the differing behavior between transactions which are skipped
due to deadlock errors and other errors. When a transaction is
skipped due to an ignored deadlock error, the entire transaction is
rolled back and skipped (though note MDEV-33930 which allows
statements in the same transaction after the deadlock-inducing one
to commit). When a transaction is skipped due to ignoring a
different error, only the erroring statements are rolled-back and
skipped - the rest of the transaction will execute as normal. The
effect of this can be seen in the test results. The added test case
to rpl_skip_error.test shows that only statements which are ignored
due to non-deadlock errors are ignored in larger transactions. A
diff between rpl_temporary_error2_skip_all.result and
rpl_temporary_error2.result shows that all statements in the errored
transaction are rolled back (diff pasted below):

: diff rpl_temporary_error2.result rpl_temporary_error2_skip_all.result
49c49
< 2	1
---
> 2	NULL
51c51
< 4	1
---
> 4	NULL
53c53
< * There will be two rows in t2 due to the retry.
---
> * There will be one row in t2 because the ignored deadlock does not retry.
57d56
< 1
59c58
< 1
---
> 0

Reviewed By:
============
Andrei Elkin <andrei.elkin@mariadb.com>
2024-04-17 11:14:21 -06:00

87 lines
2.7 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");
--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