diff --git a/mysql-test/suite/galera/r/galera_sequences_bf_kill_sr.result b/mysql-test/suite/galera/r/galera_sequences_bf_kill_sr.result new file mode 100644 index 0000000000000..476c86055e068 --- /dev/null +++ b/mysql-test/suite/galera/r/galera_sequences_bf_kill_sr.result @@ -0,0 +1,216 @@ +connection node_2; +connection node_1; +connection node_1; +CREATE SEQUENCE s INCREMENT=0 CACHE=5 ENGINE=InnoDB; +CREATE TABLE t1 (f1 INT PRIMARY KEY, f2 INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1, 0), (3, 0); +connection node_1; +START TRANSACTION; +INSERT INTO t1 VALUES (4, 0); +connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1; +SET SESSION wsrep_sync_wait=0; +# The transaction above must have replicated a fragment, otherwise +# this test degenerates into galera_sequences_bf_kill +SELECT COUNT(*) > 0 AS EXPECT_1 FROM mysql.wsrep_streaming_log; +EXPECT_1 +1 +# Block the applier on node #1 and issue a conflicting insert on node #2 +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +INSERT INTO t1 VALUES (2, next value for s); +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +# Block the commit monitor and let the local transaction reserve a +# new range of sequence values, which conflicts with the applier +SET GLOBAL wsrep_provider_options = 'dbug=d,commit_monitor_master_enter_sync'; +connection node_1; +INSERT INTO t1 VALUES (5, next value for s); +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +# Release the applier +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +# Release the local transaction +SET GLOBAL wsrep_provider_options = 'signal=commit_monitor_master_enter_sync'; +connection node_1; +COMMIT; +# Both nodes must have the same table contents. The sequence values +# are node specific, each node adjusts them to its own +# auto_increment_offset +connection node_1; +SELECT * FROM t1; +f1 f2 +1 0 +2 2 +3 0 +4 0 +5 1 +SELECT LASTVAL(s); +LASTVAL(s) +1 +SELECT next_not_cached_value FROM s; +next_not_cached_value +11 +connection node_2; +SELECT * FROM t1; +f1 f2 +1 0 +2 2 +3 0 +4 0 +5 1 +SELECT LASTVAL(s); +LASTVAL(s) +2 +SELECT next_not_cached_value FROM s; +next_not_cached_value +12 +# All fragments must have been removed on commit +connection node_1a; +SELECT COUNT(*) AS EXPECT_0 FROM mysql.wsrep_streaming_log; +EXPECT_0 +0 +connection node_1; +DROP SEQUENCE s; +DROP TABLE t1; +connection node_1; +CREATE SEQUENCE s INCREMENT=0 CACHE=5 ENGINE=InnoDB; +CREATE TABLE t1 (f1 INT PRIMARY KEY, f2 INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1, 0), (3, 0); +connection node_1; +START TRANSACTION; +INSERT INTO t1 VALUES (4, 0); +SELECT * FROM t1 WHERE f1 > 0 FOR UPDATE; +f1 f2 +1 0 +3 0 +4 0 +# Block the applier on node #1 and issue a conflicting insert on node #2 +connection node_1a; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +INSERT INTO t1 VALUES (2, next value for s); +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +# Block the commit monitor and let the local transaction reserve a +# new range of sequence values, which conflicts with the applier +SET GLOBAL wsrep_provider_options = 'dbug=d,commit_monitor_master_enter_sync'; +connection node_1; +INSERT INTO t1 VALUES (5, next value for s); +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +SET GLOBAL wsrep_provider_options = 'dbug=d,abort_trx_end'; +# Release the applier +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +# Wait until the applier has BF aborted the local transaction +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +# Release the applier and the local transaction +SET GLOBAL wsrep_provider_options = 'signal=abort_trx_end'; +SET GLOBAL wsrep_provider_options = 'signal=commit_monitor_master_enter_sync'; +# The streaming transaction is BF aborted and rolled back +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +ROLLBACK; +# The rollback must have removed the already replicated fragments +# from both nodes, so neither node has row 4 +connection node_1; +SELECT * FROM t1; +f1 f2 +1 0 +2 2 +3 0 +connection node_2; +SELECT * FROM t1; +f1 f2 +1 0 +2 2 +3 0 +connection node_1a; +SELECT COUNT(*) AS EXPECT_0 FROM mysql.wsrep_streaming_log; +EXPECT_0 +0 +connection node_1; +DROP SEQUENCE s; +DROP TABLE t1; +connection node_1; +CREATE SEQUENCE s INCREMENT=0 CACHE=5 ENGINE=InnoDB; +CREATE TABLE t1 (f1 INT PRIMARY KEY, f2 BIGINT) ENGINE=InnoDB; +connection node_1; +SET SESSION wsrep_trx_fragment_unit='rows'; +SET SESSION wsrep_trx_fragment_size=3; +START TRANSACTION; +INSERT INTO t1 VALUES (100, NULL); +# Block the applier on node #1 and let node #2 reserve several +# ranges of its own +connection node_1a; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +INSERT INTO t1 VALUES (201,NEXT VALUE FOR s),(202,NEXT VALUE FOR s),(203,NEXT VALUE FOR s),(204,NEXT VALUE FOR s),(205,NEXT VALUE FOR s),(206,NEXT VALUE FOR s),(207,NEXT VALUE FOR s),(208,NEXT VALUE FOR s),(209,NEXT VALUE FOR s),(210,NEXT VALUE FOR s),(211,NEXT VALUE FOR s),(212,NEXT VALUE FOR s); +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +# Block the commit monitor and let the local transaction reserve +# several ranges of its own, spread over several fragments +SET GLOBAL wsrep_provider_options = 'dbug=d,commit_monitor_master_enter_sync'; +connection node_1; +INSERT INTO t1 VALUES (101,NEXT VALUE FOR s),(102,NEXT VALUE FOR s),(103,NEXT VALUE FOR s),(104,NEXT VALUE FOR s),(105,NEXT VALUE FOR s),(106,NEXT VALUE FOR s),(107,NEXT VALUE FOR s),(108,NEXT VALUE FOR s),(109,NEXT VALUE FOR s),(110,NEXT VALUE FOR s),(111,NEXT VALUE FOR s),(112,NEXT VALUE FOR s); +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +# Release the applier +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +# Release the local transaction +SET GLOBAL wsrep_provider_options = 'signal=commit_monitor_master_enter_sync'; +connection node_1; +COMMIT; +# Both nodes reserved more than one cache range +connection node_1; +SELECT next_not_cached_value > 21 AS EXPECT_1 FROM s; +EXPECT_1 +1 +# No value was handed out twice, and the two nodes used disjoint +# value spaces +SELECT COUNT(*) AS EXPECT_25 FROM t1; +EXPECT_25 +25 +SELECT COUNT(f2) = COUNT(DISTINCT f2) AS EXPECT_1 FROM t1; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_0 FROM t1 WHERE f1 < 200 AND MOD(f2, 2) != 1; +EXPECT_0 +0 +SELECT COUNT(*) AS EXPECT_0 FROM t1 WHERE f1 > 200 AND MOD(f2, 2) != 0; +EXPECT_0 +0 +connection node_2; +SELECT COUNT(*) AS EXPECT_25 FROM t1; +EXPECT_25 +25 +SELECT COUNT(f2) = COUNT(DISTINCT f2) AS EXPECT_1 FROM t1; +EXPECT_1 +1 +# All fragments must have been removed on commit +connection node_1a; +SELECT COUNT(*) AS EXPECT_0 FROM mysql.wsrep_streaming_log; +EXPECT_0 +0 +connection node_1; +SET SESSION wsrep_trx_fragment_size=DEFAULT; +SET SESSION wsrep_trx_fragment_unit=DEFAULT; +DROP SEQUENCE s; +DROP TABLE t1; +connection node_1; +wsrep_local_replays +1 +disconnect node_1a; diff --git a/mysql-test/suite/galera/t/galera_sequences_bf_kill_sr.cnf b/mysql-test/suite/galera/t/galera_sequences_bf_kill_sr.cnf new file mode 100644 index 0000000000000..fd5f4edab8d66 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sequences_bf_kill_sr.cnf @@ -0,0 +1,12 @@ +!include ../galera_2nodes.cnf + +[mysqld.1] +wsrep-auto-increment-control=OFF +auto-increment-increment=2 +auto-increment-offset=1 +wsrep_trx_fragment_size=1 + +[mysqld.2] +wsrep-auto-increment-control=OFF +auto-increment-increment=2 +auto-increment-offset=2 diff --git a/mysql-test/suite/galera/t/galera_sequences_bf_kill_sr.combinations b/mysql-test/suite/galera/t/galera_sequences_bf_kill_sr.combinations new file mode 100644 index 0000000000000..cef98e75213f7 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sequences_bf_kill_sr.combinations @@ -0,0 +1,5 @@ +[binlogon] +log-bin +log-slave-updates + +[binlogoff] diff --git a/mysql-test/suite/galera/t/galera_sequences_bf_kill_sr.test b/mysql-test/suite/galera/t/galera_sequences_bf_kill_sr.test new file mode 100644 index 0000000000000..692df933a9bf0 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sequences_bf_kill_sr.test @@ -0,0 +1,308 @@ +# +# Test a conflict on an InnoDB sequence between a streaming replication +# transaction and an applier. +# +# The local transaction reserves a new range of sequence values, which +# writes the sequence table while holding SEQUENCE::mutex. At the same +# time an applier applies a sequence update originating from the other +# node, and needs the same mutex in SEQUENCE::set_value(). +# +# If the sequence table write replicates a streaming fragment while the +# mutex is held, that fragment waits for commit order behind the applier's +# write set, while the applier waits for the mutex. The node deadlocks and +# the BF abort of the local transaction can never be delivered. +# +# This is specific to streaming replication. Without it the write set is +# replicated at COMMIT, after SEQUENCE::mutex has been released. +# + +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/have_sequence.inc +--source include/have_debug.inc +--source include/have_debug_sync.inc +--source include/galera_have_debug_sync.inc + +--disable_ps2_protocol + +# +# We create an InnoDB sequence with a small cache that is then used as +# value for a column in the table. The cache is exhausted immediately, so +# that the first NEXTVAL on each node has to write the sequence table. +# +--connection node_1 +--let $wsrep_local_replays_old = `SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_local_replays'` +CREATE SEQUENCE s INCREMENT=0 CACHE=5 ENGINE=InnoDB; +CREATE TABLE t1 (f1 INT PRIMARY KEY, f2 INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1, 0), (3, 0); + +--connection node_1 +START TRANSACTION; +INSERT INTO t1 VALUES (4, 0); # No conflict in cert + +--connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1 +SET SESSION wsrep_sync_wait=0; + +--echo # The transaction above must have replicated a fragment, otherwise +--echo # this test degenerates into galera_sequences_bf_kill +SELECT COUNT(*) > 0 AS EXPECT_1 FROM mysql.wsrep_streaming_log; + +--echo # Block the applier on node #1 and issue a conflicting insert on node #2 +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_set_sync_point.inc + +# +# Send conflicting INSERT. This updates the sequence table on node #2, +# and the applier on node #1 will have to apply that update. +# +--connection node_2 +INSERT INTO t1 VALUES (2, next value for s); + +--connection node_1a +--source include/galera_wait_sync_point.inc +--source include/galera_clear_sync_point.inc + +--echo # Block the commit monitor and let the local transaction reserve a +--echo # new range of sequence values, which conflicts with the applier +--let $galera_sync_point = commit_monitor_master_enter_sync +--source include/galera_set_sync_point.inc + +--connection node_1 +--send INSERT INTO t1 VALUES (5, next value for s) + +--connection node_1a +--let $galera_sync_point = apply_monitor_slave_enter_sync commit_monitor_master_enter_sync +--source include/galera_wait_sync_point.inc +--source include/galera_clear_sync_point.inc + +--echo # Release the applier +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_signal_sync_point.inc + +--echo # Release the local transaction +--let $galera_sync_point = commit_monitor_master_enter_sync +--source include/galera_signal_sync_point.inc + +--connection node_1 +--reap +COMMIT; + +--echo # Both nodes must have the same table contents. The sequence values +--echo # are node specific, each node adjusts them to its own +--echo # auto_increment_offset +--connection node_1 +SELECT * FROM t1; +SELECT LASTVAL(s); +SELECT next_not_cached_value FROM s; + +--connection node_2 +SELECT * FROM t1; +SELECT LASTVAL(s); +SELECT next_not_cached_value FROM s; + +--echo # All fragments must have been removed on commit +--connection node_1a +--let $wait_condition = SELECT COUNT(*) = 0 FROM mysql.wsrep_streaming_log +--source include/wait_condition.inc +SELECT COUNT(*) AS EXPECT_0 FROM mysql.wsrep_streaming_log; + +--connection node_1 +DROP SEQUENCE s; +DROP TABLE t1; + +# +# Case 2: the same sequence conflict, but this time the applier also has +# to BF abort the local transaction over a gap lock. A streaming +# transaction cannot be replayed, so it is rolled back and the client +# gets ER_LOCK_DEADLOCK. +# +--connection node_1 +CREATE SEQUENCE s INCREMENT=0 CACHE=5 ENGINE=InnoDB; +CREATE TABLE t1 (f1 INT PRIMARY KEY, f2 INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1, 0), (3, 0); + +--connection node_1 +START TRANSACTION; +INSERT INTO t1 VALUES (4, 0); +SELECT * FROM t1 WHERE f1 > 0 FOR UPDATE; # Should cause GAP lock between 1 and 3 + +--echo # Block the applier on node #1 and issue a conflicting insert on node #2 +--connection node_1a +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_set_sync_point.inc + +--connection node_2 +INSERT INTO t1 VALUES (2, next value for s); # BF aborts on the GAP lock + +--connection node_1a +--source include/galera_wait_sync_point.inc +--source include/galera_clear_sync_point.inc + +--echo # Block the commit monitor and let the local transaction reserve a +--echo # new range of sequence values, which conflicts with the applier +--let $galera_sync_point = commit_monitor_master_enter_sync +--source include/galera_set_sync_point.inc + +--connection node_1 +--send INSERT INTO t1 VALUES (5, next value for s) + +--connection node_1a +--let $galera_sync_point = apply_monitor_slave_enter_sync commit_monitor_master_enter_sync +--source include/galera_wait_sync_point.inc +--source include/galera_clear_sync_point.inc + +# +# Arm a sync point in the BF abort path before releasing the applier. +# Otherwise the local transaction could be released from the commit +# monitor and finish its fragment before the applier has reached the gap +# lock conflict, and no BF abort would be delivered at all. The sibling +# test galera_sequences_bf_kill.test uses the same handshake. +# +--let $galera_sync_point = abort_trx_end +--source include/galera_set_sync_point.inc + +--echo # Release the applier +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_signal_sync_point.inc + +--echo # Wait until the applier has BF aborted the local transaction +--let $galera_sync_point = abort_trx_end commit_monitor_master_enter_sync +--source include/galera_wait_sync_point.inc +--source include/galera_clear_sync_point.inc + +--echo # Release the applier and the local transaction +--let $galera_sync_point = abort_trx_end +--source include/galera_signal_sync_point.inc +--let $galera_sync_point = commit_monitor_master_enter_sync +--source include/galera_signal_sync_point.inc + +--echo # The streaming transaction is BF aborted and rolled back +--connection node_1 +--error ER_LOCK_DEADLOCK +--reap +ROLLBACK; + +--echo # The rollback must have removed the already replicated fragments +--echo # from both nodes, so neither node has row 4 +--connection node_1 +SELECT * FROM t1; + +--connection node_2 +SELECT * FROM t1; + +--connection node_1a +--let $wait_condition = SELECT COUNT(*) = 0 FROM mysql.wsrep_streaming_log +--source include/wait_condition.inc +SELECT COUNT(*) AS EXPECT_0 FROM mysql.wsrep_streaming_log; + +--connection node_1 +DROP SEQUENCE s; +DROP TABLE t1; + +# +# Case 3: several rows per fragment and several cache range reservations +# inside one streaming transaction, on both nodes at the same time. +# +# The cases above stream one row per fragment and reserve a single cache +# range on each side. Here both nodes insert twelve rows, so with CACHE=5 +# each of them exhausts its cache three times and writes the sequence +# table three times, every time under SEQUENCE::mutex. On node #1 the +# fragment unit is rows and the fragment size is three, so those writes +# land inside fragments that carry several rows. +# +# The choreography is the one from case 1: the applier on node #1 is held +# back until the local transaction has reserved its ranges and is parked +# in the commit monitor, which is the state the deadlock needed. Both +# transactions must commit, and the values the two nodes handed out must +# be disjoint - each node reserves whole cache ranges and picks values +# from them by its own auto_increment_offset, node #1 the odd ones and +# node #2 the even ones. +# +--connection node_1 +CREATE SEQUENCE s INCREMENT=0 CACHE=5 ENGINE=InnoDB; +CREATE TABLE t1 (f1 INT PRIMARY KEY, f2 BIGINT) ENGINE=InnoDB; + +--connection node_1 +SET SESSION wsrep_trx_fragment_unit='rows'; +SET SESSION wsrep_trx_fragment_size=3; +START TRANSACTION; +INSERT INTO t1 VALUES (100, NULL); # No conflict in cert + +--echo # Block the applier on node #1 and let node #2 reserve several +--echo # ranges of its own +--connection node_1a +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_set_sync_point.inc + +--connection node_2 +INSERT INTO t1 VALUES (201,NEXT VALUE FOR s),(202,NEXT VALUE FOR s),(203,NEXT VALUE FOR s),(204,NEXT VALUE FOR s),(205,NEXT VALUE FOR s),(206,NEXT VALUE FOR s),(207,NEXT VALUE FOR s),(208,NEXT VALUE FOR s),(209,NEXT VALUE FOR s),(210,NEXT VALUE FOR s),(211,NEXT VALUE FOR s),(212,NEXT VALUE FOR s); + +--connection node_1a +--source include/galera_wait_sync_point.inc +--source include/galera_clear_sync_point.inc + +--echo # Block the commit monitor and let the local transaction reserve +--echo # several ranges of its own, spread over several fragments +--let $galera_sync_point = commit_monitor_master_enter_sync +--source include/galera_set_sync_point.inc + +--connection node_1 +--send INSERT INTO t1 VALUES (101,NEXT VALUE FOR s),(102,NEXT VALUE FOR s),(103,NEXT VALUE FOR s),(104,NEXT VALUE FOR s),(105,NEXT VALUE FOR s),(106,NEXT VALUE FOR s),(107,NEXT VALUE FOR s),(108,NEXT VALUE FOR s),(109,NEXT VALUE FOR s),(110,NEXT VALUE FOR s),(111,NEXT VALUE FOR s),(112,NEXT VALUE FOR s) + +--connection node_1a +--let $galera_sync_point = apply_monitor_slave_enter_sync commit_monitor_master_enter_sync +--source include/galera_wait_sync_point.inc +--source include/galera_clear_sync_point.inc + +--echo # Release the applier +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_signal_sync_point.inc + +--echo # Release the local transaction +--let $galera_sync_point = commit_monitor_master_enter_sync +--source include/galera_signal_sync_point.inc + +--connection node_1 +--reap +COMMIT; + +--echo # Both nodes reserved more than one cache range +--connection node_1 +--let $wait_condition = SELECT COUNT(*) = 25 FROM t1 +--source include/wait_condition.inc +SELECT next_not_cached_value > 21 AS EXPECT_1 FROM s; + +--echo # No value was handed out twice, and the two nodes used disjoint +--echo # value spaces +SELECT COUNT(*) AS EXPECT_25 FROM t1; +SELECT COUNT(f2) = COUNT(DISTINCT f2) AS EXPECT_1 FROM t1; +SELECT COUNT(*) AS EXPECT_0 FROM t1 WHERE f1 < 200 AND MOD(f2, 2) != 1; +SELECT COUNT(*) AS EXPECT_0 FROM t1 WHERE f1 > 200 AND MOD(f2, 2) != 0; + +--connection node_2 +--let $wait_condition = SELECT COUNT(*) = 25 FROM t1 +--source include/wait_condition.inc +SELECT COUNT(*) AS EXPECT_25 FROM t1; +SELECT COUNT(f2) = COUNT(DISTINCT f2) AS EXPECT_1 FROM t1; + +--echo # All fragments must have been removed on commit +--connection node_1a +--let $wait_condition = SELECT COUNT(*) = 0 FROM mysql.wsrep_streaming_log +--source include/wait_condition.inc +SELECT COUNT(*) AS EXPECT_0 FROM mysql.wsrep_streaming_log; + +--connection node_1 +SET SESSION wsrep_trx_fragment_size=DEFAULT; +SET SESSION wsrep_trx_fragment_unit=DEFAULT; +DROP SEQUENCE s; +DROP TABLE t1; + +# Streaming transactions are rolled back rather than replayed, so no +# replay happened in any of the cases above +--connection node_1 +--let $wsrep_local_replays_new = `SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_local_replays'` +--disable_query_log +--eval SELECT $wsrep_local_replays_new - $wsrep_local_replays_old = 0 AS wsrep_local_replays; +--enable_query_log + +--disconnect node_1a diff --git a/sql/handler.cc b/sql/handler.cc index 27b3e66823857..d7485096f5c3f 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -7440,7 +7440,11 @@ int handler::ha_reset() DBUG_RETURN(reset()); } -static int wsrep_after_row(THD *thd) +/* + If skip_streaming is true, the row is counted against wsrep_max_ws_rows + and validated as usual, but no streaming fragment is replicated for it. +*/ +static int wsrep_after_row(THD *thd, bool skip_streaming= false) { DBUG_ENTER("wsrep_after_row"); #ifdef WITH_WSREP @@ -7463,7 +7467,7 @@ static int wsrep_after_row(THD *thd) my_message(ER_ERROR_DURING_COMMIT, "wsrep_max_ws_rows exceeded", MYF(0)); DBUG_RETURN(ER_ERROR_DURING_COMMIT); } - else if (wsrep_after_row_internal(thd)) + else if (wsrep_after_row_internal(thd, skip_streaming)) { DBUG_RETURN(ER_LOCK_DEADLOCK); } @@ -7890,9 +7894,20 @@ int handler::ha_write_row(const uchar *buf) error= binlog_log_row(table, 0, buf, log_func); } + /* + Sequence tables are written with SEQUENCE::mutex held (see + SEQUENCE::next_value() and ha_sequence::write_row()). For a streaming + transaction the streaming step would replicate a fragment and block + waiting for certification and commit order, while an applier may be + waiting for the same mutex in SEQUENCE::set_value(). That deadlocks + the node, so skip only that step here. The row has already been + appended to the write set and will be replicated with the following + fragment, or at commit. Everything else wsrep_after_row() does, in + particular counting the row against wsrep_max_ws_rows, still applies. + */ if (WSREP_NNULL(ha_thd()) && table_share->tmp_table == NO_TMP_TABLE && ht->flags & HTON_WSREP_REPLICATION && !error) - error= wsrep_after_row(ha_thd()); + error= wsrep_after_row(ha_thd(), table_share->sequence != NULL); err: DEBUG_SYNC_C("ha_write_row_end"); diff --git a/sql/wsrep_trans_observer.h b/sql/wsrep_trans_observer.h index 2cc89044cc602..20409f3ecc303 100644 --- a/sql/wsrep_trans_observer.h +++ b/sql/wsrep_trans_observer.h @@ -165,9 +165,13 @@ static inline int wsrep_start_trx_if_not_started(THD* thd) /* Called after each row operation. + If skip_streaming is true, the streaming replication step is not run for + this row. The row is still validated here and accounted for by the caller; + it will be replicated with the following fragment, or at commit. + Return zero on succes, non-zero on failure. */ -static inline int wsrep_after_row_internal(THD* thd) +static inline int wsrep_after_row_internal(THD* thd, bool skip_streaming= false) { if (thd->wsrep_cs().state() != wsrep::client_state::s_none && wsrep_thd_is_local(thd)) @@ -176,7 +180,7 @@ static inline int wsrep_after_row_internal(THD* thd) { return 1; } - else if (wsrep_streaming_enabled(thd)) + else if (!skip_streaming && wsrep_streaming_enabled(thd)) { return thd->wsrep_cs().after_row(); }