From f4f2e8fa2a540a6e2edd75c9c6dd97ed4185163d Mon Sep 17 00:00:00 2001 From: sjaakola Date: Thu, 24 Aug 2017 10:34:21 +0300 Subject: [PATCH] MW-402 cascading FK issues * created tests focusing in multi-master conflicts during cascading foreign key processing * in row0upd.cc, calling wsrep_row_ups_check_foreign_constraints only when running in cluster * in row0ins.cc fixed regression from MW-369, which caused crash with MW-402.test --- mysql-test/suite/galera/r/MW-388.result | 14 +- mysql-test/suite/galera/r/MW-402.result | 152 ++++++++++++++++++++ mysql-test/suite/galera/t/MW-388.test | 24 +++- mysql-test/suite/galera/t/MW-402.test | 184 ++++++++++++++++++++++++ sql/wsrep_hton.cc | 3 + storage/innobase/row/row0ins.cc | 3 +- storage/innobase/row/row0upd.cc | 2 +- storage/xtradb/row/row0ins.cc | 3 +- storage/xtradb/row/row0upd.cc | 2 +- 9 files changed, 370 insertions(+), 17 deletions(-) create mode 100644 mysql-test/suite/galera/r/MW-402.result create mode 100644 mysql-test/suite/galera/t/MW-402.test diff --git a/mysql-test/suite/galera/r/MW-388.result b/mysql-test/suite/galera/r/MW-388.result index f81f1e1a9fb..117b0fe88d0 100644 --- a/mysql-test/suite/galera/r/MW-388.result +++ b/mysql-test/suite/galera/r/MW-388.result @@ -8,19 +8,20 @@ END; INSERT INTO t1 VALUES (1, 'node 1'),(2, 'node 1'); INSERT INTO t1 VALUES (3, 'node 1'); END| +SET GLOBAL wsrep_slave_threads = 2; SET GLOBAL DEBUG = "d,sync.wsrep_apply_cb"; INSERT INTO t1 VALUES (1, 'node 2');; SET SESSION DEBUG_SYNC = "now WAIT_FOR sync.wsrep_apply_cb_reached"; SET SESSION wsrep_sync_wait = 0; -SET SESSION DEBUG_SYNC = 'wsrep_before_replication SIGNAL wsrep_before_replication_reached WAIT_FOR wsrep_before_replication_continue'; +SET SESSION DEBUG_SYNC = 'wsrep_after_replication SIGNAL wsrep_after_replication_reached WAIT_FOR wsrep_after_replication_continue'; CALL insert_proc ();; -SET SESSION DEBUG_SYNC = "now WAIT_FOR wsrep_before_replication_reached"; +SET SESSION DEBUG_SYNC = "now WAIT_FOR wsrep_after_replication_reached"; SET GLOBAL DEBUG = ""; -SET DEBUG_SYNC = "now SIGNAL wsrep_before_replication_continue"; +SET DEBUG_SYNC = "now SIGNAL wsrep_after_replication_continue"; SET DEBUG_SYNC = "now SIGNAL signal.wsrep_apply_cb"; -SELECT @errno; -@errno -1213 +SELECT @errno = 1213; +@errno = 1213 +1 SELECT * FROM t1; f1 f2 1 node 2 @@ -29,5 +30,6 @@ SELECT * FROM t1; f1 f2 1 node 2 3 node 1 +SET GLOBAL wsrep_slave_threads = DEFAULT; DROP TABLE t1; DROP PROCEDURE insert_proc; diff --git a/mysql-test/suite/galera/r/MW-402.result b/mysql-test/suite/galera/r/MW-402.result new file mode 100644 index 00000000000..4261bc6bac3 --- /dev/null +++ b/mysql-test/suite/galera/r/MW-402.result @@ -0,0 +1,152 @@ +CREATE TABLE p (f1 INTEGER PRIMARY KEY, f2 INTEGER) ENGINE=INNODB; +CREATE TABLE c (f1 INTEGER PRIMARY KEY, p_id INTEGER, f2 INTEGER, +CONSTRAINT fk_1 FOREIGN KEY (p_id) REFERENCES p (f1) ON DELETE CASCADE); +INSERT INTO p VALUES (1, 0); +INSERT INTO p VALUES (2, 0); +INSERT INTO c VALUES (1, 1, 0); +SET AUTOCOMMIT=ON; +START TRANSACTION; +UPDATE c SET f2=1 where f1=1; +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +DELETE FROM p WHERE f1 = 1; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +SET GLOBAL wsrep_provider_options = 'dbug=d,local_monitor_enter_sync'; +COMMIT; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +SET GLOBAL wsrep_provider_options = 'signal=local_monitor_enter_sync'; +SET GLOBAL wsrep_provider_options = 'dbug='; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT * FROM p; +f1 f2 +2 0 +SELECT * FROM c; +f1 p_id f2 +DROP TABLE c; +DROP TABLE p; +CREATE TABLE p (f1 INTEGER PRIMARY KEY, f2 INTEGER) ENGINE=INNODB; +CREATE TABLE c (f1 INTEGER PRIMARY KEY, p_id INTEGER, f2 INTEGER, +CONSTRAINT fk_1 FOREIGN KEY (p_id) REFERENCES p (f1) ON UPDATE CASCADE); +INSERT INTO p VALUES (1, 0); +INSERT INTO p VALUES (2, 0); +INSERT INTO c VALUES (1, 1, 0); +SET AUTOCOMMIT=ON; +START TRANSACTION; +UPDATE c SET f2=2 where f1=1; +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +UPDATE p set f1=11 WHERE f1 = 1; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +SET GLOBAL wsrep_provider_options = 'dbug=d,local_monitor_enter_sync'; +COMMIT; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +SET GLOBAL wsrep_provider_options = 'signal=local_monitor_enter_sync'; +SET GLOBAL wsrep_provider_options = 'dbug='; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT * FROM p; +f1 f2 +2 0 +11 0 +SELECT * FROM c; +f1 p_id f2 +1 11 0 +DROP TABLE c; +DROP TABLE p; +CREATE TABLE p (f1 INTEGER PRIMARY KEY, f2 INTEGER) ENGINE=INNODB; +CREATE TABLE c (f1 INTEGER PRIMARY KEY, p_id INTEGER, f2 INTEGER, +CONSTRAINT fk_1 FOREIGN KEY (p_id) REFERENCES p (f1) ON UPDATE CASCADE); +INSERT INTO p VALUES (1, 0); +INSERT INTO p VALUES (2, 0); +INSERT INTO c VALUES (1, 1, 0); +SET AUTOCOMMIT=ON; +START TRANSACTION; +UPDATE c SET p_id=2 where f1=1; +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +UPDATE p set f1=11 WHERE f1 = 1; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +SET GLOBAL wsrep_provider_options = 'dbug=d,local_monitor_enter_sync'; +COMMIT; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +SET GLOBAL wsrep_provider_options = 'signal=local_monitor_enter_sync'; +SET GLOBAL wsrep_provider_options = 'dbug='; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT * FROM p; +f1 f2 +2 0 +11 0 +SELECT * FROM c; +f1 p_id f2 +1 11 0 +SET AUTOCOMMIT=ON; +START TRANSACTION; +UPDATE p set f1=21 WHERE f1 = 11; +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +UPDATE c SET p_id=2 where f1=1; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +SET GLOBAL wsrep_provider_options = 'dbug=d,local_monitor_enter_sync'; +COMMIT; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +SET GLOBAL wsrep_provider_options = 'signal=local_monitor_enter_sync'; +SET GLOBAL wsrep_provider_options = 'dbug='; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT * FROM p; +f1 f2 +2 0 +11 0 +SELECT * FROM c; +f1 p_id f2 +1 2 0 +DROP TABLE c; +DROP TABLE p; +CREATE TABLE p1 (f1 INTEGER PRIMARY KEY, f2 INTEGER) ENGINE=INNODB; +CREATE TABLE p2 (f1 INTEGER PRIMARY KEY, f2 INTEGER) ENGINE=INNODB; +CREATE TABLE c (f1 INTEGER PRIMARY KEY, p1_id INTEGER, p2_id INTEGER, f2 INTEGER, +CONSTRAINT fk_1 FOREIGN KEY (p1_id) REFERENCES p1 (f1) ON DELETE CASCADE, +CONSTRAINT fk_2 FOREIGN KEY (p2_id) REFERENCES p2 (f1)); +INSERT INTO p1 VALUES (1, 0); +INSERT INTO p2 VALUES (1, 0); +INSERT INTO c VALUES (1, 1, 1, 0); +SET AUTOCOMMIT=ON; +START TRANSACTION; +UPDATE p2 SET f2=2 where f1=1; +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +DELETE FROM p1 WHERE f1 = 1; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +SET GLOBAL wsrep_provider_options = 'dbug=d,local_monitor_enter_sync'; +COMMIT; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +SET GLOBAL wsrep_provider_options = 'signal=local_monitor_enter_sync'; +SET GLOBAL wsrep_provider_options = 'dbug='; +SELECT * FROM p1; +f1 f2 +SELECT * FROM p2; +f1 f2 +1 2 +SELECT * FROM c; +f1 p1_id p2_id f2 +DROP TABLE c; +DROP TABLE p1; +DROP TABLE p2; diff --git a/mysql-test/suite/galera/t/MW-388.test b/mysql-test/suite/galera/t/MW-388.test index 59b28dba236..5446731093b 100644 --- a/mysql-test/suite/galera/t/MW-388.test +++ b/mysql-test/suite/galera/t/MW-388.test @@ -16,6 +16,18 @@ BEGIN END| DELIMITER ;| +# We need two slave threads here to guarantee progress. +# If we use only one thread the following could happen +# in node_1: +# We block the only slave thread in wsrep_apply_cb and we +# issue an INSERT (by calling the stored procedure) that will +# try to acquire galera's local monitor in pre_commit(). +# This usually works fine, except for when a commit cut event +# sneaks in the slave queue and gets a local seqno smaller than +# that of the INSERT. Because there is only one slave thread, +# commit cut is not processed and therefore does not advance +# local monitor, and our INSERT remains stuck there. +SET GLOBAL wsrep_slave_threads = 2; SET GLOBAL DEBUG = "d,sync.wsrep_apply_cb"; --connection node_2 @@ -27,15 +39,15 @@ SET SESSION DEBUG_SYNC = "now WAIT_FOR sync.wsrep_apply_cb_reached"; --connection node_1 SET SESSION wsrep_sync_wait = 0; -SET SESSION DEBUG_SYNC = 'wsrep_before_replication SIGNAL wsrep_before_replication_reached WAIT_FOR wsrep_before_replication_continue'; +SET SESSION DEBUG_SYNC = 'wsrep_after_replication SIGNAL wsrep_after_replication_reached WAIT_FOR wsrep_after_replication_continue'; --send CALL insert_proc (); --connection node_1a -SET SESSION DEBUG_SYNC = "now WAIT_FOR wsrep_before_replication_reached"; +SET SESSION DEBUG_SYNC = "now WAIT_FOR wsrep_after_replication_reached"; + ---connection node_1a SET GLOBAL DEBUG = ""; -SET DEBUG_SYNC = "now SIGNAL wsrep_before_replication_continue"; +SET DEBUG_SYNC = "now SIGNAL wsrep_after_replication_continue"; SET DEBUG_SYNC = "now SIGNAL signal.wsrep_apply_cb"; --connection node_2 @@ -44,11 +56,13 @@ SET DEBUG_SYNC = "now SIGNAL signal.wsrep_apply_cb"; --connection node_1 # We expect no errors here, because the handler in insert_proc() caught the deadlock error --reap -SELECT @errno; +SELECT @errno = 1213; SELECT * FROM t1; --connection node_2 SELECT * FROM t1; +--connection node_1 +SET GLOBAL wsrep_slave_threads = DEFAULT; DROP TABLE t1; DROP PROCEDURE insert_proc; \ No newline at end of file diff --git a/mysql-test/suite/galera/t/MW-402.test b/mysql-test/suite/galera/t/MW-402.test new file mode 100644 index 00000000000..5713cda5282 --- /dev/null +++ b/mysql-test/suite/galera/t/MW-402.test @@ -0,0 +1,184 @@ +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/have_debug_sync.inc +--source suite/galera/include/galera_have_debug_sync.inc + +# +# we must open connection node_1a here, MW-369.inc will use it later +# +--connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1 + +# +# cascading delete operation is replicated from node2 +# and this conflicts with an update for child table in node1 +# +# As a result, the update should fail for certification error +# +--connection node_1 + +CREATE TABLE p (f1 INTEGER PRIMARY KEY, f2 INTEGER) ENGINE=INNODB; +CREATE TABLE c (f1 INTEGER PRIMARY KEY, p_id INTEGER, f2 INTEGER, + CONSTRAINT fk_1 FOREIGN KEY (p_id) REFERENCES p (f1) ON DELETE CASCADE); + + +INSERT INTO p VALUES (1, 0); +INSERT INTO p VALUES (2, 0); + +INSERT INTO c VALUES (1, 1, 0); + +--let $mw_369_parent_query = UPDATE c SET f2=1 where f1=1 +--let $mw_369_child_query = DELETE FROM p WHERE f1 = 1 + +--connection node_1a +--source MW-369.inc + +# Commit fails +--connection node_1 +--error ER_LOCK_DEADLOCK +--reap + +--connection node_2 +SELECT * FROM p; +SELECT * FROM c; + +DROP TABLE c; +DROP TABLE p; + +# +# cascading update operation is replicated from node2 +# and this conflicts with an update for child table in node1 +# +# As a result, the update should fail for certification error +# +--connection node_1 + +CREATE TABLE p (f1 INTEGER PRIMARY KEY, f2 INTEGER) ENGINE=INNODB; +CREATE TABLE c (f1 INTEGER PRIMARY KEY, p_id INTEGER, f2 INTEGER, + CONSTRAINT fk_1 FOREIGN KEY (p_id) REFERENCES p (f1) ON UPDATE CASCADE); + + +INSERT INTO p VALUES (1, 0); +INSERT INTO p VALUES (2, 0); + +INSERT INTO c VALUES (1, 1, 0); + +--let $mw_369_parent_query = UPDATE c SET f2=2 where f1=1 +--let $mw_369_child_query = UPDATE p set f1=11 WHERE f1 = 1 + +--connection node_1a +--source MW-369.inc + +# Commit fails +--connection node_1 +--error ER_LOCK_DEADLOCK +--reap + +--connection node_2 +SELECT * FROM p; +SELECT * FROM c; + +DROP TABLE c; +DROP TABLE p; + +# +# ON UPDATE CASCADE tests +# Here we update primary key of parent table to cause cascaded update +# on child table +# +# cascading update operation is replicated from node2 +# and this conflicts with an update for child table in node1 +# +# As a result, the update should fail for certification error +# +--connection node_1 + +CREATE TABLE p (f1 INTEGER PRIMARY KEY, f2 INTEGER) ENGINE=INNODB; +CREATE TABLE c (f1 INTEGER PRIMARY KEY, p_id INTEGER, f2 INTEGER, + CONSTRAINT fk_1 FOREIGN KEY (p_id) REFERENCES p (f1) ON UPDATE CASCADE); + + +INSERT INTO p VALUES (1, 0); +INSERT INTO p VALUES (2, 0); + +INSERT INTO c VALUES (1, 1, 0); + +--let $mw_369_parent_query = UPDATE c SET p_id=2 where f1=1 +--let $mw_369_child_query = UPDATE p set f1=11 WHERE f1 = 1 + +--connection node_1a +--source MW-369.inc + +# Commit fails +--connection node_1 +--error ER_LOCK_DEADLOCK +--reap + +# same as previous, but statements in different order +--connection node_2 +SELECT * FROM p; +SELECT * FROM c; + +--let $mw_369_parent_query = UPDATE p set f1=21 WHERE f1 = 11 +--let $mw_369_child_query = UPDATE c SET p_id=2 where f1=1 + +--connection node_1a +--source MW-369.inc + +# Commit fails +--connection node_1 +--error ER_LOCK_DEADLOCK +--reap + + +--connection node_2 +SELECT * FROM p; +SELECT * FROM c; + +DROP TABLE c; +DROP TABLE p; + + +# +# CASCADE DELETE tests with two parent tables +# Here we cause cascaded operation on child table through +# one parent table and have other operation on the other +# parent table +# +# cascading update operation is replicated from node2 +# but this does not conflict with an update for the other parent table in node1 +# +# As a result, the update on p2 should succeed +# +--connection node_1 + +CREATE TABLE p1 (f1 INTEGER PRIMARY KEY, f2 INTEGER) ENGINE=INNODB; +CREATE TABLE p2 (f1 INTEGER PRIMARY KEY, f2 INTEGER) ENGINE=INNODB; +CREATE TABLE c (f1 INTEGER PRIMARY KEY, p1_id INTEGER, p2_id INTEGER, f2 INTEGER, + CONSTRAINT fk_1 FOREIGN KEY (p1_id) REFERENCES p1 (f1) ON DELETE CASCADE, + CONSTRAINT fk_2 FOREIGN KEY (p2_id) REFERENCES p2 (f1)); + + +INSERT INTO p1 VALUES (1, 0); +INSERT INTO p2 VALUES (1, 0); + +INSERT INTO c VALUES (1, 1, 1, 0); + +--let $mw_369_parent_query = UPDATE p2 SET f2=2 where f1=1 +--let $mw_369_child_query = DELETE FROM p1 WHERE f1 = 1 + +--connection node_1a +--source MW-369.inc + +# Commit succeeds +--connection node_1 +--reap + +# same as previous, but statements in different order +--connection node_2 +SELECT * FROM p1; +SELECT * FROM p2; +SELECT * FROM c; + +DROP TABLE c; +DROP TABLE p1; +DROP TABLE p2; \ No newline at end of file diff --git a/sql/wsrep_hton.cc b/sql/wsrep_hton.cc index 47697c34eb4..a9dbc1a17c2 100644 --- a/sql/wsrep_hton.cc +++ b/sql/wsrep_hton.cc @@ -498,6 +498,9 @@ wsrep_run_wsrep_commit(THD *thd, handlerton *hton, bool all) } mysql_mutex_lock(&thd->LOCK_wsrep_thd); + + DEBUG_SYNC(thd, "wsrep_after_replication"); + switch(rcode) { case 0: /* diff --git a/storage/innobase/row/row0ins.cc b/storage/innobase/row/row0ins.cc index 47cbadfd102..8454f3aa540 100644 --- a/storage/innobase/row/row0ins.cc +++ b/storage/innobase/row/row0ins.cc @@ -1295,8 +1295,7 @@ row_ins_foreign_check_on_constraint( foreign, clust_rec, clust_index, - FALSE, - (node) ? TRUE : FALSE); + FALSE, FALSE); if (err != DB_SUCCESS) { fprintf(stderr, "WSREP: foreign key append failed: %d\n", err); diff --git a/storage/innobase/row/row0upd.cc b/storage/innobase/row/row0upd.cc index d6077a8f97b..0f448161876 100644 --- a/storage/innobase/row/row0upd.cc +++ b/storage/innobase/row/row0upd.cc @@ -1980,8 +1980,8 @@ row_upd_sec_index_entry( } #ifdef WITH_WSREP if (wsrep_on(trx->mysql_thd) && - !wsrep_thd_is_BF(trx->mysql_thd, FALSE) && err == DB_SUCCESS && !referenced && + !wsrep_thd_is_BF(trx->mysql_thd, FALSE) && !(parent && que_node_get_type(parent) == QUE_NODE_UPDATE && ((upd_node_t*)parent)->cascade_node == node) && diff --git a/storage/xtradb/row/row0ins.cc b/storage/xtradb/row/row0ins.cc index 75450a8eb36..dea328f1ff5 100644 --- a/storage/xtradb/row/row0ins.cc +++ b/storage/xtradb/row/row0ins.cc @@ -1301,8 +1301,7 @@ row_ins_foreign_check_on_constraint( foreign, clust_rec, clust_index, - FALSE, - (node) ? TRUE : FALSE); + FALSE, FALSE); if (err != DB_SUCCESS) { fprintf(stderr, "WSREP: foreign key append failed: %d\n", err); diff --git a/storage/xtradb/row/row0upd.cc b/storage/xtradb/row/row0upd.cc index 079025d017d..60434a6d338 100644 --- a/storage/xtradb/row/row0upd.cc +++ b/storage/xtradb/row/row0upd.cc @@ -1985,8 +1985,8 @@ row_upd_sec_index_entry( } #ifdef WITH_WSREP if (wsrep_on(trx->mysql_thd) && - !wsrep_thd_is_BF(trx->mysql_thd, FALSE) && err == DB_SUCCESS && !referenced && + !wsrep_thd_is_BF(trx->mysql_thd, FALSE) && !(parent && que_node_get_type(parent) == QUE_NODE_UPDATE && ((upd_node_t*)parent)->cascade_node == node) &&