Commit b1b2689f authored by Daniele Sciascia's avatar Daniele Sciascia Committed by Jan Lindström

MDEV-25553 : Avoid unnecessary rollbacks with SR

This patch changes statement rollback for streaming replication.
Previously, a statement rollback was turned into full transaction
rollback in the case where the transaction had already replicated a
fragment. This was introduced in the initial implementation of
streaming replication due to the fact that we do not have a mechanism
to perform a statement rollback on the applying side.
This policy is however overly pessimistic, causing full rollbacks even
in cases where a local statement rollback, would not require a
statement rollback on the applying side. This happens to be case when
the statement itself has not replicated any fragments.
So the patch changes the condition that determines if a statement
rollback should be turned into a full rollback accordingly.
Reviewed-by: default avatarJan Lindström <jan.lindstrom@mariadb.com>
parent 206d630e
GCF-336 :
GCF-582 :
GCF-609 :
GCF-810A :
GCF-810B :
GCF-810C :
......
connection node_2;
connection node_1;
CREATE TABLE t1 (f1 INTEGER PRIMARY KEY) ENGINE=InnoDB;
connection node_1;
SET SESSION wsrep_trx_fragment_size=1;
SET AUTOCOMMIT=OFF;
START TRANSACTION;
INSERT INTO t1 VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10);
connection node_2;
SET SESSION wsrep_trx_fragment_size=1;
SET AUTOCOMMIT=OFF;
START TRANSACTION;
INSERT INTO t1 VALUES (11),(12),(13),(14),(15),(16),(17),(18),(19),(20);
INSERT INTO t1 VALUES (11),(12),(13),(14),(15),(16),(17),(18),(19),(20);
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
ERROR 23000: Duplicate entry '11' for key 'PRIMARY'
INSERT INTO t1 VALUES (31),(32),(33);
SELECT COUNT(*) = 0 FROM mysql.wsrep_streaming_log;
COUNT(*) = 0
0
connection node_1;
SELECT COUNT(*) = 0 FROM mysql.wsrep_streaming_log;
COUNT(*) = 0
0
COMMIT;
connection node_2;
COMMIT;
SELECT * FROM t1;
f1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
31
32
33
connection node_1;
SELECT * FROM t1;
f1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
31
32
33
DROP TABLE t1;
......@@ -17,7 +17,7 @@ SET SESSION wsrep_trx_fragment_size=1;
SET AUTOCOMMIT=OFF;
START TRANSACTION;
INSERT INTO t1 VALUES (11),(12),(13),(14),(15),(16),(17),(18),(19),(20);
--error ER_LOCK_DEADLOCK
--error ER_DUP_ENTRY
INSERT INTO t1 VALUES (11),(12),(13),(14),(15),(16),(17),(18),(19),(20);
INSERT INTO t1 VALUES (31),(32),(33);
......@@ -27,4 +27,11 @@ SELECT COUNT(*) = 0 FROM mysql.wsrep_streaming_log;
SELECT COUNT(*) = 0 FROM mysql.wsrep_streaming_log;
COMMIT;
--connection node_2
COMMIT;
SELECT * FROM t1;
--connection node_1
SELECT * FROM t1;
DROP TABLE t1;
connection node_2;
connection node_1;
CREATE TABLE t1 (f1 INTEGER PRIMARY KEY AUTO_INCREMENT, f2 CHAR(10)) ENGINE=InnoDB;
connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1;
connection node_1;
SET SESSION wsrep_trx_fragment_size = 1;
CREATE TABLE t1 (f1 INTEGER PRIMARY KEY);
CREATE TABLE t2 (f1 INTEGER PRIMARY KEY);
INSERT INTO t2 VALUES (1),(2),(3);
ALTER TABLE t2 DROP PRIMARY KEY;
INSERT INTO t2 VALUES (1);
SET SESSION wsrep_trx_fragment_size = 1;
START TRANSACTION;
INSERT INTO t1 SELECT * FROM t2;
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
SELECT * FROM t1;
f1
connection node_2;
SELECT * FROM t1;
f1
connection node_1;
DROP TABLE t1;
DROP TABLE t2;
CREATE TABLE t1 (f1 INTEGER PRIMARY KEY AUTO_INCREMENT, f2 CHAR(10)) ENGINE=InnoDB;
connection node_1;
SET SESSION wsrep_trx_fragment_size = 1;
START TRANSACTION;
INSERT INTO t1 VALUES (1, 'node1');
connection node_1a;
INSERT INTO t1 VALUES (5, 'node2');
connection node_1;
INSERT INTO t1 VALUES (5, 'node1');
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
ERROR 23000: Duplicate entry '5' for key 'PRIMARY'
SELECT * FROM t1;
f1 f2
1 node1
5 node2
SET SESSION wsrep_trx_fragment_size = 10000;
START TRANSACTION;
......@@ -24,6 +44,7 @@ INSERT INTO t1 VALUES(15, 'node2');
connection node_1;
SELECT * FROM t1;
f1 f2
1 node1
5 node2
10 node1
INSERT INTO t1 VALUES(15, 'node1');
......@@ -31,6 +52,7 @@ ERROR 23000: Duplicate entry '15' for key 'PRIMARY'
COMMIT;
SELECT * FROM t1;
f1 f2
1 node1
5 node2
10 node1
15 node2
......
......@@ -13,7 +13,7 @@ INSERT INTO t1 VALUES (REPEAT('e', 512));
INSERT INTO t1 VALUES (REPEAT('f', 512));
connection node_2;
connection node_1;
INSERT INTO t1 VALUES (REPEAT('c', 512));
INSERT INTO t1 VALUES (REPEAT('g', 1024)),(REPEAT('c', 512));
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
connection node_1;
SELECT COUNT(*) = 0 FROM mysql.wsrep_streaming_log;
......
connection node_2;
connection node_1;
CREATE TABLE t1 (f1 INTEGER PRIMARY KEY);
SET SESSION wsrep_trx_fragment_size=1;
START TRANSACTION;
INSERT INTO t1 VALUES (1);
SET SESSION wsrep_cluster_name = ' ';
ERROR HY000: Variable 'wsrep_cluster_name' is a GLOBAL variable and should be set with SET GLOBAL
INSERT INTO t1 VALUES (2);
COMMIT;
SELECT f1 AS expect_1_and_2 FROM t1;
expect_1_and_2
1
2
DROP TABLE t1;
--source include/galera_cluster.inc
--source include/have_innodb.inc
CREATE TABLE t1 (f1 INTEGER PRIMARY KEY AUTO_INCREMENT, f2 CHAR(10)) ENGINE=InnoDB;
--connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1
#
# Test 1: statement rollback is not safe
# (some fragments were already replicated)
# Test 1: statement rollback is not safe.
# Statement has already replicated some fragments
#
--connection node_1
SET SESSION wsrep_trx_fragment_size = 1;
CREATE TABLE t1 (f1 INTEGER PRIMARY KEY);
CREATE TABLE t2 (f1 INTEGER PRIMARY KEY);
INSERT INTO t2 VALUES (1),(2),(3);
ALTER TABLE t2 DROP PRIMARY KEY;
INSERT INTO t2 VALUES (1);
SET SESSION wsrep_trx_fragment_size = 1;
START TRANSACTION;
# The following INSERT .. SELECT inserts a duplicate key,
# ER_LOCK_DEADLOCK is the only possible outcome at this point.
# Notice that ER_DUP_ENTRY is NOT an option here because we were
# forced to rollback the whole transaction (not just the statement)
--error ER_LOCK_DEADLOCK
INSERT INTO t1 SELECT * FROM t2;
SELECT * FROM t1;
--connection node_2
SELECT * FROM t1;
--connection node_1
DROP TABLE t1;
DROP TABLE t2;
#
# Test 2: statement rollback is safe.
# Fragments were already replicated, not in the same statement
#
CREATE TABLE t1 (f1 INTEGER PRIMARY KEY AUTO_INCREMENT, f2 CHAR(10)) ENGINE=InnoDB;
--connection node_1
SET SESSION wsrep_trx_fragment_size = 1;
START TRANSACTION;
......@@ -19,20 +47,16 @@ INSERT INTO t1 VALUES (1, 'node1');
INSERT INTO t1 VALUES (5, 'node2');
--connection node_1
# If we try to INSERT a duplicate key, ER_LOCK_DEADLOCK is the only possible
# outcome at this point. Notice that ER_DUP_ENTRY is NOT an option here
# because we were forced to rollback the whole transaction (not just the
# statement)
--error ER_LOCK_DEADLOCK
# Only the statement is rolled back, expect ER_DUP_ENTRY
--error ER_DUP_ENTRY
INSERT INTO t1 VALUES (5, 'node1');
SELECT * FROM t1;
#
# Test 2: statement rollback is safe
# (no fragments have been replicated)
# Test 3: statement rollback is safe
# No fragments have been replicated
#
SET SESSION wsrep_trx_fragment_size = 10000;
START TRANSACTION;
......
......@@ -22,12 +22,13 @@ INSERT INTO t1 VALUES (REPEAT('f', 512));
--let $wait_condition = SELECT COUNT(*) > 0 FROM mysql.wsrep_streaming_log;
--connection node_1
# Deadlock error instead of dupkey since the transaction is SR and
# statement rollback is not safe.
# Deadlock error instead of dupkey since the transaction is SR,
# and the statement has already replicated a fragment (which
# makes statement rollback unsafe).
--error ER_LOCK_DEADLOCK
INSERT INTO t1 VALUES (REPEAT('c', 512));
INSERT INTO t1 VALUES (REPEAT('g', 1024)),(REPEAT('c', 512));
# Confirm that the wsrep_streaming_log table is now empty, as it was a full transaction rollback
# Confirm that the wsrep_schema table is now empty, as it was a full transaction rollback
--connection node_1
SELECT COUNT(*) = 0 FROM mysql.wsrep_streaming_log;
......
#
# Statement with no side effects causes unnecessary full rollback
#
--source include/galera_cluster.inc
CREATE TABLE t1 (f1 INTEGER PRIMARY KEY);
SET SESSION wsrep_trx_fragment_size=1;
START TRANSACTION;
INSERT INTO t1 VALUES (1);
# Let's cause some bogus error with a statement that
# does not cause any replication event.
# The following used to return error ER_LOCK_DEADLOCK
# and cause the entire transaction to be rolled back.
--error ER_GLOBAL_VARIABLE
SET SESSION wsrep_cluster_name = ' ';
INSERT INTO t1 VALUES (2);
COMMIT;
SELECT f1 AS expect_1_and_2 FROM t1;
DROP TABLE t1;
\ No newline at end of file
......@@ -10843,35 +10843,6 @@ void wsrep_thd_binlog_stmt_rollback(THD * thd)
DBUG_VOID_RETURN;
}
bool wsrep_stmt_rollback_is_safe(THD* thd)
{
bool ret(true);
DBUG_ENTER("wsrep_binlog_stmt_rollback_is_safe");
binlog_cache_mngr *cache_mngr=
(binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton);
if (binlog_hton && cache_mngr)
{
binlog_cache_data * trx_cache = &cache_mngr->trx_cache;
if (thd->wsrep_sr().fragments_certified() > 0 &&
(trx_cache->get_prev_position() == MY_OFF_T_UNDEF ||
trx_cache->get_prev_position() < thd->wsrep_sr().log_position()))
{
WSREP_DEBUG("statement rollback is not safe for streaming replication"
" pre-stmt_pos: %llu, frag repl pos: %zu\n"
"Thread: %llu, SQL: %s",
trx_cache->get_prev_position(),
thd->wsrep_sr().log_position(),
thd->thread_id, thd->query());
ret = false;
}
}
DBUG_RETURN(ret);
}
void wsrep_register_binlog_handler(THD *thd, bool trx)
{
DBUG_ENTER("register_binlog_handler");
......
......@@ -377,8 +377,6 @@ int wsrep_to_buf_helper(
int wsrep_create_trigger_query(THD *thd, uchar** buf, size_t* buf_len);
int wsrep_create_event_query(THD *thd, uchar** buf, size_t* buf_len);
bool wsrep_stmt_rollback_is_safe(THD* thd);
void wsrep_init_sidno(const wsrep_uuid_t&);
bool wsrep_node_is_donor();
bool wsrep_node_is_synced();
......
......@@ -345,11 +345,14 @@ static inline int wsrep_before_rollback(THD* thd, bool all)
}
if (thd->wsrep_trx().is_streaming() &&
!wsrep_stmt_rollback_is_safe(thd))
(wsrep_fragments_certified_for_stmt(thd) > 0))
{
/* Non-safe statement rollback during SR multi statement
transasction. Self abort the transaction, the actual rollback
and error handling will be done in after statement phase. */
transaction. A statement rollback is considered unsafe, if
the same statement has already replicated one or more fragments.
Self abort the transaction, the actual rollback and error
handling will be done in after statement phase. */
WSREP_DEBUG("statement rollback is not safe for streaming replication");
wsrep_thd_self_abort(thd);
ret= 0;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment