diff options
author | Kristian Nielsen <knielsen@knielsen-hq.org> | 2017-07-03 09:33:41 +0200 |
---|---|---|
committer | Kristian Nielsen <knielsen@knielsen-hq.org> | 2017-07-03 09:33:41 +0200 |
commit | 1d91910b944a801a2bbe138d4258c53eaeb0c473 (patch) | |
tree | 76f2ed8b5bb2c9e44eea7cc0366d8d1d7a070966 | |
parent | 176000a54ceb8dabe8f8b985aff565dfae6fb0df (diff) | |
parent | 95e09f0766f037530d2dcfbb6c530137a4ee0db4 (diff) | |
download | mariadb-git-1d91910b944a801a2bbe138d4258c53eaeb0c473.tar.gz |
MDEV-12179: Per-engine mysql.gtid_slave_pos table
Merge into MariaDB 10.3.
50 files changed, 2944 insertions, 136 deletions
diff --git a/mysql-test/extra/rpl_tests/rpl_deadlock.test b/mysql-test/extra/rpl_tests/rpl_deadlock.test index f7a1e71d5d9..e9191d5fcd8 100644 --- a/mysql-test/extra/rpl_tests/rpl_deadlock.test +++ b/mysql-test/extra/rpl_tests/rpl_deadlock.test @@ -46,6 +46,9 @@ BEGIN; SELECT * FROM t1 FOR UPDATE; # Save variable 'Slave_retried_transactions' before deadlock let $slave_retried_transactions= query_get_value(SHOW GLOBAL STATUS LIKE 'Slave_retried_transactions', Value, 1); +# Run the START SLAVE in a separate connection. Otherwise it terminates +# the SELECT FOR UPDATE transaction (START SLAVE does implicit COMMIT!). +connection slave1; START SLAVE; # Wait until SQL thread blocked: variable 'Slave_retried_transactions' will incremented let $status_var= Slave_retried_transactions; @@ -53,6 +56,7 @@ let $status_var_value= $slave_retried_transactions; let $status_type= GLOBAL; let $status_var_comparsion= >; --source include/wait_for_status_var.inc +connection slave; SELECT COUNT(*) FROM t2; COMMIT; sync_with_master; @@ -78,9 +82,11 @@ BEGIN; # Hold lock SELECT * FROM t1 FOR UPDATE; # Wait until slave stopped with error 'Lock wait timeout exceeded' +connection slave1; START SLAVE; let $slave_sql_errno= 1205; --source include/wait_for_slave_sql_error.inc +connection slave; SELECT COUNT(*) FROM t2; COMMIT; --source include/start_slave.inc @@ -109,9 +115,11 @@ BEGIN; # Hold lock SELECT * FROM t1 FOR UPDATE; # Wait until slave stopped with error 'Lock wait timeout exceeded' +connection slave1; START SLAVE; let $slave_sql_errno= 1205; --source include/wait_for_slave_sql_error.inc +connection slave; SELECT COUNT(*) FROM t2; COMMIT; --source include/start_slave.inc diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 3bd8dd3ce8f..9e1c72e32ca 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -5225,6 +5225,7 @@ sub server_need_restart { if (!My::Options::same($started_opts, $extra_opts) || exists $server->{'restart_opts'}) { + delete $server->{'restart_opts'}; my $use_dynamic_option_switch= 0; if (!$use_dynamic_option_switch) { diff --git a/mysql-test/r/mysqld--help.result b/mysql-test/r/mysqld--help.result index 1fd365e3029..838ee745f5d 100644 --- a/mysql-test/r/mysqld--help.result +++ b/mysql-test/r/mysqld--help.result @@ -257,6 +257,13 @@ The following options may be given as the first argument: applied; this means it is the responsibility of the user to ensure that GTID sequence numbers are strictly increasing. + --gtid-pos-auto-engines=name + List of engines for which to automatically create a + mysql.gtid_slave_pos_ENGINE table, if a transaction using + that engine is replicated. This can be used to avoid + introducing cross-engine transactions, if engines are + used different from that used by table + mysql.gtid_slave_pos --gtid-strict-mode Enforce strict seq_no ordering of events in the binary log. Slave stops with an error if it encounters an event that would cause it to generate an out-of-order binlog if @@ -1259,6 +1266,7 @@ getopt-prefix-matching TRUE group-concat-max-len 1048576 gtid-domain-id 0 gtid-ignore-duplicates FALSE +gtid-pos-auto-engines gtid-strict-mode FALSE help TRUE histogram-size 0 diff --git a/mysql-test/suite/multi_source/gtid_ignore_duplicates.result b/mysql-test/suite/multi_source/gtid_ignore_duplicates.result index 92d096245c7..96627b42c97 100644 --- a/mysql-test/suite/multi_source/gtid_ignore_duplicates.result +++ b/mysql-test/suite/multi_source/gtid_ignore_duplicates.result @@ -65,6 +65,7 @@ include/wait_for_slave_to_start.inc set default_master_connection = ''; connection server_1; ALTER TABLE mysql.gtid_slave_pos ENGINE=InnoDB; +CALL mtr.add_suppression("This change will not take full effect until all SQL threads have been restarted"); CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB; INSERT INTO t1 VALUES (1); BEGIN; @@ -491,17 +492,21 @@ SET GLOBAL slave_parallel_threads= @old_parallel; SET GLOBAL gtid_ignore_duplicates= @old_ignore_duplicates; connection server_1; DROP TABLE t1; +ALTER TABLE mysql.gtid_slave_pos ENGINE=MyISAM; include/reset_master_slave.inc disconnect server_1; connection server_2; DROP TABLE t1; +ALTER TABLE mysql.gtid_slave_pos ENGINE=MyISAM; include/reset_master_slave.inc disconnect server_2; connection server_3; DROP TABLE t1; +ALTER TABLE mysql.gtid_slave_pos ENGINE=MyISAM; include/reset_master_slave.inc disconnect server_3; connection server_4; DROP TABLE t1; +ALTER TABLE mysql.gtid_slave_pos ENGINE=MyISAM; include/reset_master_slave.inc disconnect server_4; diff --git a/mysql-test/suite/multi_source/gtid_ignore_duplicates.test b/mysql-test/suite/multi_source/gtid_ignore_duplicates.test index 218d91aa7fb..b61da0f0f33 100644 --- a/mysql-test/suite/multi_source/gtid_ignore_duplicates.test +++ b/mysql-test/suite/multi_source/gtid_ignore_duplicates.test @@ -86,6 +86,7 @@ set default_master_connection = ''; --connection server_1 ALTER TABLE mysql.gtid_slave_pos ENGINE=InnoDB; +CALL mtr.add_suppression("This change will not take full effect until all SQL threads have been restarted"); CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB; INSERT INTO t1 VALUES (1); BEGIN; @@ -431,20 +432,24 @@ SET GLOBAL gtid_ignore_duplicates= @old_ignore_duplicates; --connection server_1 DROP TABLE t1; +ALTER TABLE mysql.gtid_slave_pos ENGINE=MyISAM; --source include/reset_master_slave.inc --disconnect server_1 --connection server_2 DROP TABLE t1; +ALTER TABLE mysql.gtid_slave_pos ENGINE=MyISAM; --source include/reset_master_slave.inc --disconnect server_2 --connection server_3 DROP TABLE t1; +ALTER TABLE mysql.gtid_slave_pos ENGINE=MyISAM; --source include/reset_master_slave.inc --disconnect server_3 --connection server_4 DROP TABLE t1; +ALTER TABLE mysql.gtid_slave_pos ENGINE=MyISAM; --source include/reset_master_slave.inc --disconnect server_4 diff --git a/mysql-test/suite/multi_source/gtid_slave_pos.result b/mysql-test/suite/multi_source/gtid_slave_pos.result new file mode 100644 index 00000000000..d57cfc17959 --- /dev/null +++ b/mysql-test/suite/multi_source/gtid_slave_pos.result @@ -0,0 +1,155 @@ +connect slave1,127.0.0.1,root,,,$SERVER_MYPORT_3; +connect master1,127.0.0.1,root,,,$SERVER_MYPORT_1; +connect master2,127.0.0.1,root,,,$SERVER_MYPORT_2; +connection slave1; +CHANGE MASTER 'slave1' TO master_port=MYPORT_1, master_host='127.0.0.1', master_user='root', master_use_gtid=slave_pos; +CHANGE MASTER 'slave2' TO master_port=MYPORT_2, master_host='127.0.0.1', master_user='root', master_use_gtid=slave_pos; +set default_master_connection = 'slave1'; +START SLAVE; +include/wait_for_slave_to_start.inc +set default_master_connection = 'slave2'; +START SLAVE; +include/wait_for_slave_to_start.inc +set default_master_connection = ''; +connection master1; +SET GLOBAL gtid_domain_id= 1; +SET SESSION gtid_domain_id= 1; +CREATE TABLE t3 (a INT PRIMARY KEY, b VARCHAR(10)) ENGINE=InnoDB; +CREATE TABLE t1 (a INT PRIMARY KEY, b VARCHAR(10)); +INSERT INTO t1 VALUES (1, "initial"); +INSERT INTO t3 VALUES (101, "initial 1"); +include/save_master_gtid.inc +connection master2; +SET GLOBAL gtid_domain_id= 2; +SET SESSION gtid_domain_id= 2; +CREATE TABLE t2 (a INT PRIMARY KEY, b VARCHAR(10)) ENGINE=InnoDB; +INSERT INTO t2 VALUES (1, "initial"); +connection slave1; +include/sync_with_master_gtid.inc +connection master2; +include/save_master_gtid.inc +connection slave1; +include/sync_with_master_gtid.inc +*** Add an innodb gtid_slave_pos table. It is not used yet as slaves are already running *** +SET sql_log_bin=0; +CREATE TABLE mysql.gtid_slave_pos_innodb LIKE mysql.gtid_slave_pos; +ALTER TABLE mysql.gtid_slave_pos_innodb ENGINE=InnoDB; +SET sql_log_bin=0; +connection master1; +INSERT INTO t3 VALUES (102, "secondary"); +include/save_master_gtid.inc +connection slave1; +include/sync_with_master_gtid.inc +SELECT domain_id, max(seq_no) FROM mysql.gtid_slave_pos GROUP BY domain_id; +domain_id max(seq_no) +1 5 +2 2 +SELECT domain_id, max(seq_no) FROM mysql.gtid_slave_pos_innodb GROUP BY domain_id; +domain_id max(seq_no) +*** Restart one slave thread, the other keeps running. Now the new table is used *** +connection slave1; +set default_master_connection = 'slave1'; +STOP SLAVE; +include/wait_for_slave_to_stop.inc +START SLAVE; +include/wait_for_slave_to_start.inc +connection master1; +INSERT INTO t1 VALUES (2, "followup"); +include/save_master_gtid.inc +connection slave1; +include/sync_with_master_gtid.inc +connection master2; +INSERT INTO t2 VALUES (2, "secondary2"); +include/save_master_gtid.inc +connection slave1; +include/sync_with_master_gtid.inc +SELECT domain_id, max(seq_no) FROM mysql.gtid_slave_pos GROUP BY domain_id; +domain_id max(seq_no) +1 6 +2 2 +SELECT domain_id, max(seq_no) FROM mysql.gtid_slave_pos_innodb GROUP BY domain_id; +domain_id max(seq_no) +2 3 +*** Remove a gtid_slave_posXXX table, restart one slave *** +*** Get a warning that the change is not yet picked up *** +*** See that updates fail due to trying to use the missing table *** +connection slave1; +SET sql_log_bin=0; +DROP TABLE mysql.gtid_slave_pos_innodb; +SET sql_log_bin=1; +set default_master_connection = 'slave2'; +STOP SLAVE; +include/wait_for_slave_to_stop.inc +START SLAVE; +include/wait_for_slave_to_start.inc +CALL mtr.add_suppression("The table mysql.gtid_slave_pos_innodb was removed."); +connection master2; +INSERT INTO t2 VALUES (3, "tertiary 2"); +connection slave1; +include/wait_for_slave_sql_error.inc [errno=1942] +SELECT domain_id, max(seq_no) FROM mysql.gtid_slave_pos GROUP BY domain_id; +domain_id max(seq_no) +1 6 +2 2 +*** Stop both slaves, see that the drop of mysql.gtid_slave_pos_innodb is now picked up *** +connection slave1; +set default_master_connection = 'slave1'; +STOP SLAVE; +include/wait_for_slave_to_stop.inc +set default_master_connection = 'slave2'; +STOP SLAVE; +include/wait_for_slave_to_stop.inc +set default_master_connection = 'slave1'; +START SLAVE; +include/wait_for_slave_to_start.inc +connection master1; +INSERT INTO t1 VALUES (3, "more stuff"); +include/save_master_gtid.inc +connection slave1; +include/sync_with_master_gtid.inc +set default_master_connection = 'slave2'; +START SLAVE; +include/wait_for_slave_to_start.inc +connection master2; +include/save_master_gtid.inc +connection slave1; +include/sync_with_master_gtid.inc +SELECT * FROM t1 ORDER BY a; +a b +1 initial +2 followup +3 more stuff +SELECT * FROM t2 ORDER BY a; +a b +1 initial +2 secondary2 +3 tertiary 2 +SELECT * FROM t3 ORDER BY a; +a b +101 initial 1 +102 secondary +SELECT domain_id, max(seq_no) FROM mysql.gtid_slave_pos GROUP BY domain_id; +domain_id max(seq_no) +1 7 +2 4 +connection master1; +DROP TABLE t1; +DROP TABLE t3; +connection master2; +DROP TABLE t2; +connection slave1; +SET GLOBAL gtid_domain_id=0; +STOP ALL SLAVES; +Warnings: +Note 1938 SLAVE 'slave1' stopped +Note 1938 SLAVE 'slave2' stopped +include/reset_master_slave.inc +disconnect slave1; +connection master1; +SET GLOBAL gtid_domain_id=0; +include/reset_master_slave.inc +disconnect master1; +connection master2; +SET GLOBAL gtid_domain_id=0; +include/reset_master_slave.inc +disconnect master2; diff --git a/mysql-test/suite/multi_source/gtid_slave_pos.test b/mysql-test/suite/multi_source/gtid_slave_pos.test new file mode 100644 index 00000000000..c01130f8cd5 --- /dev/null +++ b/mysql-test/suite/multi_source/gtid_slave_pos.test @@ -0,0 +1,173 @@ +--source include/not_embedded.inc +--source include/have_innodb.inc + +# +# Test multiple mysql.gtid_slave_posXXX tables with multiple master connections +# + +--connect (slave1,127.0.0.1,root,,,$SERVER_MYPORT_3) +--connect (master1,127.0.0.1,root,,,$SERVER_MYPORT_1) +--connect (master2,127.0.0.1,root,,,$SERVER_MYPORT_2) + +--connection slave1 +--replace_result $SERVER_MYPORT_1 MYPORT_1 +eval CHANGE MASTER 'slave1' TO master_port=$SERVER_MYPORT_1, master_host='127.0.0.1', master_user='root', master_use_gtid=slave_pos; +--replace_result $SERVER_MYPORT_2 MYPORT_2 +eval CHANGE MASTER 'slave2' TO master_port=$SERVER_MYPORT_2, master_host='127.0.0.1', master_user='root', master_use_gtid=slave_pos; +set default_master_connection = 'slave1'; +START SLAVE; +--source include/wait_for_slave_to_start.inc +set default_master_connection = 'slave2'; +START SLAVE; +--source include/wait_for_slave_to_start.inc +set default_master_connection = ''; + + +--connection master1 +SET GLOBAL gtid_domain_id= 1; +SET SESSION gtid_domain_id= 1; +CREATE TABLE t3 (a INT PRIMARY KEY, b VARCHAR(10)) ENGINE=InnoDB; +CREATE TABLE t1 (a INT PRIMARY KEY, b VARCHAR(10)); +INSERT INTO t1 VALUES (1, "initial"); +INSERT INTO t3 VALUES (101, "initial 1"); +--source include/save_master_gtid.inc + +--connection master2 +SET GLOBAL gtid_domain_id= 2; +SET SESSION gtid_domain_id= 2; +CREATE TABLE t2 (a INT PRIMARY KEY, b VARCHAR(10)) ENGINE=InnoDB; +INSERT INTO t2 VALUES (1, "initial"); + + +--connection slave1 +--source include/sync_with_master_gtid.inc + +--connection master2 +--source include/save_master_gtid.inc + +--connection slave1 +--source include/sync_with_master_gtid.inc + + +--echo *** Add an innodb gtid_slave_pos table. It is not used yet as slaves are already running *** + +SET sql_log_bin=0; +CREATE TABLE mysql.gtid_slave_pos_innodb LIKE mysql.gtid_slave_pos; +ALTER TABLE mysql.gtid_slave_pos_innodb ENGINE=InnoDB; +SET sql_log_bin=0; + +--connection master1 +INSERT INTO t3 VALUES (102, "secondary"); +--source include/save_master_gtid.inc + +--connection slave1 +--source include/sync_with_master_gtid.inc +SELECT domain_id, max(seq_no) FROM mysql.gtid_slave_pos GROUP BY domain_id; +SELECT domain_id, max(seq_no) FROM mysql.gtid_slave_pos_innodb GROUP BY domain_id; + +--echo *** Restart one slave thread, the other keeps running. Now the new table is used *** +--connection slave1 +set default_master_connection = 'slave1'; +STOP SLAVE; +--source include/wait_for_slave_to_stop.inc +START SLAVE; +--source include/wait_for_slave_to_start.inc + +# Send through a transaction on the slave1 connection, to be sure that it has +# had time to update the state with the new table. +--connection master1 +INSERT INTO t1 VALUES (2, "followup"); +--source include/save_master_gtid.inc +--connection slave1 +--source include/sync_with_master_gtid.inc + +--connection master2 +INSERT INTO t2 VALUES (2, "secondary2"); +--source include/save_master_gtid.inc + +--connection slave1 +--source include/sync_with_master_gtid.inc +SELECT domain_id, max(seq_no) FROM mysql.gtid_slave_pos GROUP BY domain_id; +SELECT domain_id, max(seq_no) FROM mysql.gtid_slave_pos_innodb GROUP BY domain_id; + +--echo *** Remove a gtid_slave_posXXX table, restart one slave *** +--echo *** Get a warning that the change is not yet picked up *** +--echo *** See that updates fail due to trying to use the missing table *** +--connection slave1 +SET sql_log_bin=0; +DROP TABLE mysql.gtid_slave_pos_innodb; +SET sql_log_bin=1; +set default_master_connection = 'slave2'; +STOP SLAVE; +--source include/wait_for_slave_to_stop.inc +START SLAVE; +--source include/wait_for_slave_to_start.inc +CALL mtr.add_suppression("The table mysql.gtid_slave_pos_innodb was removed."); + +--connection master2 +INSERT INTO t2 VALUES (3, "tertiary 2"); + +--connection slave1 +--let $slave_sql_errno= 1942 +--source include/wait_for_slave_sql_error.inc +SELECT domain_id, max(seq_no) FROM mysql.gtid_slave_pos GROUP BY domain_id; + +--echo *** Stop both slaves, see that the drop of mysql.gtid_slave_pos_innodb is now picked up *** +--connection slave1 +set default_master_connection = 'slave1'; +STOP SLAVE; +--source include/wait_for_slave_to_stop.inc +set default_master_connection = 'slave2'; +STOP SLAVE; +--source include/wait_for_slave_to_stop.inc +set default_master_connection = 'slave1'; +START SLAVE; +--source include/wait_for_slave_to_start.inc +# Send through a transaction on the slave1 connection, to be sure that it has +# had time to update the state with the new table. +--connection master1 +INSERT INTO t1 VALUES (3, "more stuff"); +--source include/save_master_gtid.inc +--connection slave1 +--source include/sync_with_master_gtid.inc +set default_master_connection = 'slave2'; +START SLAVE; +--source include/wait_for_slave_to_start.inc + +--connection master2 +--source include/save_master_gtid.inc +--connection slave1 +--source include/sync_with_master_gtid.inc +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; +SELECT * FROM t3 ORDER BY a; +SELECT domain_id, max(seq_no) FROM mysql.gtid_slave_pos GROUP BY domain_id; + + +# Cleanup. +--connection master1 +DROP TABLE t1; +DROP TABLE t3; + +--connection master2 +DROP TABLE t2; + +--connection slave1 +SET GLOBAL gtid_domain_id=0; +--let $wait_condition= SELECT COUNT(*)=0 FROM information_schema.tables WHERE table_name IN ("t1", "t2", "t3") AND table_schema = "test" +--source include/wait_condition.inc +--sorted_result +STOP ALL SLAVES; +--source include/reset_master_slave.inc +--disconnect slave1 + + +--connection master1 +SET GLOBAL gtid_domain_id=0; +--source include/reset_master_slave.inc +--disconnect master1 + +--connection master2 +SET GLOBAL gtid_domain_id=0; +--source include/reset_master_slave.inc +--disconnect master2 diff --git a/mysql-test/suite/rpl/r/rpl_deadlock_innodb.result b/mysql-test/suite/rpl/r/rpl_deadlock_innodb.result index 1c9611ba8f0..bb8c45ae4eb 100644 --- a/mysql-test/suite/rpl/r/rpl_deadlock_innodb.result +++ b/mysql-test/suite/rpl/r/rpl_deadlock_innodb.result @@ -39,7 +39,9 @@ connection slave; BEGIN; SELECT * FROM t1 FOR UPDATE; a +connection slave1; START SLAVE; +connection slave; SELECT COUNT(*) FROM t2; COUNT(*) 0 @@ -61,8 +63,10 @@ BEGIN; SELECT * FROM t1 FOR UPDATE; a 1 +connection slave1; START SLAVE; include/wait_for_slave_sql_error.inc [errno=1205] +connection slave; SELECT COUNT(*) FROM t2; COUNT(*) 0 @@ -92,8 +96,10 @@ SELECT * FROM t1 FOR UPDATE; a 1 1 +connection slave1; START SLAVE; include/wait_for_slave_sql_error.inc [errno=1205] +connection slave; SELECT COUNT(*) FROM t2; COUNT(*) 0 diff --git a/mysql-test/suite/rpl/r/rpl_gtid_crash.result b/mysql-test/suite/rpl/r/rpl_gtid_crash.result index 7b6e95bf718..ed2a2b287e9 100644 --- a/mysql-test/suite/rpl/r/rpl_gtid_crash.result +++ b/mysql-test/suite/rpl/r/rpl_gtid_crash.result @@ -88,16 +88,16 @@ include/save_master_gtid.inc connection server_2; include/sync_with_master_gtid.inc include/stop_slave.inc -SET GLOBAL debug_dbug="+d,crash_commit_before"; START SLAVE; +SET GLOBAL debug_dbug="+d,crash_commit_before"; connection server_1; INSERT INTO t1 VALUES (5); include/save_master_gtid.inc connection server_2; include/sync_with_master_gtid.inc include/stop_slave.inc -SET GLOBAL debug_dbug="+d,crash_commit_after"; START SLAVE; +SET GLOBAL debug_dbug="+d,crash_commit_after"; connection server_1; INSERT INTO t1 VALUES (6); include/save_master_gtid.inc diff --git a/mysql-test/suite/rpl/r/rpl_gtid_errorhandling.result b/mysql-test/suite/rpl/r/rpl_gtid_errorhandling.result index 7c2471ba37c..62a5b9c3531 100644 --- a/mysql-test/suite/rpl/r/rpl_gtid_errorhandling.result +++ b/mysql-test/suite/rpl/r/rpl_gtid_errorhandling.result @@ -12,21 +12,21 @@ connection master; INSERT INTO t1 VALUES (1); connection slave; CALL mtr.add_suppression("Slave: Failed to open mysql.gtid_slave_pos"); -include/wait_for_slave_sql_error.inc [errno=1942] +include/wait_for_slave_sql_error.inc [errno=1944] include/stop_slave.inc ALTER TABLE mysql.gtid_slave_pos CHANGE seq_no seq_no BIGINT UNSIGNED NOT NULL; ALTER TABLE mysql.gtid_slave_pos DROP PRIMARY KEY; ALTER TABLE mysql.gtid_slave_pos ADD PRIMARY KEY (sub_id, domain_id); START SLAVE; -include/wait_for_slave_sql_error.inc [errno=1942] +include/wait_for_slave_sql_error.inc [errno=1944] include/stop_slave.inc ALTER TABLE mysql.gtid_slave_pos DROP PRIMARY KEY; START SLAVE; -include/wait_for_slave_sql_error.inc [errno=1942] +include/wait_for_slave_sql_error.inc [errno=1944] include/stop_slave.inc ALTER TABLE mysql.gtid_slave_pos ADD PRIMARY KEY (sub_id); START SLAVE; -include/wait_for_slave_sql_error.inc [errno=1942] +include/wait_for_slave_sql_error.inc [errno=1944] include/stop_slave.inc ALTER TABLE mysql.gtid_slave_pos DROP PRIMARY KEY; ALTER TABLE mysql.gtid_slave_pos ADD PRIMARY KEY (domain_id, sub_id); diff --git a/mysql-test/suite/rpl/r/rpl_gtid_ignored.result b/mysql-test/suite/rpl/r/rpl_gtid_ignored.result index 8d5b9be2ca0..ac608c3c2a3 100644 --- a/mysql-test/suite/rpl/r/rpl_gtid_ignored.result +++ b/mysql-test/suite/rpl/r/rpl_gtid_ignored.result @@ -79,6 +79,7 @@ a 9 connection server_1; DROP TABLE t1; +ALTER TABLE mysql.gtid_slave_pos ENGINE=MyISAM; SET GLOBAL gtid_strict_mode= @old_gtid_strict_mode; SET debug_sync = "reset"; connection server_2; diff --git a/mysql-test/suite/rpl/r/rpl_gtid_mdev4484.result b/mysql-test/suite/rpl/r/rpl_gtid_mdev4484.result index c49207b99fa..aaeb0c8f119 100644 --- a/mysql-test/suite/rpl/r/rpl_gtid_mdev4484.result +++ b/mysql-test/suite/rpl/r/rpl_gtid_mdev4484.result @@ -1,5 +1,12 @@ include/master-slave.inc [connection master] +connection slave; +include/stop_slave.inc +SET sql_log_bin=0; +ALTER TABLE mysql.gtid_slave_pos ENGINE=MyISAM; +SET sql_log_bin=1; +include/start_slave.inc +connection master; CREATE TABLE t1 (i int) ENGINE=InnoDB; connection slave; *** MDEV-4484, incorrect error handling when entries in gtid_slave_pos not found. *** @@ -13,7 +20,6 @@ SET @old_dbug= @@GLOBAL.debug_dbug; SET GLOBAL debug_dbug="+d,gtid_slave_pos_simulate_failed_delete"; SET sql_log_bin= 0; CALL mtr.add_suppression("Can't find file"); -ALTER TABLE mysql.gtid_slave_pos ENGINE=MyISAM; SET sql_log_bin= 1; include/start_slave.inc connection master; diff --git a/mysql-test/suite/rpl/r/rpl_gtid_stop_start.result b/mysql-test/suite/rpl/r/rpl_gtid_stop_start.result index 3f3b5e4344a..ff845794c22 100644 --- a/mysql-test/suite/rpl/r/rpl_gtid_stop_start.result +++ b/mysql-test/suite/rpl/r/rpl_gtid_stop_start.result @@ -194,7 +194,7 @@ domain_id COUNT(*) *** MDEV-4650: show variables; ERROR 1946 (HY000): Failed to load replication slave GTID position *** connection server_2; SET sql_log_bin=0; -RENAME TABLE mysql.gtid_slave_pos TO mysql.gtid_slave_pos_old; +RENAME TABLE mysql.gtid_slave_pos TO mysql.old_gtid_slave_pos; SET sql_log_bin=1; SHOW VARIABLES; SHOW VARIABLES LIKE 'gtid_slave_pos'; @@ -207,7 +207,7 @@ Level Code Message Error 1146 Table 'mysql.gtid_slave_pos' doesn't exist Error 1946 Failed to load replication slave GTID position from table mysql.gtid_slave_pos SET sql_log_bin=0; -RENAME TABLE mysql.gtid_slave_pos_old TO mysql.gtid_slave_pos; +RENAME TABLE mysql.old_gtid_slave_pos TO mysql.gtid_slave_pos; CALL mtr.add_suppression("Failed to load slave replication state from table mysql.gtid_slave_pos"); SET sql_log_bin=1; SHOW VARIABLES LIKE 'gtid_slave_pos'; diff --git a/mysql-test/suite/rpl/r/rpl_gtid_until.result b/mysql-test/suite/rpl/r/rpl_gtid_until.result index 886f6cfd2cb..2295aad34ac 100644 --- a/mysql-test/suite/rpl/r/rpl_gtid_until.result +++ b/mysql-test/suite/rpl/r/rpl_gtid_until.result @@ -10,6 +10,8 @@ SET s= SUBSTR(s FROM 1 FOR LOCATE(",", s) - 1); RETURN s; END| connection server_2; +include/stop_slave.inc +include/start_slave.inc START SLAVE UNTIL master_gtid_pos = ""; ERROR HY000: Slave is already running include/stop_slave_io.inc diff --git a/mysql-test/suite/rpl/r/rpl_mdev10863.result b/mysql-test/suite/rpl/r/rpl_mdev10863.result index 158d4a921b7..6accd1ee830 100644 --- a/mysql-test/suite/rpl/r/rpl_mdev10863.result +++ b/mysql-test/suite/rpl/r/rpl_mdev10863.result @@ -46,5 +46,6 @@ SET GLOBAL slave_parallel_threads=@old_parallel_threads; SET GLOBAL max_relay_log_size= @old_max_relay; include/start_slave.inc connection server_1; +ALTER TABLE mysql.gtid_slave_pos ENGINE=MyISAM; DROP TABLE t1; include/rpl_end.inc diff --git a/mysql-test/suite/rpl/r/rpl_mdev12179.result b/mysql-test/suite/rpl/r/rpl_mdev12179.result new file mode 100644 index 00000000000..40059375356 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_mdev12179.result @@ -0,0 +1,265 @@ +include/rpl_init.inc [topology=1->2] +connection server_2; +SET GLOBAL gtid_pos_auto_engines="innodb"; +ERROR HY000: This operation cannot be performed as you have a running slave ''; run STOP SLAVE '' first +include/stop_slave.inc +CHANGE MASTER TO master_use_gtid=slave_pos; +SELECT @@gtid_pos_auto_engines; +@@gtid_pos_auto_engines + +SELECT @@SESSION.gtid_pos_auto_engines; +ERROR HY000: Variable 'gtid_pos_auto_engines' is a GLOBAL variable +SET GLOBAL gtid_pos_auto_engines= NULL; +ERROR 42000: Variable 'gtid_pos_auto_engines' can't be set to the value of 'NULL' +SET GLOBAL gtid_pos_auto_engines="innodb"; +SELECT @@gtid_pos_auto_engines; +@@gtid_pos_auto_engines +InnoDB +SET GLOBAL gtid_pos_auto_engines="myisam,innodb"; +SELECT @@gtid_pos_auto_engines; +@@gtid_pos_auto_engines +MyISAM,InnoDB +SET GLOBAL gtid_pos_auto_engines="innodb,myisam"; +SELECT @@gtid_pos_auto_engines; +@@gtid_pos_auto_engines +InnoDB,MyISAM +SET GLOBAL gtid_pos_auto_engines="innodb,innodb,myisam,innodb,myisam,myisam,innodb"; +SELECT @@gtid_pos_auto_engines; +@@gtid_pos_auto_engines +InnoDB,MyISAM +SET GLOBAL gtid_pos_auto_engines=DEFAULT; +SELECT @@gtid_pos_auto_engines; +@@gtid_pos_auto_engines + +SET GLOBAL gtid_pos_auto_engines=""; +SELECT @@gtid_pos_auto_engines; +@@gtid_pos_auto_engines + +include/start_slave.inc +connection server_1; +CREATE TABLE t1 (a INT PRIMARY KEY); +INSERT INTO t1 VALUES (1); +SELECT * FROM t1 ORDER BY a; +a +1 +connection server_2; +SELECT * FROM t1 ORDER BY a; +a +1 +include/stop_slave.inc +SET sql_log_bin=0; +ALTER TABLE mysql.gtid_slave_pos ENGINE=MyISAM; +CREATE TABLE mysql.gtid_slave_pos_innodb LIKE mysql.gtid_slave_pos; +ALTER TABLE mysql.gtid_slave_pos_innodb ENGINE=InnoDB; +INSERT INTO mysql.gtid_slave_pos_innodb SELECT * FROM mysql.gtid_slave_pos; +TRUNCATE mysql.gtid_slave_pos; +SET sql_log_bin=1; +connection server_1; +INSERT INTO t1 VALUES (2); +INSERT INTO t1 VALUES (3); +SELECT * FROM t1 ORDER BY a; +a +1 +2 +3 +include/save_master_gtid.inc +*** Restart server with --gtid-pos-auto-engines=innodb,myisam *** +connection server_2; +include/sync_with_master_gtid.inc +SELECT * FROM t1 ORDER BY a; +a +1 +2 +3 +*** Verify no new gtid_slave_pos* tables are created *** +SELECT table_name, engine FROM information_schema.tables +WHERE table_schema='mysql' AND table_name LIKE 'gtid_slave_pos%' + ORDER BY table_name; +table_name engine +gtid_slave_pos MyISAM +gtid_slave_pos_innodb InnoDB +SELECT @@gtid_pos_auto_engines; +@@gtid_pos_auto_engines +InnoDB,MyISAM +include/stop_slave.inc +SET sql_log_bin=0; +INSERT INTO mysql.gtid_slave_pos_innodb SELECT * FROM mysql.gtid_slave_pos; +DROP TABLE mysql.gtid_slave_pos; +RENAME TABLE mysql.gtid_slave_pos_innodb TO mysql.gtid_slave_pos; +SET sql_log_bin=1; +connection server_1; +CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=InnoDB; +INSERT INTO t1 VALUES (4); +INSERT INTO t2 VALUES (1); +SELECT * FROM t1 ORDER BY a; +a +1 +2 +3 +4 +SELECT * FROM t2 ORDER BY a; +a +1 +include/save_master_gtid.inc +*** Restart server with --gtid-pos-auto-engines=myisam,innodb *** +connection server_2; +include/sync_with_master_gtid.inc +SELECT * FROM t1 ORDER BY a; +a +1 +2 +3 +4 +SELECT * FROM t2 ORDER BY a; +a +1 +*** Verify that no new gtid_slave_pos* tables are auto-created *** +SELECT table_name, engine FROM information_schema.tables +WHERE table_schema='mysql' AND table_name LIKE 'gtid_slave_pos%' + ORDER BY table_name; +table_name engine +gtid_slave_pos InnoDB +include/stop_slave.inc +SET sql_log_bin=0; +ALTER TABLE mysql.gtid_slave_pos ENGINE=MyISAM; +SET sql_log_bin=1; +connection server_1; +INSERT INTO t1 VALUES (5); +INSERT INTO t2 VALUES (2); +SELECT * FROM t1 ORDER BY a; +a +1 +2 +3 +4 +5 +SELECT * FROM t2 ORDER BY a; +a +1 +2 +include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +SELECT * FROM t1 ORDER BY a; +a +1 +2 +3 +4 +5 +SELECT * FROM t2 ORDER BY a; +a +1 +2 +*** Verify that mysql.gtid_slave_pos_InnoDB is auto-created *** +SELECT table_name, engine FROM information_schema.tables +WHERE table_schema='mysql' AND table_name LIKE 'gtid_slave_pos%' + ORDER BY table_name; +table_name engine +gtid_slave_pos MyISAM +gtid_slave_pos_InnoDB InnoDB +include/stop_slave.inc +SET sql_log_bin=0; +INSERT INTO mysql.gtid_slave_pos SELECT * FROM mysql.gtid_slave_pos_InnoDB; +DROP TABLE mysql.gtid_slave_pos_InnoDB; +SET sql_log_bin=1; +connection server_1; +INSERT INTO t1 VALUES (6); +INSERT INTO t2 VALUES (3); +SELECT * FROM t1 ORDER BY a; +a +1 +2 +3 +4 +5 +6 +SELECT * FROM t2 ORDER BY a; +a +1 +2 +3 +include/save_master_gtid.inc +*** Restart server without --gtid-pos-auto-engines *** +connection server_2; +include/sync_with_master_gtid.inc +SELECT * FROM t1 ORDER BY a; +a +1 +2 +3 +4 +5 +6 +SELECT * FROM t2 ORDER BY a; +a +1 +2 +3 +*** Verify that no mysql.gtid_slave_pos* table is auto-created *** +SELECT table_name, engine FROM information_schema.tables +WHERE table_schema='mysql' AND table_name LIKE 'gtid_slave_pos%' + ORDER BY table_name; +table_name engine +gtid_slave_pos MyISAM +SELECT domain_id, max(seq_no) FROM mysql.gtid_slave_pos GROUP BY domain_id; +domain_id max(seq_no) +0 11 +include/stop_slave.inc +SET GLOBAL gtid_pos_auto_engines="innodb"; +include/start_slave.inc +connection server_1; +INSERT INTO t1 VALUES (7); +INSERT INTO t2 VALUES (4); +SELECT * FROM t1 ORDER BY a; +a +1 +2 +3 +4 +5 +6 +7 +SELECT * FROM t2 ORDER BY a; +a +1 +2 +3 +4 +include/save_master_gtid.inc +connection server_2; +include/sync_with_master_gtid.inc +SELECT * FROM t1 ORDER BY a; +a +1 +2 +3 +4 +5 +6 +7 +SELECT * FROM t2 ORDER BY a; +a +1 +2 +3 +4 +*** Verify that mysql.gtid_slave_pos_InnoDB is auto-created *** +SELECT table_name, engine FROM information_schema.tables +WHERE table_schema='mysql' AND table_name LIKE 'gtid_slave_pos%' + ORDER BY table_name; +table_name engine +gtid_slave_pos MyISAM +gtid_slave_pos_InnoDB InnoDB +SELECT domain_id, max(seq_no) FROM mysql.gtid_slave_pos GROUP BY domain_id; +domain_id max(seq_no) +0 13 +include/stop_slave.inc +SET GLOBAL gtid_pos_auto_engines=""; +SET sql_log_bin=0; +DROP TABLE mysql.gtid_slave_pos_InnoDB; +SET sql_log_bin=1; +include/start_slave.inc +connection server_1; +DROP TABLE t1, t2; +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/r/show_status_stop_slave_race-7126.result b/mysql-test/suite/rpl/r/show_status_stop_slave_race-7126.result index 64219e3908d..999d9417b3f 100644 --- a/mysql-test/suite/rpl/r/show_status_stop_slave_race-7126.result +++ b/mysql-test/suite/rpl/r/show_status_stop_slave_race-7126.result @@ -3,6 +3,7 @@ include/master-slave.inc call mtr.add_suppression("Master is configured to log replication events"); connection slave; connection slave; +include/wait_for_slave_to_stop.inc start slave; connection master; include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_gtid_crash.test b/mysql-test/suite/rpl/t/rpl_gtid_crash.test index b81cbd38cd3..5cf28b6e49a 100644 --- a/mysql-test/suite/rpl/t/rpl_gtid_crash.test +++ b/mysql-test/suite/rpl/t/rpl_gtid_crash.test @@ -161,8 +161,8 @@ EOF --write_file $MYSQLTEST_VARDIR/tmp/mysqld.2.expect wait EOF -SET GLOBAL debug_dbug="+d,crash_commit_before"; START SLAVE; +SET GLOBAL debug_dbug="+d,crash_commit_before"; --connection server_1 INSERT INTO t1 VALUES (5); @@ -185,8 +185,8 @@ EOF --write_file $MYSQLTEST_VARDIR/tmp/mysqld.2.expect wait EOF -SET GLOBAL debug_dbug="+d,crash_commit_after"; START SLAVE; +SET GLOBAL debug_dbug="+d,crash_commit_after"; --connection server_1 INSERT INTO t1 VALUES (6); diff --git a/mysql-test/suite/rpl/t/rpl_gtid_errorhandling.test b/mysql-test/suite/rpl/t/rpl_gtid_errorhandling.test index 05da466597e..796f6894f19 100644 --- a/mysql-test/suite/rpl/t/rpl_gtid_errorhandling.test +++ b/mysql-test/suite/rpl/t/rpl_gtid_errorhandling.test @@ -17,7 +17,7 @@ INSERT INTO t1 VALUES (1); --connection slave CALL mtr.add_suppression("Slave: Failed to open mysql.gtid_slave_pos"); ---let $slave_sql_errno=1942 +--let $slave_sql_errno=1944 --source include/wait_for_slave_sql_error.inc --source include/stop_slave.inc @@ -25,19 +25,19 @@ ALTER TABLE mysql.gtid_slave_pos CHANGE seq_no seq_no BIGINT UNSIGNED NOT NULL; ALTER TABLE mysql.gtid_slave_pos DROP PRIMARY KEY; ALTER TABLE mysql.gtid_slave_pos ADD PRIMARY KEY (sub_id, domain_id); START SLAVE; ---let $slave_sql_errno=1942 +--let $slave_sql_errno=1944 --source include/wait_for_slave_sql_error.inc --source include/stop_slave.inc ALTER TABLE mysql.gtid_slave_pos DROP PRIMARY KEY; START SLAVE; ---let $slave_sql_errno=1942 +--let $slave_sql_errno=1944 --source include/wait_for_slave_sql_error.inc --source include/stop_slave.inc ALTER TABLE mysql.gtid_slave_pos ADD PRIMARY KEY (sub_id); START SLAVE; ---let $slave_sql_errno=1942 +--let $slave_sql_errno=1944 --source include/wait_for_slave_sql_error.inc --source include/stop_slave.inc diff --git a/mysql-test/suite/rpl/t/rpl_gtid_ignored.test b/mysql-test/suite/rpl/t/rpl_gtid_ignored.test index cb98be3c838..6e927bd5a77 100644 --- a/mysql-test/suite/rpl/t/rpl_gtid_ignored.test +++ b/mysql-test/suite/rpl/t/rpl_gtid_ignored.test @@ -129,6 +129,7 @@ SELECT * FROM t1 ORDER BY a; # Clean up. --connection server_1 DROP TABLE t1; +ALTER TABLE mysql.gtid_slave_pos ENGINE=MyISAM; SET GLOBAL gtid_strict_mode= @old_gtid_strict_mode; SET debug_sync = "reset"; diff --git a/mysql-test/suite/rpl/t/rpl_gtid_mdev4484.test b/mysql-test/suite/rpl/t/rpl_gtid_mdev4484.test index 43634ec1528..57f78530b01 100644 --- a/mysql-test/suite/rpl/t/rpl_gtid_mdev4484.test +++ b/mysql-test/suite/rpl/t/rpl_gtid_mdev4484.test @@ -2,6 +2,18 @@ --source include/have_innodb.inc --source include/have_debug.inc +--connection slave +--source include/stop_slave.inc +# Since we inject an error updating mysql.gtid_slave_pos, we will get different +# output depending on whether it is InnoDB or MyISAM (roll back or no roll +# back). So fix it to make sure we are consistent, in case an earlier test case +# left it as InnoDB. +SET sql_log_bin=0; +ALTER TABLE mysql.gtid_slave_pos ENGINE=MyISAM; +SET sql_log_bin=1; +--source include/start_slave.inc + +--connection master CREATE TABLE t1 (i int) ENGINE=InnoDB; --sync_slave_with_master @@ -20,10 +32,6 @@ SET @old_dbug= @@GLOBAL.debug_dbug; SET GLOBAL debug_dbug="+d,gtid_slave_pos_simulate_failed_delete"; SET sql_log_bin= 0; CALL mtr.add_suppression("Can't find file"); -# Since we inject an error updating mysql.gtid_slave_pos, we will get different -# output depending on whether it is InnoDB or MyISAM (roll back or no roll -# back). So fix it to make sure we are consistent. -ALTER TABLE mysql.gtid_slave_pos ENGINE=MyISAM; SET sql_log_bin= 1; --source include/start_slave.inc diff --git a/mysql-test/suite/rpl/t/rpl_gtid_stop_start.test b/mysql-test/suite/rpl/t/rpl_gtid_stop_start.test index 09b35011f1f..309debd87c5 100644 --- a/mysql-test/suite/rpl/t/rpl_gtid_stop_start.test +++ b/mysql-test/suite/rpl/t/rpl_gtid_stop_start.test @@ -232,6 +232,20 @@ EOF SET sql_log_bin= 0; ALTER TABLE mysql.gtid_slave_pos ENGINE=MyISAM; SET sql_log_bin= 1; +# Do a second restart to get the mysql.gtid_slave_pos table loaded with +# the right engine. +--write_file $MYSQLTEST_VARDIR/tmp/mysqld.2.expect +wait +EOF +--shutdown_server 30 +--source include/wait_until_disconnected.inc + +--append_file $MYSQLTEST_VARDIR/tmp/mysqld.2.expect +restart: +EOF +--enable_reconnect +--source include/wait_until_connected_again.inc + --source include/start_slave.inc --connection server_1 @@ -285,7 +299,7 @@ SELECT domain_id, COUNT(*) FROM mysql.gtid_slave_pos GROUP BY domain_id; --connection server_2 SET sql_log_bin=0; --let $old_pos= `SELECT @@GLOBAL.gtid_slave_pos` -RENAME TABLE mysql.gtid_slave_pos TO mysql.gtid_slave_pos_old; +RENAME TABLE mysql.gtid_slave_pos TO mysql.old_gtid_slave_pos; SET sql_log_bin=1; --write_file $MYSQLTEST_VARDIR/tmp/mysqld.2.expect @@ -313,7 +327,7 @@ SHOW WARNINGS; # Restore things. SET sql_log_bin=0; -RENAME TABLE mysql.gtid_slave_pos_old TO mysql.gtid_slave_pos; +RENAME TABLE mysql.old_gtid_slave_pos TO mysql.gtid_slave_pos; CALL mtr.add_suppression("Failed to load slave replication state from table mysql.gtid_slave_pos"); SET sql_log_bin=1; diff --git a/mysql-test/suite/rpl/t/rpl_gtid_until.test b/mysql-test/suite/rpl/t/rpl_gtid_until.test index 20d4510ccc8..aa05ecf79ab 100644 --- a/mysql-test/suite/rpl/t/rpl_gtid_until.test +++ b/mysql-test/suite/rpl/t/rpl_gtid_until.test @@ -19,6 +19,9 @@ delimiter ;| --connection server_2 --sync_with_master +# Restart SQL thread to pick up ALTER TABLE of mysql.gtid_slave_pos. +--source include/stop_slave.inc +--source include/start_slave.inc # Both replication threads must be stopped for UNTIL master_gtid_pos. --error ER_SLAVE_WAS_RUNNING diff --git a/mysql-test/suite/rpl/t/rpl_mdev10863.test b/mysql-test/suite/rpl/t/rpl_mdev10863.test index 796e770672d..81cdfd84dbe 100644 --- a/mysql-test/suite/rpl/t/rpl_mdev10863.test +++ b/mysql-test/suite/rpl/t/rpl_mdev10863.test @@ -99,6 +99,7 @@ SET GLOBAL max_relay_log_size= @old_max_relay; --source include/start_slave.inc --connection server_1 +ALTER TABLE mysql.gtid_slave_pos ENGINE=MyISAM; DROP TABLE t1; --source include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_mdev12179.test b/mysql-test/suite/rpl/t/rpl_mdev12179.test new file mode 100644 index 00000000000..a9113c91797 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_mdev12179.test @@ -0,0 +1,280 @@ +--source include/have_innodb.inc +--let $rpl_topology=1->2 +--source include/rpl_init.inc + +--connection server_2 +--error ER_SLAVE_MUST_STOP +SET GLOBAL gtid_pos_auto_engines="innodb"; +--source include/stop_slave.inc +CHANGE MASTER TO master_use_gtid=slave_pos; + +# Test the @@gtid_pos_auto_engines sysvar. +SELECT @@gtid_pos_auto_engines; +--error ER_INCORRECT_GLOBAL_LOCAL_VAR +SELECT @@SESSION.gtid_pos_auto_engines; +--error ER_WRONG_VALUE_FOR_VAR +SET GLOBAL gtid_pos_auto_engines= NULL; +SET GLOBAL gtid_pos_auto_engines="innodb"; +SELECT @@gtid_pos_auto_engines; +SET GLOBAL gtid_pos_auto_engines="myisam,innodb"; +SELECT @@gtid_pos_auto_engines; +SET GLOBAL gtid_pos_auto_engines="innodb,myisam"; +SELECT @@gtid_pos_auto_engines; +SET GLOBAL gtid_pos_auto_engines="innodb,innodb,myisam,innodb,myisam,myisam,innodb"; +SELECT @@gtid_pos_auto_engines; +SET GLOBAL gtid_pos_auto_engines=DEFAULT; +SELECT @@gtid_pos_auto_engines; +SET GLOBAL gtid_pos_auto_engines=""; +SELECT @@gtid_pos_auto_engines; + +--source include/start_slave.inc + +--connection server_1 +CREATE TABLE t1 (a INT PRIMARY KEY); +INSERT INTO t1 VALUES (1); +SELECT * FROM t1 ORDER BY a; +--save_master_pos + +--connection server_2 +--sync_with_master +SELECT * FROM t1 ORDER BY a; +--source include/stop_slave.inc +SET sql_log_bin=0; +# Reset storage engine for mysql.gtid_slave_pos in case an earlier test +# might have changed it to InnoDB. +ALTER TABLE mysql.gtid_slave_pos ENGINE=MyISAM; +CREATE TABLE mysql.gtid_slave_pos_innodb LIKE mysql.gtid_slave_pos; +ALTER TABLE mysql.gtid_slave_pos_innodb ENGINE=InnoDB; +INSERT INTO mysql.gtid_slave_pos_innodb SELECT * FROM mysql.gtid_slave_pos; +TRUNCATE mysql.gtid_slave_pos; +SET sql_log_bin=1; + +# Restart the slave mysqld server, and verify that the GTID position is +# read correctly from the new mysql.gtid_slave_pos_innodb table. + +--write_file $MYSQLTEST_VARDIR/tmp/mysqld.2.expect +wait +EOF +--shutdown_server 30 +--source include/wait_until_disconnected.inc + +--connection server_1 +INSERT INTO t1 VALUES (2); +INSERT INTO t1 VALUES (3); +SELECT * FROM t1 ORDER BY a; +--source include/save_master_gtid.inc + +# Let the slave mysqld server start again. +# As we are restarting, also take the opportunity to test --gtid-pos-auto-engines +--echo *** Restart server with --gtid-pos-auto-engines=innodb,myisam *** +--append_file $MYSQLTEST_VARDIR/tmp/mysqld.2.expect +restart: --skip-slave-start=0 --gtid-pos-auto-engines=innodb,myisam +EOF + +--connection server_2 +--enable_reconnect +--source include/wait_until_connected_again.inc + +--source include/sync_with_master_gtid.inc +SELECT * FROM t1 ORDER BY a; + +--echo *** Verify no new gtid_slave_pos* tables are created *** +SELECT table_name, engine FROM information_schema.tables + WHERE table_schema='mysql' AND table_name LIKE 'gtid_slave_pos%' + ORDER BY table_name; + +SELECT @@gtid_pos_auto_engines; +--source include/stop_slave.inc +SET sql_log_bin=0; +INSERT INTO mysql.gtid_slave_pos_innodb SELECT * FROM mysql.gtid_slave_pos; +DROP TABLE mysql.gtid_slave_pos; +RENAME TABLE mysql.gtid_slave_pos_innodb TO mysql.gtid_slave_pos; +SET sql_log_bin=1; + +--write_file $MYSQLTEST_VARDIR/tmp/mysqld.2.expect +wait +EOF +--shutdown_server 30 +--source include/wait_until_disconnected.inc + +--connection server_1 +CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=InnoDB; +INSERT INTO t1 VALUES (4); +INSERT INTO t2 VALUES (1); +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; +--source include/save_master_gtid.inc + +--echo *** Restart server with --gtid-pos-auto-engines=myisam,innodb *** +--append_file $MYSQLTEST_VARDIR/tmp/mysqld.2.expect +restart: --skip-slave-start=0 --gtid-pos-auto-engines=myisam,innodb +EOF + +--connection server_2 +--enable_reconnect +--source include/wait_until_connected_again.inc + +--source include/sync_with_master_gtid.inc +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; + +--echo *** Verify that no new gtid_slave_pos* tables are auto-created *** +SELECT table_name, engine FROM information_schema.tables + WHERE table_schema='mysql' AND table_name LIKE 'gtid_slave_pos%' + ORDER BY table_name; + + +--source include/stop_slave.inc +SET sql_log_bin=0; +ALTER TABLE mysql.gtid_slave_pos ENGINE=MyISAM; +SET sql_log_bin=1; + +--write_file $MYSQLTEST_VARDIR/tmp/mysqld.2.expect +wait +EOF +--shutdown_server 30 +--source include/wait_until_disconnected.inc + +--connection server_1 +INSERT INTO t1 VALUES (5); +INSERT INTO t2 VALUES (2); +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; +--source include/save_master_gtid.inc + +--append_file $MYSQLTEST_VARDIR/tmp/mysqld.2.expect +--echo *** Restart server with --gtid-pos-auto-engines=innodb *** +restart: --skip-slave-start=0 --gtid-pos-auto-engines=innodb +EOF + +--connection server_2 +--enable_reconnect +--source include/wait_until_connected_again.inc + +--source include/sync_with_master_gtid.inc +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; + +--echo *** Verify that mysql.gtid_slave_pos_InnoDB is auto-created *** +# Note, the create happens asynchronously, so wait for it. +let $wait_condition= + SELECT EXISTS (SELECT * FROM information_schema.tables + WHERE table_schema='mysql' AND table_name='gtid_slave_pos_InnoDB'); +--source include/wait_condition.inc +SELECT table_name, engine FROM information_schema.tables + WHERE table_schema='mysql' AND table_name LIKE 'gtid_slave_pos%' + ORDER BY table_name; + + +--source include/stop_slave.inc +SET sql_log_bin=0; +INSERT INTO mysql.gtid_slave_pos SELECT * FROM mysql.gtid_slave_pos_InnoDB; +DROP TABLE mysql.gtid_slave_pos_InnoDB; +SET sql_log_bin=1; + +--write_file $MYSQLTEST_VARDIR/tmp/mysqld.2.expect +wait +EOF +--shutdown_server 30 +--source include/wait_until_disconnected.inc + +--connection server_1 +INSERT INTO t1 VALUES (6); +INSERT INTO t2 VALUES (3); +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; +--source include/save_master_gtid.inc + +--echo *** Restart server without --gtid-pos-auto-engines *** +--append_file $MYSQLTEST_VARDIR/tmp/mysqld.2.expect +restart: --skip-slave-start=0 +EOF + +--connection server_2 +--enable_reconnect +--source include/wait_until_connected_again.inc + +--source include/sync_with_master_gtid.inc +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; + +--echo *** Verify that no mysql.gtid_slave_pos* table is auto-created *** +SELECT table_name, engine FROM information_schema.tables + WHERE table_schema='mysql' AND table_name LIKE 'gtid_slave_pos%' + ORDER BY table_name; +SELECT domain_id, max(seq_no) FROM mysql.gtid_slave_pos GROUP BY domain_id; + +--source include/stop_slave.inc +SET GLOBAL gtid_pos_auto_engines="innodb"; +--source include/start_slave.inc + +--connection server_1 +INSERT INTO t1 VALUES (7); +INSERT INTO t2 VALUES (4); +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; +--source include/save_master_gtid.inc + +--connection server_2 +--source include/sync_with_master_gtid.inc +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; + +--echo *** Verify that mysql.gtid_slave_pos_InnoDB is auto-created *** +let $wait_condition= + SELECT EXISTS (SELECT * FROM information_schema.tables + WHERE table_schema='mysql' AND table_name='gtid_slave_pos_InnoDB'); +--source include/wait_condition.inc +SELECT table_name, engine FROM information_schema.tables + WHERE table_schema='mysql' AND table_name LIKE 'gtid_slave_pos%' + ORDER BY table_name; +SELECT domain_id, max(seq_no) FROM mysql.gtid_slave_pos GROUP BY domain_id; + +# Check that the auto-created InnoDB table starts being used without +# needing slave restart. The auto-create happens asynchronously, so it +# is non-deterministic when it will start being used. But we can wait +# for it to happen. + +--let $count=300 +--let $done=0 +--let $old_silent= $keep_include_silent +--let $keep_include_silent= 1 +--disable_query_log +while (!$done) +{ + --connection server_1 + INSERT INTO t2(a) SELECT 1+MAX(a) FROM t2; + --source include/save_master_gtid.inc + + --connection server_2 + --source include/sync_with_master_gtid.inc + --let $done=`SELECT COUNT(*) > 0 FROM mysql.gtid_slave_pos_InnoDB` + if (!$done) + { + dec $count; + if (!$count) + { + SELECT * FROM mysql.gtid_slave_pos_InnoDB; + --die Timeout waiting for mysql.gtid_slave_pos_InnoDB to be used + } + real_sleep 0.1; + } +} +--enable_query_log +--let $keep_include_silent=$old_silent +# Note that at this point, the contents of table t2, as well as the GTID +# position, is non-deterministic. + + +#--connection server_2 +--source include/stop_slave.inc +SET GLOBAL gtid_pos_auto_engines=""; +SET sql_log_bin=0; +DROP TABLE mysql.gtid_slave_pos_InnoDB; +SET sql_log_bin=1; +--source include/start_slave.inc + +--connection server_1 +DROP TABLE t1, t2; + +--source include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/show_status_stop_slave_race-7126.test b/mysql-test/suite/rpl/t/show_status_stop_slave_race-7126.test index 88d7076b1e4..06a9e8ad75b 100644 --- a/mysql-test/suite/rpl/t/show_status_stop_slave_race-7126.test +++ b/mysql-test/suite/rpl/t/show_status_stop_slave_race-7126.test @@ -15,6 +15,7 @@ call mtr.add_suppression("Master is configured to log replication events"); # All done. --connection slave +--source include/wait_for_slave_to_stop.inc start slave; --connection master diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result index 13072f5afe1..46ffa2f08eb 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result @@ -1115,6 +1115,20 @@ NUMERIC_BLOCK_SIZE NULL ENUM_VALUE_LIST OFF,ON READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL +VARIABLE_NAME GTID_POS_AUTO_ENGINES +SESSION_VALUE NULL +GLOBAL_VALUE +GLOBAL_VALUE_ORIGIN COMPILE-TIME +DEFAULT_VALUE +VARIABLE_SCOPE GLOBAL +VARIABLE_TYPE VARCHAR +VARIABLE_COMMENT List of engines for which to automatically create a mysql.gtid_slave_pos_ENGINE table, if a transaction using that engine is replicated. This can be used to avoid introducing cross-engine transactions, if engines are used different from that used by table mysql.gtid_slave_pos +NUMERIC_MIN_VALUE NULL +NUMERIC_MAX_VALUE NULL +NUMERIC_BLOCK_SIZE NULL +ENUM_VALUE_LIST NULL +READ_ONLY NO +COMMAND_LINE_ARGUMENT NULL VARIABLE_NAME GTID_SEQ_NO SESSION_VALUE 0 GLOBAL_VALUE NULL diff --git a/scripts/mysql_system_tables.sql b/scripts/mysql_system_tables.sql index 7b614163f46..60c1ac97955 100644 --- a/scripts/mysql_system_tables.sql +++ b/scripts/mysql_system_tables.sql @@ -224,6 +224,8 @@ CREATE TABLE IF NOT EXISTS column_stats (db_name varchar(64) NOT NULL, table_nam CREATE TABLE IF NOT EXISTS index_stats (db_name varchar(64) NOT NULL, table_name varchar(64) NOT NULL, index_name varchar(64) NOT NULL, prefix_arity int(11) unsigned NOT NULL, avg_frequency decimal(12,4) DEFAULT NULL, PRIMARY KEY (db_name,table_name,index_name,prefix_arity) ) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_bin comment='Statistics on Indexes'; +-- Note: This definition must be kept in sync with the one used in +-- build_gtid_pos_create_query() in sql/slave.cc SET @cmd= "CREATE TABLE IF NOT EXISTS gtid_slave_pos ( domain_id INT UNSIGNED NOT NULL, sub_id BIGINT UNSIGNED NOT NULL, diff --git a/sql/handler.cc b/sql/handler.cc index c74675ed113..7a1cec3b6f2 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -1546,6 +1546,7 @@ static int commit_one_phase_2(THD *thd, bool all, THD_TRANS *trans, bool is_real_trans) { int error= 0; + uint count= 0; Ha_trx_info *ha_info= trans->ha_list, *ha_info_next; DBUG_ENTER("commit_one_phase_2"); if (is_real_trans) @@ -1563,6 +1564,8 @@ commit_one_phase_2(THD *thd, bool all, THD_TRANS *trans, bool is_real_trans) } /* Should this be done only if is_real_trans is set ? */ status_var_increment(thd->status_var.ha_commit_count); + if (is_real_trans && ht != binlog_hton && ha_info->is_trx_read_write()) + ++count; ha_info_next= ha_info->next(); ha_info->reset(); /* keep it conveniently zero-filled */ } @@ -1581,6 +1584,8 @@ commit_one_phase_2(THD *thd, bool all, THD_TRANS *trans, bool is_real_trans) { thd->has_waiter= false; thd->transaction.cleanup(); + if (count >= 2) + statistic_increment(transactions_multi_engine, LOCK_status); } DBUG_RETURN(error); diff --git a/sql/log.h b/sql/log.h index eaa63d4072d..f57693f9d2a 100644 --- a/sql/log.h +++ b/sql/log.h @@ -1096,6 +1096,7 @@ void make_default_log_name(char **out, const char* log_ext, bool once); void binlog_reset_cache(THD *thd); extern MYSQL_PLUGIN_IMPORT MYSQL_BIN_LOG mysql_bin_log; +extern handlerton *binlog_hton; extern LOGGER logger; extern const char *log_bin_index; diff --git a/sql/log_event.cc b/sql/log_event.cc index 44a2bf782fd..6aaa9a657fa 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -5027,6 +5027,7 @@ int Query_log_event::do_apply_event(rpl_group_info *rgi, int expected_error,actual_error= 0; Schema_specification_st db_options; uint64 sub_id= 0; + void *hton= NULL; rpl_gtid gtid; Relay_log_info const *rli= rgi->rli; Rpl_filter *rpl_filter= rli->mi->rpl_filter; @@ -5197,7 +5198,7 @@ int Query_log_event::do_apply_event(rpl_group_info *rgi, gtid= rgi->current_gtid; if (rpl_global_gtid_slave_state->record_gtid(thd, >id, sub_id, - true, false)) + true, false, &hton)) { int errcode= thd->get_stmt_da()->sql_errno(); if (!is_parallel_retry_error(rgi, errcode)) @@ -5418,7 +5419,7 @@ compare_errors: end: if (sub_id && !thd->is_slave_error) - rpl_global_gtid_slave_state->update_state_hash(sub_id, >id, rgi); + rpl_global_gtid_slave_state->update_state_hash(sub_id, >id, hton, rgi); /* Probably we have set thd->query, thd->db, thd->catalog to point to places @@ -7901,15 +7902,17 @@ Gtid_list_log_event::do_apply_event(rpl_group_info *rgi) int ret; if (gl_flags & FLAG_IGN_GTIDS) { + void *hton= NULL; uint32 i; + for (i= 0; i < count; ++i) { if ((ret= rpl_global_gtid_slave_state->record_gtid(thd, &list[i], - sub_id_list[i], - false, false))) + sub_id_list[i], + false, false, &hton))) return ret; rpl_global_gtid_slave_state->update_state_hash(sub_id_list[i], &list[i], - NULL); + hton, NULL); } } ret= Log_event::do_apply_event(rgi); @@ -8390,6 +8393,7 @@ int Xid_log_event::do_apply_event(rpl_group_info *rgi) rpl_gtid gtid; uint64 sub_id= 0; Relay_log_info const *rli= rgi->rli; + void *hton= NULL; /* XID_EVENT works like a COMMIT statement. And it also updates the @@ -8414,7 +8418,7 @@ int Xid_log_event::do_apply_event(rpl_group_info *rgi) gtid= rgi->current_gtid; err= rpl_global_gtid_slave_state->record_gtid(thd, >id, sub_id, true, - false); + false, &hton); if (err) { int ec= thd->get_stmt_da()->sql_errno(); @@ -8447,7 +8451,7 @@ int Xid_log_event::do_apply_event(rpl_group_info *rgi) thd->mdl_context.release_transactional_locks(); if (!res && sub_id) - rpl_global_gtid_slave_state->update_state_hash(sub_id, >id, rgi); + rpl_global_gtid_slave_state->update_state_hash(sub_id, >id, hton, rgi); /* Increment the global status commit count variable diff --git a/sql/mysqld.cc b/sql/mysqld.cc index b8e0b44843c..c66f1c45c43 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -372,6 +372,8 @@ char *my_bind_addr_str; static char *default_collation_name; char *default_storage_engine, *default_tmp_storage_engine; char *enforced_storage_engine=NULL; +char *gtid_pos_auto_engines; +plugin_ref *opt_gtid_pos_auto_plugins; static char compiled_default_collation_name[]= MYSQL_DEFAULT_COLLATION_NAME; static I_List<CONNECT> thread_cache; static bool binlog_format_used= false; @@ -523,6 +525,9 @@ ulong max_connections, max_connect_errors; ulong extra_max_connections; uint max_digest_length= 0; ulong slave_retried_transactions; +ulong transactions_multi_engine; +ulong rpl_transactions_multi_engine; +ulong transactions_gtid_foreign_engine; ulonglong slave_skipped_errors; ulong feature_files_opened_with_delayed_keys= 0, feature_check_constraint= 0; ulonglong denied_connections; @@ -4258,6 +4263,7 @@ static int init_common_variables() default_storage_engine= const_cast<char *>("MyISAM"); #endif default_tmp_storage_engine= NULL; + gtid_pos_auto_engines= const_cast<char *>(""); /* Add server status variables to the dynamic list of @@ -4937,6 +4943,34 @@ static int init_default_storage_engine_impl(const char *opt_name, return 0; } + +static int +init_gtid_pos_auto_engines(void) +{ + plugin_ref *plugins; + + /* + For the command-line option --gtid_pos_auto_engines, we allow (and ignore) + engines that are unknown. This is convenient, since it allows to set + default auto-create engines that might not be used by particular users. + The option sets a list of storage engines that will have gtid position + table auto-created for them if needed. And if the engine is not available, + then it will certainly not be needed. + */ + if (gtid_pos_auto_engines) + plugins= resolve_engine_list(NULL, gtid_pos_auto_engines, + strlen(gtid_pos_auto_engines), false, false); + else + plugins= resolve_engine_list(NULL, "", 0, false, false); + if (!plugins) + return 1; + mysql_mutex_lock(&LOCK_global_system_variables); + opt_gtid_pos_auto_plugins= plugins; + mysql_mutex_unlock(&LOCK_global_system_variables); + return 0; +} + + static int init_server_components() { DBUG_ENTER("init_server_components"); @@ -5374,6 +5408,9 @@ static int init_server_components() if (init_default_storage_engine(enforced_storage_engine, enforced_table_plugin)) unireg_abort(1); + if (init_gtid_pos_auto_engines()) + unireg_abort(1); + #ifdef USE_ARIA_FOR_TMP_TABLES if (!ha_storage_engine_is_enabled(maria_hton) && !opt_bootstrap) { @@ -7367,6 +7404,14 @@ struct my_option my_long_options[]= "Set up signals usable for debugging. Deprecated, use --debug-gdb instead.", &opt_debugging, &opt_debugging, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"gtid-pos-auto-engines", 0, + "List of engines for which to automatically create a " + "mysql.gtid_slave_pos_ENGINE table, if a transaction using that engine " + "is replicated. This can be used to avoid introducing cross-engine " + "transactions, if engines are used different from that used by table " + "mysql.gtid_slave_pos", + >id_pos_auto_engines, 0, 0, GET_STR, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0 }, #ifdef HAVE_LARGE_PAGE_OPTION {"super-large-pages", 0, "Enable support for super large pages.", &opt_super_large_pages, &opt_super_large_pages, 0, @@ -7764,7 +7809,7 @@ static int show_slaves_running(THD *thd, SHOW_VAR *var, char *buff) var->type= SHOW_LONGLONG; var->value= buff; - *((longlong *)buff)= any_slave_sql_running(); + *((longlong *)buff)= any_slave_sql_running(false); return 0; } @@ -8539,6 +8584,9 @@ SHOW_VAR status_vars[]= { {"Threads_connected", (char*) &connection_count, SHOW_INT}, {"Threads_created", (char*) &thread_created, SHOW_LONG_NOFLUSH}, {"Threads_running", (char*) &thread_running, SHOW_INT}, + {"Transactions_multi_engine", (char*) &transactions_multi_engine, SHOW_LONG}, + {"Rpl_transactions_multi_engine", (char*) &rpl_transactions_multi_engine, SHOW_LONG}, + {"Transactions_gtid_foreign_engine", (char*) &transactions_gtid_foreign_engine, SHOW_LONG}, {"Update_scan", (char*) offsetof(STATUS_VAR, update_scan_count), SHOW_LONG_STATUS}, {"Uptime", (char*) &show_starttime, SHOW_SIMPLE_FUNC}, #ifdef ENABLED_PROFILING @@ -8782,6 +8830,9 @@ static int mysql_init_variables(void) report_user= report_password = report_host= 0; /* TO BE DELETED */ opt_relay_logname= opt_relaylog_index_name= 0; slave_retried_transactions= 0; + transactions_multi_engine= 0; + rpl_transactions_multi_engine= 0; + transactions_gtid_foreign_engine= 0; log_bin_basename= NULL; log_bin_index= NULL; diff --git a/sql/mysqld.h b/sql/mysqld.h index 38e42dd61f1..6cf5a3776a0 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -19,6 +19,7 @@ #include <my_global.h> /* MYSQL_PLUGIN_IMPORT, FN_REFLEN, FN_EXTLEN */ #include "sql_basic_types.h" /* query_id_t */ +#include "sql_plugin.h" #include "sql_bitmap.h" /* Bitmap */ #include "my_decimal.h" /* my_decimal */ #include "mysql_com.h" /* SERVER_VERSION_LENGTH */ @@ -130,6 +131,9 @@ extern my_bool opt_safe_show_db, opt_local_infile, opt_myisam_use_mmap; extern my_bool opt_slave_compressed_protocol, use_temp_pool; extern ulong slave_exec_mode_options, slave_ddl_exec_mode_options; extern ulong slave_retried_transactions; +extern ulong transactions_multi_engine; +extern ulong rpl_transactions_multi_engine; +extern ulong transactions_gtid_foreign_engine; extern ulong slave_run_triggers_for_rbr; extern ulonglong slave_type_conversions_options; extern my_bool read_only, opt_readonly; @@ -153,6 +157,8 @@ extern char *default_tz_name; extern Time_zone *default_tz; extern char *default_storage_engine, *default_tmp_storage_engine; extern char *enforced_storage_engine; +extern char *gtid_pos_auto_engines; +extern plugin_ref *opt_gtid_pos_auto_plugins; extern bool opt_endinfo, using_udf_functions; extern my_bool locked_in_memory; extern bool opt_using_transactions; diff --git a/sql/rpl_gtid.cc b/sql/rpl_gtid.cc index c385434e41e..fb57babd50f 100644 --- a/sql/rpl_gtid.cc +++ b/sql/rpl_gtid.cc @@ -26,6 +26,7 @@ #include "key.h" #include "rpl_gtid.h" #include "rpl_rli.h" +#include "slave.h" const LEX_STRING rpl_gtid_slave_state_table_name= @@ -33,7 +34,7 @@ const LEX_STRING rpl_gtid_slave_state_table_name= void -rpl_slave_state::update_state_hash(uint64 sub_id, rpl_gtid *gtid, +rpl_slave_state::update_state_hash(uint64 sub_id, rpl_gtid *gtid, void *hton, rpl_group_info *rgi) { int err; @@ -45,7 +46,7 @@ rpl_slave_state::update_state_hash(uint64 sub_id, rpl_gtid *gtid, it is even committed. */ mysql_mutex_lock(&LOCK_slave_state); - err= update(gtid->domain_id, gtid->server_id, sub_id, gtid->seq_no, rgi); + err= update(gtid->domain_id, gtid->server_id, sub_id, gtid->seq_no, hton, rgi); mysql_mutex_unlock(&LOCK_slave_state); if (err) { @@ -74,12 +75,14 @@ rpl_slave_state::record_and_update_gtid(THD *thd, rpl_group_info *rgi) if (rgi->gtid_pending) { uint64 sub_id= rgi->gtid_sub_id; + void *hton= NULL; + rgi->gtid_pending= false; if (rgi->gtid_ignore_duplicate_state!=rpl_group_info::GTID_DUPLICATE_IGNORE) { - if (record_gtid(thd, &rgi->current_gtid, sub_id, false, false)) + if (record_gtid(thd, &rgi->current_gtid, sub_id, false, false, &hton)) DBUG_RETURN(1); - update_state_hash(sub_id, &rgi->current_gtid, rgi); + update_state_hash(sub_id, &rgi->current_gtid, hton, rgi); } rgi->gtid_ignore_duplicate_state= rpl_group_info::GTID_DUPLICATE_NULL; } @@ -243,7 +246,7 @@ rpl_slave_state_free_element(void *arg) rpl_slave_state::rpl_slave_state() - : last_sub_id(0), loaded(false) + : last_sub_id(0), gtid_pos_tables(0), loaded(false) { mysql_mutex_init(key_LOCK_slave_state, &LOCK_slave_state, MY_MUTEX_INIT_SLOW); @@ -255,6 +258,7 @@ rpl_slave_state::rpl_slave_state() rpl_slave_state::~rpl_slave_state() { + free_gtid_pos_tables((struct gtid_pos_table *)gtid_pos_tables); truncate_hash(); my_hash_free(&hash); delete_dynamic(>id_sort_array); @@ -286,11 +290,12 @@ rpl_slave_state::truncate_hash() int rpl_slave_state::update(uint32 domain_id, uint32 server_id, uint64 sub_id, - uint64 seq_no, rpl_group_info *rgi) + uint64 seq_no, void *hton, rpl_group_info *rgi) { element *elem= NULL; list_element *list_elem= NULL; + DBUG_ASSERT(hton || !loaded); if (!(elem= get_element(domain_id))) return 1; @@ -335,6 +340,7 @@ rpl_slave_state::update(uint32 domain_id, uint32 server_id, uint64 sub_id, list_elem->server_id= server_id; list_elem->sub_id= sub_id; list_elem->seq_no= seq_no; + list_elem->hton= hton; elem->add(list_elem); if (last_sub_id < sub_id) @@ -466,6 +472,94 @@ gtid_check_rpl_slave_state_table(TABLE *table) /* + Attempt to find a mysql.gtid_slave_posXXX table that has a storage engine + that is already in use by the current transaction, if any. +*/ +void +rpl_slave_state::select_gtid_pos_table(THD *thd, LEX_STRING *out_tablename) +{ + struct gtid_pos_table *list, *table_entry, *default_entry; + + /* + See comments on rpl_slave_state::gtid_pos_tables for rules around proper + access to the list. + */ + list= (struct gtid_pos_table *) + my_atomic_loadptr_explicit(>id_pos_tables, MY_MEMORY_ORDER_ACQUIRE); + + Ha_trx_info *ha_info; + uint count = 0; + for (ha_info= thd->transaction.all.ha_list; ha_info; ha_info= ha_info->next()) + { + void *trx_hton= ha_info->ht(); + table_entry= list; + + if (!ha_info->is_trx_read_write() || trx_hton == binlog_hton) + continue; + while (table_entry) + { + if (table_entry->table_hton == trx_hton) + { + if (likely(table_entry->state == GTID_POS_AVAILABLE)) + { + *out_tablename= table_entry->table_name; + /* + Check if this is a cross-engine transaction, so we can correctly + maintain the rpl_transactions_multi_engine status variable. + */ + if (count >= 1) + statistic_increment(rpl_transactions_multi_engine, LOCK_status); + else + { + for (;;) + { + ha_info= ha_info->next(); + if (!ha_info) + break; + if (ha_info->is_trx_read_write() && ha_info->ht() != binlog_hton) + { + statistic_increment(rpl_transactions_multi_engine, LOCK_status); + break; + } + } + } + return; + } + /* + This engine is marked to automatically create the table. + We cannot easily do this here (possibly in the middle of a + transaction). But we can request the slave background thread + to create it, and in a short while it should become available + for following transactions. + */ +#ifdef HAVE_REPLICATION + slave_background_gtid_pos_create_request(table_entry); +#endif + break; + } + table_entry= table_entry->next; + } + ++count; + } + /* + If we cannot find any table whose engine matches an engine that is + already active in the transaction, or if there is no current transaction + engines available, we return the default gtid_slave_pos table. + */ + default_entry= (struct gtid_pos_table *) + my_atomic_loadptr_explicit(&default_gtid_pos_table, MY_MEMORY_ORDER_ACQUIRE); + *out_tablename= default_entry->table_name; + /* Record in status that we failed to find a suitable gtid_pos table. */ + if (count > 0) + { + statistic_increment(transactions_gtid_foreign_engine, LOCK_status); + if (count > 1) + statistic_increment(rpl_transactions_multi_engine, LOCK_status); + } +} + + +/* Write a gtid to the replication slave state table. Do it as part of the transaction, to get slave crash safety, or as a separate @@ -481,19 +575,24 @@ gtid_check_rpl_slave_state_table(TABLE *table) */ int rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id, - bool in_transaction, bool in_statement) + bool in_transaction, bool in_statement, + void **out_hton) { TABLE_LIST tlist; int err= 0; bool table_opened= false; TABLE *table; - list_element *elist= 0, *next; + list_element *delete_list= 0, *next, *cur, **next_ptr_ptr, **best_ptr_ptr; + uint64_t best_sub_id; element *elem; ulonglong thd_saved_option= thd->variables.option_bits; Query_tables_list lex_backup; wait_for_commit* suspended_wfc; + void *hton= NULL; + LEX_STRING gtid_pos_table_name; DBUG_ENTER("record_gtid"); + *out_hton= NULL; if (unlikely(!loaded)) { /* @@ -508,6 +607,7 @@ rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id, if (!in_statement) thd->reset_for_next_command(); + select_gtid_pos_table(thd, >id_pos_table_name); DBUG_EXECUTE_IF("gtid_inject_record_gtid", { @@ -538,14 +638,13 @@ rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id, */ suspended_wfc= thd->suspend_subsequent_commits(); thd->lex->reset_n_backup_query_tables_list(&lex_backup); - tlist.init_one_table(STRING_WITH_LEN("mysql"), - rpl_gtid_slave_state_table_name.str, - rpl_gtid_slave_state_table_name.length, - NULL, TL_WRITE); + tlist.init_one_table(STRING_WITH_LEN("mysql"), gtid_pos_table_name.str, + gtid_pos_table_name.length, NULL, TL_WRITE); if ((err= open_and_lock_tables(thd, &tlist, FALSE, 0))) goto end; table_opened= true; table= tlist.table; + hton= table->s->db_type(); if ((err= gtid_check_rpl_slave_state_table(table))) goto end; @@ -581,6 +680,7 @@ rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id, table->file->print_error(err, MYF(0)); goto end; } + *out_hton= hton; if(opt_bin_log && (err= mysql_bin_log.bump_seq_no_counter_if_needed(gtid->domain_id, @@ -598,36 +698,62 @@ rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id, err= 1; goto end; } - if ((elist= elem->grab_list()) != NULL) + + /* Now pull out all GTIDs that were recorded in this engine. */ + delete_list = NULL; + next_ptr_ptr= &elem->list; + cur= elem->list; + best_sub_id= 0; + best_ptr_ptr= NULL; + while (cur) { - /* Delete any old stuff, but keep around the most recent one. */ - list_element *cur= elist; - uint64 best_sub_id= cur->sub_id; - list_element **best_ptr_ptr= &elist; - while ((next= cur->next)) + list_element *next= cur->next; + if (cur->hton == hton) { - if (next->sub_id > best_sub_id) + /* Belongs to same engine, so move it to the delete list. */ + cur->next= delete_list; + delete_list= cur; + if (cur->sub_id > best_sub_id) { - best_sub_id= next->sub_id; + best_sub_id= cur->sub_id; + best_ptr_ptr= &delete_list; + } + else if (best_ptr_ptr == &delete_list) best_ptr_ptr= &cur->next; + } + else + { + /* Another engine, leave it in the list. */ + if (cur->sub_id > best_sub_id) + { + best_sub_id= cur->sub_id; + /* Current best is not on the delete list. */ + best_ptr_ptr= NULL; } - cur= next; + *next_ptr_ptr= cur; + next_ptr_ptr= &cur->next; } - /* - Delete the highest sub_id element from the old list, and put it back as - the single-element new list. - */ + cur= next; + } + *next_ptr_ptr= NULL; + /* + If the highest sub_id element is on the delete list, put it back on the + original list, to preserve the highest sub_id element in the table for + GTID position recovery. + */ + if (best_ptr_ptr) + { cur= *best_ptr_ptr; *best_ptr_ptr= cur->next; - cur->next= NULL; + cur->next= elem->list; elem->list= cur; } mysql_mutex_unlock(&LOCK_slave_state); - if (!elist) + if (!delete_list) goto end; - /* Now delete any already committed rows. */ + /* Now delete any already committed GTIDs. */ bitmap_set_bit(table->read_set, table->field[0]->field_index); bitmap_set_bit(table->read_set, table->field[1]->field_index); @@ -636,7 +762,7 @@ rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id, table->file->print_error(err, MYF(0)); goto end; } - while (elist) + while (delete_list) { uchar key_buffer[4+8]; @@ -646,9 +772,9 @@ rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id, /* `break' does not work inside DBUG_EXECUTE_IF */ goto dbug_break; }); - next= elist->next; + next= delete_list->next; - table->field[1]->store(elist->sub_id, true); + table->field[1]->store(delete_list->sub_id, true); /* domain_id is already set in table->record[0] from write_row() above. */ key_copy(key_buffer, table->record[0], &table->key_info[0], 0, false); if (table->file->ha_index_read_map(table->record[1], key_buffer, @@ -662,8 +788,8 @@ rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id, not want to endlessly error on the same element in case of table corruption or such. */ - my_free(elist); - elist= next; + my_free(delete_list); + delete_list= next; if (err) break; } @@ -681,13 +807,13 @@ end: if (err || (err= ha_commit_trans(thd, FALSE))) { /* - If error, we need to put any remaining elist back into the HASH so we - can do another delete attempt later. + If error, we need to put any remaining delete_list back into the HASH + so we can do another delete attempt later. */ - if (elist) + if (delete_list) { mysql_mutex_lock(&LOCK_slave_state); - put_back_list(gtid->domain_id, elist); + put_back_list(gtid->domain_id, delete_list); mysql_mutex_unlock(&LOCK_slave_state); } @@ -1077,11 +1203,12 @@ rpl_slave_state::load(THD *thd, const char *state_from_master, size_t len, { rpl_gtid gtid; uint64 sub_id; + void *hton= NULL; if (gtid_parser_helper(&state_from_master, end, >id) || !(sub_id= next_sub_id(gtid.domain_id)) || - record_gtid(thd, >id, sub_id, false, in_statement) || - update(gtid.domain_id, gtid.server_id, sub_id, gtid.seq_no, NULL)) + record_gtid(thd, >id, sub_id, false, in_statement, &hton) || + update(gtid.domain_id, gtid.server_id, sub_id, gtid.seq_no, hton, NULL)) return 1; if (state_from_master == end) break; @@ -1115,6 +1242,75 @@ rpl_slave_state::is_empty() } +void +rpl_slave_state::free_gtid_pos_tables(struct rpl_slave_state::gtid_pos_table *list) +{ + struct gtid_pos_table *cur, *next; + + cur= list; + while (cur) + { + next= cur->next; + my_free(cur); + cur= next; + } +} + + +/* + Replace the list of available mysql.gtid_slave_posXXX tables with a new list. + The caller must be holding LOCK_slave_state. Additionally, this function + must only be called while all SQL threads are stopped. +*/ +void +rpl_slave_state::set_gtid_pos_tables_list(rpl_slave_state::gtid_pos_table *new_list, + rpl_slave_state::gtid_pos_table *default_entry) +{ + gtid_pos_table *old_list; + + mysql_mutex_assert_owner(&LOCK_slave_state); + old_list= (struct gtid_pos_table *)gtid_pos_tables; + my_atomic_storeptr_explicit(>id_pos_tables, new_list, MY_MEMORY_ORDER_RELEASE); + my_atomic_storeptr_explicit(&default_gtid_pos_table, default_entry, + MY_MEMORY_ORDER_RELEASE); + free_gtid_pos_tables(old_list); +} + + +void +rpl_slave_state::add_gtid_pos_table(rpl_slave_state::gtid_pos_table *entry) +{ + mysql_mutex_assert_owner(&LOCK_slave_state); + entry->next= (struct gtid_pos_table *)gtid_pos_tables; + my_atomic_storeptr_explicit(>id_pos_tables, entry, MY_MEMORY_ORDER_RELEASE); +} + + +struct rpl_slave_state::gtid_pos_table * +rpl_slave_state::alloc_gtid_pos_table(LEX_STRING *table_name, void *hton, + rpl_slave_state::gtid_pos_table_state state) +{ + struct gtid_pos_table *p; + char *allocated_str; + + if (!my_multi_malloc(MYF(MY_WME), + &p, sizeof(*p), + &allocated_str, table_name->length+1, + NULL)) + { + my_error(ER_OUTOFMEMORY, MYF(0), (int)(sizeof(*p) + table_name->length+1)); + return NULL; + } + memcpy(allocated_str, table_name->str, table_name->length+1); // Also copy '\0' + p->next = NULL; + p->table_hton= hton; + p->table_name.str= allocated_str; + p->table_name.length= table_name->length; + p->state= state; + return p; +} + + void rpl_binlog_state::init() { my_hash_init(&hash, &my_charset_bin, 32, offsetof(element, domain_id), diff --git a/sql/rpl_gtid.h b/sql/rpl_gtid.h index 5dfac7a3c6f..4a7661e1c52 100644 --- a/sql/rpl_gtid.h +++ b/sql/rpl_gtid.h @@ -112,6 +112,12 @@ struct rpl_slave_state uint64 sub_id; uint64 seq_no; uint32 server_id; + /* + hton of mysql.gtid_slave_pos* table used to record this GTID. + Can be NULL if the gtid table failed to load (eg. missing + mysql.gtid_slave_pos table following an upgrade). + */ + void *hton; }; /* Elements in the HASH that hold the state for one domain_id. */ @@ -155,6 +161,26 @@ struct rpl_slave_state } }; + /* Descriptor for mysql.gtid_slave_posXXX table in specific engine. */ + enum gtid_pos_table_state { + GTID_POS_AUTO_CREATE, + GTID_POS_CREATE_REQUESTED, + GTID_POS_CREATE_IN_PROGRESS, + GTID_POS_AVAILABLE + }; + struct gtid_pos_table { + struct gtid_pos_table *next; + /* + Use a void * here, rather than handlerton *, to make explicit that we + are not using the value to access any functionality in the engine. It + is just used as an opaque value to identify which engine we are using + for each GTID row. + */ + void *table_hton; + LEX_STRING table_name; + uint8 state; + }; + /* Mapping from domain_id to its element. */ HASH hash; /* Mutex protecting access to the state. */ @@ -163,6 +189,30 @@ struct rpl_slave_state DYNAMIC_ARRAY gtid_sort_array; uint64 last_sub_id; + /* + List of tables available for durably storing the slave GTID position. + + Accesses to this table is protected by LOCK_slave_state. However for + efficiency, there is also a provision for read access to it from a running + slave without lock. + + An element can be added at the head of a list by storing the new + gtid_pos_tables pointer atomically with release semantics, to ensure that + the next pointer of the new element is visible to readers of the new list. + Other changes (like deleting or replacing elements) must happen only while + all SQL driver threads are stopped. LOCK_slave_state must be held in any + case. + + The list can be read without lock by an SQL driver thread or worker thread + by reading the gtid_pos_tables pointer atomically with acquire semantics, + to ensure that it will see the correct next pointer of a new head element. + + The type is struct gtid_pos_table *, but needs to be void * to allow using + my_atomic operations without violating C strict aliasing semantics. + */ + void * volatile gtid_pos_tables; + /* The default entry in gtid_pos_tables, mysql.gtid_slave_pos. */ + void * volatile default_gtid_pos_table; bool loaded; rpl_slave_state(); @@ -171,10 +221,11 @@ struct rpl_slave_state void truncate_hash(); ulong count() const { return hash.records; } int update(uint32 domain_id, uint32 server_id, uint64 sub_id, - uint64 seq_no, rpl_group_info *rgi); + uint64 seq_no, void *hton, rpl_group_info *rgi); int truncate_state_table(THD *thd); + void select_gtid_pos_table(THD *thd, LEX_STRING *out_tablename); int record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id, - bool in_transaction, bool in_statement); + bool in_transaction, bool in_statement, void **out_hton); uint64 next_sub_id(uint32 domain_id); int iterate(int (*cb)(rpl_gtid *, void *), void *data, rpl_gtid *extra_gtids, uint32 num_extra, @@ -188,10 +239,17 @@ struct rpl_slave_state element *get_element(uint32 domain_id); int put_back_list(uint32 domain_id, list_element *list); - void update_state_hash(uint64 sub_id, rpl_gtid *gtid, rpl_group_info *rgi); + void update_state_hash(uint64 sub_id, rpl_gtid *gtid, void *hton, + rpl_group_info *rgi); int record_and_update_gtid(THD *thd, struct rpl_group_info *rgi); int check_duplicate_gtid(rpl_gtid *gtid, rpl_group_info *rgi); void release_domain_owner(rpl_group_info *rgi); + void set_gtid_pos_tables_list(gtid_pos_table *new_list, + gtid_pos_table *default_entry); + void add_gtid_pos_table(gtid_pos_table *entry); + struct gtid_pos_table *alloc_gtid_pos_table(LEX_STRING *table_name, + void *hton, rpl_slave_state::gtid_pos_table_state state); + void free_gtid_pos_tables(struct gtid_pos_table *list); }; diff --git a/sql/rpl_mi.cc b/sql/rpl_mi.cc index e90557efd0d..43c5eceecf4 100644 --- a/sql/rpl_mi.cc +++ b/sql/rpl_mi.cc @@ -1557,6 +1557,9 @@ bool give_error_if_slave_running(bool already_locked) /** any_slave_sql_running() + @param + already_locked 0 if we need to lock, 1 if we have LOCK_active_mi_locked + @return 0 No Slave SQL thread is running # Number of slave SQL thread running @@ -1567,26 +1570,28 @@ bool give_error_if_slave_running(bool already_locked) hash entries can't be accessed. */ -uint any_slave_sql_running() +uint any_slave_sql_running(bool already_locked) { uint count= 0; HASH *hash; DBUG_ENTER("any_slave_sql_running"); - mysql_mutex_lock(&LOCK_active_mi); + if (!already_locked) + mysql_mutex_lock(&LOCK_active_mi); if (unlikely(shutdown_in_progress || !master_info_index)) + count= 1; + else { - mysql_mutex_unlock(&LOCK_active_mi); - DBUG_RETURN(1); - } - hash= &master_info_index->master_info_hash; - for (uint i= 0; i< hash->records; ++i) - { - Master_info *mi= (Master_info *)my_hash_element(hash, i); - if (mi->rli.slave_running != MYSQL_SLAVE_NOT_RUN) - count++; + hash= &master_info_index->master_info_hash; + for (uint i= 0; i< hash->records; ++i) + { + Master_info *mi= (Master_info *)my_hash_element(hash, i); + if (mi->rli.slave_running != MYSQL_SLAVE_NOT_RUN) + count++; + } } - mysql_mutex_unlock(&LOCK_active_mi); + if (!already_locked) + mysql_mutex_unlock(&LOCK_active_mi); DBUG_RETURN(count); } diff --git a/sql/rpl_mi.h b/sql/rpl_mi.h index ccc1be6e5ce..58333ad1aa8 100644 --- a/sql/rpl_mi.h +++ b/sql/rpl_mi.h @@ -379,7 +379,7 @@ void create_logfile_name_with_suffix(char *res_file_name, size_t length, uchar *get_key_master_info(Master_info *mi, size_t *length, my_bool not_used __attribute__((unused))); void free_key_master_info(Master_info *mi); -uint any_slave_sql_running(); +uint any_slave_sql_running(bool already_locked); bool give_error_if_slave_running(bool already_lock); #endif /* HAVE_REPLICATION */ diff --git a/sql/rpl_parallel.cc b/sql/rpl_parallel.cc index aaa72da29db..0994bce4f7a 100644 --- a/sql/rpl_parallel.cc +++ b/sql/rpl_parallel.cc @@ -1466,7 +1466,7 @@ rpl_parallel_change_thread_count(rpl_parallel_thread_pool *pool, */ if (!new_count && !force) { - if (any_slave_sql_running()) + if (any_slave_sql_running(false)) { DBUG_PRINT("warning", ("SQL threads running while trying to reset parallel pool")); @@ -1621,7 +1621,7 @@ err: int rpl_parallel_resize_pool_if_no_slaves(void) { /* master_info_index is set to NULL on shutdown */ - if (opt_slave_parallel_threads > 0 && !any_slave_sql_running()) + if (opt_slave_parallel_threads > 0 && !any_slave_sql_running(false)) return rpl_parallel_inactivate_pool(&global_rpl_thread_pool); return 0; } diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index fda922c3e12..63179f5d8e9 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -32,6 +32,8 @@ #include "slave.h" #include <mysql/plugin.h> #include <mysql/service_thd_wait.h> +#include "lock.h" +#include "sql_table.h" static int count_relay_log_space(Relay_log_info* rli); @@ -1466,41 +1468,22 @@ Relay_log_info::update_relay_log_state(rpl_gtid *gtid_list, uint32 count) #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) -int -rpl_load_gtid_slave_state(THD *thd) +struct gtid_pos_element { uint64 sub_id; rpl_gtid gtid; void *hton; }; + +static int +scan_one_gtid_slave_pos_table(THD *thd, HASH *hash, DYNAMIC_ARRAY *array, + LEX_STRING *tablename, void **out_hton) { TABLE_LIST tlist; TABLE *table; bool table_opened= false; bool table_scanned= false; - bool array_inited= false; - struct local_element { uint64 sub_id; rpl_gtid gtid; }; - struct local_element tmp_entry, *entry; - HASH hash; - DYNAMIC_ARRAY array; + struct gtid_pos_element tmp_entry, *entry; int err= 0; - uint32 i; - DBUG_ENTER("rpl_load_gtid_slave_state"); - - mysql_mutex_lock(&rpl_global_gtid_slave_state->LOCK_slave_state); - bool loaded= rpl_global_gtid_slave_state->loaded; - mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state); - if (loaded) - DBUG_RETURN(0); - - my_hash_init(&hash, &my_charset_bin, 32, - offsetof(local_element, gtid) + offsetof(rpl_gtid, domain_id), - sizeof(uint32), NULL, my_free, HASH_UNIQUE); - if ((err= my_init_dynamic_array(&array, sizeof(local_element), 0, 0, MYF(0)))) - goto end; - array_inited= true; thd->reset_for_next_command(); - - tlist.init_one_table(STRING_WITH_LEN("mysql"), - rpl_gtid_slave_state_table_name.str, - rpl_gtid_slave_state_table_name.length, - NULL, TL_READ); + tlist.init_one_table(STRING_WITH_LEN("mysql"), tablename->str, + tablename->length, NULL, TL_READ); if ((err= open_and_lock_tables(thd, &tlist, FALSE, 0))) goto end; table_opened= true; @@ -1546,26 +1529,28 @@ rpl_load_gtid_slave_state(THD *thd) tmp_entry.gtid.domain_id= domain_id; tmp_entry.gtid.server_id= server_id; tmp_entry.gtid.seq_no= seq_no; - if ((err= insert_dynamic(&array, (uchar *)&tmp_entry))) + tmp_entry.hton= table->s->db_type(); + if ((err= insert_dynamic(array, (uchar *)&tmp_entry))) { my_error(ER_OUT_OF_RESOURCES, MYF(0)); goto end; } - if ((rec= my_hash_search(&hash, (const uchar *)&domain_id, 0))) + if ((rec= my_hash_search(hash, (const uchar *)&domain_id, 0))) { - entry= (struct local_element *)rec; + entry= (struct gtid_pos_element *)rec; if (entry->sub_id >= sub_id) continue; entry->sub_id= sub_id; DBUG_ASSERT(entry->gtid.domain_id == domain_id); entry->gtid.server_id= server_id; entry->gtid.seq_no= seq_no; + entry->hton= table->s->db_type(); } else { - if (!(entry= (struct local_element *)my_malloc(sizeof(*entry), - MYF(MY_WME)))) + if (!(entry= (struct gtid_pos_element *)my_malloc(sizeof(*entry), + MYF(MY_WME)))) { my_error(ER_OUTOFMEMORY, MYF(0), (int)sizeof(*entry)); err= 1; @@ -1575,7 +1560,8 @@ rpl_load_gtid_slave_state(THD *thd) entry->gtid.domain_id= domain_id; entry->gtid.server_id= server_id; entry->gtid.seq_no= seq_no; - if ((err= my_hash_insert(&hash, (uchar *)entry))) + entry->hton= table->s->db_type(); + if ((err= my_hash_insert(hash, (uchar *)entry))) { my_free(entry); my_error(ER_OUT_OF_RESOURCES, MYF(0)); @@ -1583,6 +1569,249 @@ rpl_load_gtid_slave_state(THD *thd) } } } + err= 0; /* Clear HA_ERR_END_OF_FILE */ + +end: + if (table_scanned) + { + table->file->ha_index_or_rnd_end(); + ha_commit_trans(thd, FALSE); + ha_commit_trans(thd, TRUE); + } + if (table_opened) + { + *out_hton= table->s->db_type(); + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + } + return err; +} + + +/* + Look for all tables mysql.gtid_slave_pos*. Read all rows from each such + table found into ARRAY. For each domain id, put the row with highest sub_id + into HASH. +*/ +static int +scan_all_gtid_slave_pos_table(THD *thd, int (*cb)(THD *, LEX_STRING *, void *), + void *cb_data) +{ + static LEX_STRING mysql_db_name= {C_STRING_WITH_LEN("mysql")}; + char path[FN_REFLEN]; + MY_DIR *dirp; + + thd->reset_for_next_command(); + if (lock_schema_name(thd, mysql_db_name.str)) + return 1; + + build_table_filename(path, sizeof(path) - 1, mysql_db_name.str, "", "", 0); + if (!(dirp= my_dir(path, MYF(MY_DONT_SORT)))) + { + my_error(ER_FILE_NOT_FOUND, MYF(0), path, my_errno); + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + return 1; + } + else + { + size_t i; + Dynamic_array<LEX_STRING*> files(dirp->number_of_files); + Discovered_table_list tl(thd, &files); + int err; + + err= ha_discover_table_names(thd, &mysql_db_name, dirp, &tl, false); + my_dirend(dirp); + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + if (err) + return err; + + for (i = 0; i < files.elements(); ++i) + { + if (strncmp(files.at(i)->str, + rpl_gtid_slave_state_table_name.str, + rpl_gtid_slave_state_table_name.length) == 0) + { + if ((err= (*cb)(thd, files.at(i), cb_data))) + return err; + } + } + } + + return 0; +} + + +struct load_gtid_state_cb_data { + HASH *hash; + DYNAMIC_ARRAY *array; + struct rpl_slave_state::gtid_pos_table *table_list; + struct rpl_slave_state::gtid_pos_table *default_entry; +}; + +static int +process_gtid_pos_table(THD *thd, LEX_STRING *table_name, void *hton, + struct load_gtid_state_cb_data *data) +{ + struct rpl_slave_state::gtid_pos_table *p, *entry, **next_ptr; + bool is_default= + (strcmp(table_name->str, rpl_gtid_slave_state_table_name.str) == 0); + + /* + Ignore tables with duplicate storage engine, with a warning. + Prefer the default mysql.gtid_slave_pos over another table + mysql.gtid_slave_posXXX with the same storage engine. + */ + next_ptr= &data->table_list; + entry= data->table_list; + while (entry) + { + if (entry->table_hton == hton) + { + static const char *warning_msg= "Ignoring redundant table mysql.%s " + "since mysql.%s has the same storage engine"; + if (!is_default) + { + /* Ignore the redundant table. */ + sql_print_warning(warning_msg, table_name->str, entry->table_name); + return 0; + } + else + { + sql_print_warning(warning_msg, entry->table_name, table_name->str); + /* Delete the redundant table, and proceed to add this one instead. */ + *next_ptr= entry->next; + my_free(entry); + break; + } + } + next_ptr= &entry->next; + entry= entry->next; + } + + p= rpl_global_gtid_slave_state->alloc_gtid_pos_table(table_name, + hton, rpl_slave_state::GTID_POS_AVAILABLE); + if (!p) + return 1; + p->next= data->table_list; + data->table_list= p; + if (is_default) + data->default_entry= p; + return 0; +} + + +/* + Put tables corresponding to @@gtid_pos_auto_engines at the end of the list, + marked to be auto-created if needed. +*/ +static int +gtid_pos_auto_create_tables(rpl_slave_state::gtid_pos_table **list_ptr) +{ + plugin_ref *auto_engines; + int err= 0; + mysql_mutex_lock(&LOCK_global_system_variables); + for (auto_engines= opt_gtid_pos_auto_plugins; + !err && auto_engines && *auto_engines; + ++auto_engines) + { + void *hton= plugin_hton(*auto_engines); + char buf[FN_REFLEN+1]; + LEX_STRING table_name; + char *p; + rpl_slave_state::gtid_pos_table *entry, **next_ptr; + + /* See if this engine is already in the list. */ + next_ptr= list_ptr; + entry= *list_ptr; + while (entry) + { + if (entry->table_hton == hton) + break; + next_ptr= &entry->next; + entry= entry->next; + } + if (entry) + continue; + + /* Add an auto-create entry for this engine at end of list. */ + p= strmake(buf, rpl_gtid_slave_state_table_name.str, FN_REFLEN); + p= strmake(p, "_", FN_REFLEN - (p - buf)); + p= strmake(p, plugin_name(*auto_engines)->str, FN_REFLEN - (p - buf)); + table_name.str= buf; + table_name.length= p - buf; + entry= rpl_global_gtid_slave_state->alloc_gtid_pos_table + (&table_name, hton, rpl_slave_state::GTID_POS_AUTO_CREATE); + if (!entry) + { + err= 1; + break; + } + *next_ptr= entry; + } + mysql_mutex_unlock(&LOCK_global_system_variables); + return err; +} + + +static int +load_gtid_state_cb(THD *thd, LEX_STRING *table_name, void *arg) +{ + int err; + load_gtid_state_cb_data *data= static_cast<load_gtid_state_cb_data *>(arg); + void *hton; + + if ((err= scan_one_gtid_slave_pos_table(thd, data->hash, data->array, + table_name, &hton))) + return err; + return process_gtid_pos_table(thd, table_name, hton, data); +} + + +int +rpl_load_gtid_slave_state(THD *thd) +{ + bool array_inited= false; + struct gtid_pos_element tmp_entry, *entry; + HASH hash; + DYNAMIC_ARRAY array; + int err= 0; + uint32 i; + load_gtid_state_cb_data cb_data; + DBUG_ENTER("rpl_load_gtid_slave_state"); + + mysql_mutex_lock(&rpl_global_gtid_slave_state->LOCK_slave_state); + bool loaded= rpl_global_gtid_slave_state->loaded; + mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state); + if (loaded) + DBUG_RETURN(0); + + cb_data.table_list= NULL; + cb_data.default_entry= NULL; + my_hash_init(&hash, &my_charset_bin, 32, + offsetof(gtid_pos_element, gtid) + offsetof(rpl_gtid, domain_id), + sizeof(uint32), NULL, my_free, HASH_UNIQUE); + if ((err= my_init_dynamic_array(&array, sizeof(gtid_pos_element), 0, 0, MYF(0)))) + goto end; + array_inited= true; + + cb_data.hash = &hash; + cb_data.array = &array; + if ((err= scan_all_gtid_slave_pos_table(thd, load_gtid_state_cb, &cb_data))) + goto end; + + if (!cb_data.default_entry) + { + /* + If the mysql.gtid_slave_pos table does not exist, but at least one other + table is available, arbitrarily pick the first in the list to use as + default. + */ + cb_data.default_entry= cb_data.table_list; + } + if ((err= gtid_pos_auto_create_tables(&cb_data.table_list))) + goto end; mysql_mutex_lock(&rpl_global_gtid_slave_state->LOCK_slave_state); if (rpl_global_gtid_slave_state->loaded) @@ -1591,14 +1820,24 @@ rpl_load_gtid_slave_state(THD *thd) goto end; } + if (!cb_data.table_list) + { + my_error(ER_NO_SUCH_TABLE, MYF(0), "mysql", + rpl_gtid_slave_state_table_name.str); + mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state); + err= 1; + goto end; + } + for (i= 0; i < array.elements; ++i) { get_dynamic(&array, (uchar *)&tmp_entry, i); if ((err= rpl_global_gtid_slave_state->update(tmp_entry.gtid.domain_id, - tmp_entry.gtid.server_id, - tmp_entry.sub_id, - tmp_entry.gtid.seq_no, - NULL))) + tmp_entry.gtid.server_id, + tmp_entry.sub_id, + tmp_entry.gtid.seq_no, + tmp_entry.hton, + NULL))) { mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state); my_error(ER_OUT_OF_RESOURCES, MYF(0)); @@ -1608,7 +1847,7 @@ rpl_load_gtid_slave_state(THD *thd) for (i= 0; i < hash.records; ++i) { - entry= (struct local_element *)my_hash_element(&hash, i); + entry= (struct gtid_pos_element *)my_hash_element(&hash, i); if (opt_bin_log && mysql_bin_log.bump_seq_no_counter_if_needed(entry->gtid.domain_id, entry->gtid.seq_no)) @@ -1619,27 +1858,175 @@ rpl_load_gtid_slave_state(THD *thd) } } + rpl_global_gtid_slave_state->set_gtid_pos_tables_list(cb_data.table_list, + cb_data.default_entry); + cb_data.table_list= NULL; rpl_global_gtid_slave_state->loaded= true; mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state); - err= 0; /* Clear HA_ERR_END_OF_FILE */ +end: + if (array_inited) + delete_dynamic(&array); + my_hash_free(&hash); + if (cb_data.table_list) + rpl_global_gtid_slave_state->free_gtid_pos_tables(cb_data.table_list); + DBUG_RETURN(err); +} + + +static int +find_gtid_pos_tables_cb(THD *thd, LEX_STRING *table_name, void *arg) +{ + load_gtid_state_cb_data *data= static_cast<load_gtid_state_cb_data *>(arg); + TABLE_LIST tlist; + TABLE *table= NULL; + int err; + + thd->reset_for_next_command(); + tlist.init_one_table(STRING_WITH_LEN("mysql"), table_name->str, + table_name->length, NULL, TL_READ); + if ((err= open_and_lock_tables(thd, &tlist, FALSE, 0))) + goto end; + table= tlist.table; + + if ((err= gtid_check_rpl_slave_state_table(table))) + goto end; + err= process_gtid_pos_table(thd, table_name, table->s->db_type(), data); end: - if (table_scanned) + if (table) { - table->file->ha_index_or_rnd_end(); ha_commit_trans(thd, FALSE); ha_commit_trans(thd, TRUE); - } - if (table_opened) - { close_thread_tables(thd); thd->mdl_context.release_transactional_locks(); } - if (array_inited) - delete_dynamic(&array); - my_hash_free(&hash); - DBUG_RETURN(err); + + return err; +} + + +/* + Re-compute the list of available mysql.gtid_slave_posXXX tables. + + This is done at START SLAVE to pick up any newly created tables without + requiring server restart. +*/ +int +find_gtid_slave_pos_tables(THD *thd) +{ + int err= 0; + load_gtid_state_cb_data cb_data; + uint num_running; + + mysql_mutex_lock(&rpl_global_gtid_slave_state->LOCK_slave_state); + bool loaded= rpl_global_gtid_slave_state->loaded; + mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state); + if (!loaded) + return 0; + + cb_data.table_list= NULL; + cb_data.default_entry= NULL; + if ((err= scan_all_gtid_slave_pos_table(thd, find_gtid_pos_tables_cb, &cb_data))) + goto end; + + if (!cb_data.table_list) + { + my_error(ER_NO_SUCH_TABLE, MYF(0), "mysql", + rpl_gtid_slave_state_table_name.str); + err= 1; + goto end; + } + if (!cb_data.default_entry) + { + /* + If the mysql.gtid_slave_pos table does not exist, but at least one other + table is available, arbitrarily pick the first in the list to use as + default. + */ + cb_data.default_entry= cb_data.table_list; + } + if ((err= gtid_pos_auto_create_tables(&cb_data.table_list))) + goto end; + + mysql_mutex_lock(&LOCK_active_mi); + num_running= any_slave_sql_running(true); + mysql_mutex_lock(&rpl_global_gtid_slave_state->LOCK_slave_state); + if (num_running <= 1) + { + /* + If no slave is running now, the count will be 1, since this SQL thread + which is starting is included in the count. In this case, we can safely + replace the list, no-one can be trying to read it without lock. + */ + DBUG_ASSERT(num_running == 1); + rpl_global_gtid_slave_state->set_gtid_pos_tables_list(cb_data.table_list, + cb_data.default_entry); + cb_data.table_list= NULL; + } + else + { + /* + If there are SQL threads running, we cannot safely remove the old list. + However we can add new entries, and warn about any tables that + disappeared, but may still be visible to running SQL threads. + */ + rpl_slave_state::gtid_pos_table *old_entry, *new_entry, **next_ptr_ptr; + + old_entry= (rpl_slave_state::gtid_pos_table *) + rpl_global_gtid_slave_state->gtid_pos_tables; + while (old_entry) + { + new_entry= cb_data.table_list; + while (new_entry) + { + if (new_entry->table_hton == old_entry->table_hton) + break; + new_entry= new_entry->next; + } + if (!new_entry) + sql_print_warning("The table mysql.%s was removed. " + "This change will not take full effect " + "until all SQL threads have been restarted", + old_entry->table_name.str); + old_entry= old_entry->next; + } + next_ptr_ptr= &cb_data.table_list; + new_entry= cb_data.table_list; + while (new_entry) + { + /* Check if we already have a table with this storage engine. */ + old_entry= (rpl_slave_state::gtid_pos_table *) + rpl_global_gtid_slave_state->gtid_pos_tables; + while (old_entry) + { + if (new_entry->table_hton == old_entry->table_hton) + break; + old_entry= old_entry->next; + } + if (old_entry) + { + /* This new_entry is already available in the list. */ + next_ptr_ptr= &new_entry->next; + new_entry= new_entry->next; + } + else + { + /* Move this new_entry to the list. */ + rpl_slave_state::gtid_pos_table *next= new_entry->next; + rpl_global_gtid_slave_state->add_gtid_pos_table(new_entry); + *next_ptr_ptr= next; + new_entry= next; + } + } + } + mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state); + mysql_mutex_unlock(&LOCK_active_mi); + +end: + if (cb_data.table_list) + rpl_global_gtid_slave_state->free_gtid_pos_tables(cb_data.table_list); + return err; } diff --git a/sql/rpl_rli.h b/sql/rpl_rli.h index 448fc231b2b..e293d681034 100644 --- a/sql/rpl_rli.h +++ b/sql/rpl_rli.h @@ -959,6 +959,7 @@ extern struct rpl_slave_state *rpl_global_gtid_slave_state; extern gtid_waiting rpl_global_gtid_waiting; int rpl_load_gtid_slave_state(THD *thd); +int find_gtid_slave_pos_tables(THD *thd); int event_group_new_gtid(rpl_group_info *rgi, Gtid_log_event *gev); void delete_or_keep_event_post_apply(rpl_group_info *rgi, Log_event_type typ, Log_event *ev); diff --git a/sql/set_var.cc b/sql/set_var.cc index 15f6bbdafc5..55587796281 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -1293,3 +1293,222 @@ enum sys_var::where get_sys_var_value_origin(void *ptr) return sys_var::CONFIG; } + +/* + Find the next item in string of comma-separated items. + END_POS points at the end of the string. + ITEM_START and ITEM_END return the limits of the next item. + Returns true while items are available, false at the end. +*/ +static bool +engine_list_next_item(const char **pos, const char *end_pos, + const char **item_start, const char **item_end) +{ + if (*pos >= end_pos) + return false; + *item_start= *pos; + while (*pos < end_pos && **pos != ',') + ++*pos; + *item_end= *pos; + ++*pos; + return true; +} + + +static bool +resolve_engine_list_item(THD *thd, plugin_ref *list, uint32 *idx, + const char *pos, const char *pos_end, + bool error_on_unknown_engine, bool temp_copy) +{ + LEX_STRING item_str; + plugin_ref ref; + uint32_t i; + THD *thd_or_null = (temp_copy ? thd : NULL); + + item_str.str= const_cast<char*>(pos); + item_str.length= pos_end-pos; + ref= ha_resolve_by_name(thd_or_null, &item_str, false); + if (!ref) + { + if (error_on_unknown_engine) + { + ErrConvString err(pos, pos_end-pos, system_charset_info); + my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), err.ptr()); + return true; + } + return false; + } + /* Ignore duplicates, like --plugin-load does. */ + for (i= 0; i < *idx; ++i) + { + if (plugin_hton(list[i]) == plugin_hton(ref)) + { + if (!temp_copy) + plugin_unlock(NULL, ref); + return false; + } + } + list[*idx]= ref; + ++*idx; + return false; +} + + +/* + Helper for class Sys_var_pluginlist. + Resolve a comma-separated list of storage engine names to a null-terminated + array of plugin_ref. + + If TEMP_COPY is true, a THD must be given as well. In this case, the + allocated memory and locked plugins are registered in the THD and will + be freed / unlocked automatically. If TEMP_COPY is true, THD can be + passed as NULL, and resources must be freed explicitly later with + free_engine_list(). +*/ +plugin_ref * +resolve_engine_list(THD *thd, const char *str_arg, size_t str_arg_len, + bool error_on_unknown_engine, bool temp_copy) +{ + uint32 count, idx; + const char *pos, *item_start, *item_end; + const char *str_arg_end= str_arg + str_arg_len; + plugin_ref *res; + + count= 0; + pos= str_arg; + for (;;) + { + if (!engine_list_next_item(&pos, str_arg_end, &item_start, &item_end)) + break; + ++count; + } + + if (temp_copy) + res= (plugin_ref *)thd->calloc((count+1)*sizeof(*res)); + else + res= (plugin_ref *)my_malloc((count+1)*sizeof(*res), MYF(MY_ZEROFILL|MY_WME)); + if (!res) + { + my_error(ER_OUTOFMEMORY, MYF(0), (int)((count+1)*sizeof(*res))); + goto err; + } + + idx= 0; + pos= str_arg; + for (;;) + { + if (!engine_list_next_item(&pos, str_arg_end, &item_start, &item_end)) + break; + DBUG_ASSERT(idx < count); + if (idx >= count) + break; + if (resolve_engine_list_item(thd, res, &idx, item_start, item_end, + error_on_unknown_engine, temp_copy)) + goto err; + } + + return res; + +err: + if (!temp_copy) + free_engine_list(res); + return NULL; +} + + +void +free_engine_list(plugin_ref *list) +{ + plugin_ref *p; + + if (!list) + return; + for (p= list; *p; ++p) + plugin_unlock(NULL, *p); + my_free(list); +} + + +plugin_ref * +copy_engine_list(plugin_ref *list) +{ + plugin_ref *p; + uint32 count, i; + + for (p= list, count= 0; *p; ++p, ++count) + ; + p= (plugin_ref *)my_malloc((count+1)*sizeof(*p), MYF(0)); + if (!p) + { + my_error(ER_OUTOFMEMORY, MYF(0), (int)((count+1)*sizeof(*p))); + return NULL; + } + for (i= 0; i < count; ++i) + p[i]= my_plugin_lock(NULL, list[i]); + p[i] = NULL; + return p; +} + + +/* + Create a temporary copy of an engine list. The memory will be freed + (and the plugins unlocked) automatically, on the passed THD. +*/ +plugin_ref * +temp_copy_engine_list(THD *thd, plugin_ref *list) +{ + plugin_ref *p; + uint32 count, i; + + for (p= list, count= 0; *p; ++p, ++count) + ; + p= (plugin_ref *)thd->alloc((count+1)*sizeof(*p)); + if (!p) + { + my_error(ER_OUTOFMEMORY, MYF(0), (int)((count+1)*sizeof(*p))); + return NULL; + } + for (i= 0; i < count; ++i) + p[i]= my_plugin_lock(thd, list[i]); + p[i] = NULL; + return p; +} + + +char * +pretty_print_engine_list(THD *thd, plugin_ref *list) +{ + plugin_ref *p; + size_t size; + char *buf, *pos; + + if (!list) + return thd->strmake("", 0); + + size= 0; + for (p= list; *p; ++p) + size+= plugin_name(*p)->length + 1; + buf= static_cast<char *>(thd->alloc(size)); + if (!buf) + return NULL; + pos= buf; + for (p= list; *p; ++p) + { + LEX_STRING *name; + size_t remain; + + remain= buf + size - pos; + DBUG_ASSERT(remain > 0); + if (remain <= 1) + break; + if (pos != buf) + { + pos= strmake(pos, ",", remain-1); + --remain; + } + name= plugin_name(*p); + pos= strmake(pos, name->str, MY_MIN(name->length, remain-1)); + } + *pos= '\0'; + return buf; +} diff --git a/sql/set_var.h b/sql/set_var.h index 17d1ff93ebc..8d39854a744 100644 --- a/sql/set_var.h +++ b/sql/set_var.h @@ -286,6 +286,7 @@ public: longlong longlong_value; ///< for signed integer double double_value; ///< for Sys_var_double plugin_ref plugin; ///< for Sys_var_plugin + plugin_ref *plugins; ///< for Sys_var_pluginlist Time_zone *time_zone; ///< for Sys_var_tz LEX_STRING string_value; ///< for Sys_var_charptr and others const void *ptr; ///< for Sys_var_struct @@ -424,6 +425,12 @@ int sys_var_init(); uint sys_var_elements(); int sys_var_add_options(DYNAMIC_ARRAY *long_options, int parse_flags); void sys_var_end(void); +plugin_ref *resolve_engine_list(THD *thd, const char *str_arg, size_t str_arg_len, + bool error_on_unknown_engine, bool temp_copy); +void free_engine_list(plugin_ref *list); +plugin_ref *copy_engine_list(plugin_ref *list); +plugin_ref *temp_copy_engine_list(THD *thd, plugin_ref *list); +char *pretty_print_engine_list(THD *thd, plugin_ref *list); #endif diff --git a/sql/slave.cc b/sql/slave.cc index a7f0f003e5c..67785be6255 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -60,6 +60,7 @@ #include "rpl_tblmap.h" #include "debug_sync.h" #include "rpl_parallel.h" +#include "sql_show.h" #define FLAGSTR(V,F) ((V)&(F)?#F" ":"") @@ -279,15 +280,180 @@ static void init_slave_psi_keys(void) #endif /* HAVE_PSI_INTERFACE */ +/* + Note: This definition needs to be kept in sync with the one in + mysql_system_tables.sql which is used by mysql_create_db. +*/ +static const char gtid_pos_table_definition1[]= + "CREATE TABLE "; +static const char gtid_pos_table_definition2[]= + " (domain_id INT UNSIGNED NOT NULL, " + "sub_id BIGINT UNSIGNED NOT NULL, " + "server_id INT UNSIGNED NOT NULL, " + "seq_no BIGINT UNSIGNED NOT NULL, " + "PRIMARY KEY (domain_id, sub_id)) CHARSET=latin1 " + "COMMENT='Replication slave GTID position' " + "ENGINE="; + +/* + Build a query string + CREATE TABLE mysql.gtid_slave_pos_<engine> ... ENGINE=<engine> +*/ +static bool +build_gtid_pos_create_query(THD *thd, String *query, + LEX_STRING *table_name, + LEX_STRING *engine_name) +{ + bool err= false; + err|= query->append(gtid_pos_table_definition1); + err|= append_identifier(thd, query, table_name->str, table_name->length); + err|= query->append(gtid_pos_table_definition2); + err|= append_identifier(thd, query, engine_name->str, engine_name->length); + return err; +} + + +static int +gtid_pos_table_creation(THD *thd, plugin_ref engine, LEX_STRING *table_name) +{ + int err; + StringBuffer<sizeof(gtid_pos_table_definition1) + + sizeof(gtid_pos_table_definition1) + + 2*FN_REFLEN> query; + + if (build_gtid_pos_create_query(thd, &query, table_name, plugin_name(engine))) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + return 1; + } + + thd->set_db("mysql", 5); + thd->clear_error(); + ulonglong thd_saved_option= thd->variables.option_bits; + /* This query shuold not be binlogged. */ + thd->variables.option_bits&= ~(ulonglong)OPTION_BIN_LOG; + thd->set_query_and_id(query.c_ptr(), query.length(), thd->charset(), + next_query_id()); + Parser_state parser_state; + err= parser_state.init(thd, thd->query(), thd->query_length()); + if (err) + goto end; + mysql_parse(thd, thd->query(), thd->query_length(), &parser_state, + FALSE, FALSE); + if (thd->is_error()) + err= 1; +end: + thd->variables.option_bits= thd_saved_option; + thd->reset_query(); + return err; +} + + +static void +handle_gtid_pos_auto_create_request(THD *thd, void *hton) +{ + int err; + plugin_ref engine= NULL, *auto_engines; + rpl_slave_state::gtid_pos_table *entry; + StringBuffer<FN_REFLEN> loc_table_name; + LEX_STRING table_name; + + /* + Check that the plugin is still in @@gtid_pos_auto_engines, and lock + it. + */ + mysql_mutex_lock(&LOCK_global_system_variables); + engine= NULL; + for (auto_engines= opt_gtid_pos_auto_plugins; + auto_engines && *auto_engines; + ++auto_engines) + { + if (plugin_hton(*auto_engines) == hton) + { + engine= my_plugin_lock(NULL, *auto_engines); + break; + } + } + mysql_mutex_unlock(&LOCK_global_system_variables); + if (!engine) + { + /* The engine is gone from @@gtid_pos_auto_engines, so no action. */ + goto end; + } + + /* Find the entry for the table to auto-create. */ + mysql_mutex_lock(&rpl_global_gtid_slave_state->LOCK_slave_state); + entry= (rpl_slave_state::gtid_pos_table *) + rpl_global_gtid_slave_state->gtid_pos_tables; + while (entry) + { + if (entry->table_hton == hton && + entry->state == rpl_slave_state::GTID_POS_CREATE_REQUESTED) + break; + entry= entry->next; + } + if (entry) + { + entry->state = rpl_slave_state::GTID_POS_CREATE_IN_PROGRESS; + err= loc_table_name.append(entry->table_name.str, entry->table_name.length); + } + mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state); + if (!entry) + goto end; + if (err) + { + sql_print_error("Out of memory while trying to auto-create GTID position table"); + goto end; + } + table_name.str= loc_table_name.c_ptr_safe(); + table_name.length= loc_table_name.length(); + + err= gtid_pos_table_creation(thd, engine, &table_name); + if (err) + { + sql_print_error("Error auto-creating GTID position table `mysql.%s`: %s Error_code: %d", + table_name.str, thd->get_stmt_da()->message(), + thd->get_stmt_da()->sql_errno()); + thd->clear_error(); + goto end; + } + + /* Now enable the entry for the auto-created table. */ + mysql_mutex_lock(&rpl_global_gtid_slave_state->LOCK_slave_state); + entry= (rpl_slave_state::gtid_pos_table *) + rpl_global_gtid_slave_state->gtid_pos_tables; + while (entry) + { + if (entry->table_hton == hton && + entry->state == rpl_slave_state::GTID_POS_CREATE_IN_PROGRESS) + { + entry->state= rpl_slave_state::GTID_POS_AVAILABLE; + break; + } + entry= entry->next; + } + mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state); + +end: + if (engine) + plugin_unlock(NULL, engine); +} + + static bool slave_background_thread_running; static bool slave_background_thread_stop; static bool slave_background_thread_gtid_loaded; -struct slave_background_kill_t { +static struct slave_background_kill_t { slave_background_kill_t *next; THD *to_kill; } *slave_background_kill_list; +static struct slave_background_gtid_pos_create_t { + slave_background_gtid_pos_create_t *next; + void *hton; +} *slave_background_gtid_pos_create_list; + pthread_handler_t handle_slave_background(void *arg __attribute__((unused))) @@ -321,6 +487,7 @@ handle_slave_background(void *arg __attribute__((unused))) do { slave_background_kill_t *kill_list; + slave_background_gtid_pos_create_t *create_list; thd->ENTER_COND(&COND_slave_background, &LOCK_slave_background, &stage_slave_background_wait_request, @@ -329,12 +496,14 @@ handle_slave_background(void *arg __attribute__((unused))) { stop= abort_loop || thd->killed || slave_background_thread_stop; kill_list= slave_background_kill_list; - if (stop || kill_list) + create_list= slave_background_gtid_pos_create_list; + if (stop || kill_list || create_list) break; mysql_cond_wait(&COND_slave_background, &LOCK_slave_background); } slave_background_kill_list= NULL; + slave_background_gtid_pos_create_list= NULL; thd->EXIT_COND(&old_stage); while (kill_list) @@ -353,6 +522,16 @@ handle_slave_background(void *arg __attribute__((unused))) mysql_mutex_unlock(&to_kill->LOCK_wakeup_ready); my_free(p); } + + while (create_list) + { + slave_background_gtid_pos_create_t *next= create_list->next; + void *hton= create_list->hton; + handle_gtid_pos_auto_create_request(thd, hton); + my_free(create_list); + create_list= next; + } + mysql_mutex_lock(&LOCK_slave_background); } while (!stop); @@ -392,6 +571,41 @@ slave_background_kill_request(THD *to_kill) /* + This function must only be called from a slave SQL thread (or worker thread), + to ensure that the table_entry will not go away before we can lock the + LOCK_slave_state. +*/ +void +slave_background_gtid_pos_create_request( + rpl_slave_state::gtid_pos_table *table_entry) +{ + slave_background_gtid_pos_create_t *p; + + if (table_entry->state != rpl_slave_state::GTID_POS_AUTO_CREATE) + return; + p= (slave_background_gtid_pos_create_t *)my_malloc(sizeof(*p), MYF(MY_WME)); + if (!p) + return; + mysql_mutex_lock(&rpl_global_gtid_slave_state->LOCK_slave_state); + if (table_entry->state != rpl_slave_state::GTID_POS_AUTO_CREATE) + { + my_free(p); + mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state); + return; + } + table_entry->state= rpl_slave_state::GTID_POS_CREATE_REQUESTED; + mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state); + + p->hton= table_entry->table_hton; + mysql_mutex_lock(&LOCK_slave_background); + p->next= slave_background_gtid_pos_create_list; + slave_background_gtid_pos_create_list= p; + mysql_cond_signal(&COND_slave_background); + mysql_mutex_unlock(&LOCK_slave_background); +} + + +/* Start the slave background thread. This thread is currently used for two purposes: @@ -5065,6 +5279,14 @@ pthread_handler_t handle_slave_sql(void *arg) if (mi->using_gtid != Master_info::USE_GTID_NO || opt_gtid_strict_mode) goto err; } + /* Re-load the set of mysql.gtid_slave_posXXX tables available. */ + if (find_gtid_slave_pos_tables(thd)) + { + rli->report(ERROR_LEVEL, thd->get_stmt_da()->sql_errno(), NULL, + "Error processing replication GTID position tables: %s", + thd->get_stmt_da()->message()); + goto err; + } /* execute init_slave variable */ if (opt_init_slave.length) diff --git a/sql/slave.h b/sql/slave.h index 431e6847abe..c856a6989ed 100644 --- a/sql/slave.h +++ b/sql/slave.h @@ -48,6 +48,7 @@ #include "my_list.h" #include "rpl_filter.h" #include "rpl_tblmap.h" +#include "rpl_gtid.h" #define SLAVE_NET_TIMEOUT 60 @@ -268,6 +269,8 @@ void slave_output_error_info(rpl_group_info *rgi, THD *thd); pthread_handler_t handle_slave_sql(void *arg); bool net_request_file(NET* net, const char* fname); void slave_background_kill_request(THD *to_kill); +void slave_background_gtid_pos_create_request + (rpl_slave_state::gtid_pos_table *table_entry); extern bool volatile abort_loop; extern Master_info *active_mi; /* active_mi for multi-master */ diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index 92b9c94e84c..c73229a2ee0 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -941,6 +941,10 @@ SHOW_COMP_OPTION plugin_status(const char *name, size_t len, int type) } +/* + If LEX is passed non-NULL, an automatic unlock of the plugin will happen + in the LEX destructor. +*/ static plugin_ref intern_plugin_lock(LEX *lex, plugin_ref rc) { st_plugin_int *pi= plugin_ref_to_int(rc); @@ -984,6 +988,16 @@ static plugin_ref intern_plugin_lock(LEX *lex, plugin_ref rc) } +/* + Notes on lifetime: + + If THD is passed as non-NULL (and with a non-NULL thd->lex), an entry is made + in the thd->lex which will cause an automatic unlock of the plugin in the LEX + destructor. In this case, no manual unlock must be done. + + Otherwise, when passing a NULL THD, the caller must arrange that plugin + unlock happens later. +*/ plugin_ref plugin_lock(THD *thd, plugin_ref ptr) { LEX *lex= thd ? thd->lex : 0; @@ -1020,6 +1034,16 @@ plugin_ref plugin_lock(THD *thd, plugin_ref ptr) } +/* + Notes on lifetime: + + If THD is passed as non-NULL (and with a non-NULL thd->lex), an entry is made + in the thd->lex which will cause an automatic unlock of the plugin in the LEX + destructor. In this case, no manual unlock must be done. + + Otherwise, when passing a NULL THD, the caller must arrange that plugin + unlock happens later. +*/ plugin_ref plugin_lock_by_name(THD *thd, const LEX_CSTRING *name, int type) { LEX *lex= thd ? thd->lex : 0; @@ -1935,6 +1959,12 @@ void plugin_shutdown(void) if (initialized) { + if (opt_gtid_pos_auto_plugins) + { + free_engine_list(opt_gtid_pos_auto_plugins); + opt_gtid_pos_auto_plugins= NULL; + } + mysql_mutex_lock(&LOCK_plugin); reap_needed= true; diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 28665098d4d..de054f31afa 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -3556,6 +3556,45 @@ static Sys_var_plugin Sys_enforce_storage_engine( NO_CMD_LINE, MYSQL_STORAGE_ENGINE_PLUGIN, DEFAULT(&enforced_storage_engine), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_has_super)); + +#ifdef HAVE_REPLICATION +/* + Check + 1. Value for gtid_pos_auto_engines is not NULL. + 2. No slave SQL thread is running. +*/ +static bool +check_gtid_pos_auto_engines(sys_var *self, THD *thd, set_var *var) +{ + bool running; + bool err= false; + + DBUG_ASSERT(var->type == OPT_GLOBAL); + if (var->value && var->value->is_null()) + err= true; + else + { + running= give_error_if_slave_running(false); + if (running) + err= true; + } + return err; +} + + +static Sys_var_pluginlist Sys_gtid_pos_auto_engines( + "gtid_pos_auto_engines", + "List of engines for which to automatically create a " + "mysql.gtid_slave_pos_ENGINE table, if a transaction using that engine " + "is replicated. This can be used to avoid introducing cross-engine " + "transactions, if engines are used different from that used by table " + "mysql.gtid_slave_pos", + GLOBAL_VAR(opt_gtid_pos_auto_plugins), NO_CMD_LINE, + DEFAULT(>id_pos_auto_engines), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_gtid_pos_auto_engines)); +#endif + + #if defined(ENABLED_DEBUG_SYNC) /* Variable can be set for the session only. diff --git a/sql/sys_vars.ic b/sql/sys_vars.ic index f9acfb3b657..9b0553015a5 100644 --- a/sql/sys_vars.ic +++ b/sql/sys_vars.ic @@ -1535,6 +1535,116 @@ public: { return valptr(thd, get_default(thd)); } }; +/** + Class for variables that containg a list of plugins. + Currently this is used only for @@gtid_pos_auto_create_engines + + Backing store: plugin_ref + + @note + Currently this is only used for storage engine type plugins, and thus only + storage engine type plugin is implemented. It could be extended to other + plugin types later if needed, similar to Sys_var_plugin. + + These variables don't support command-line equivalents, any such + command-line options should be added manually to my_long_options in mysqld.cc + + Note on lifetimes of resources allocated: We allocate a zero-terminated array + of plugin_ref*, and lock the contained plugins. The list in the global + variable must be freed (with free_engine_list()). However, the way Sys_var + works, there is no place to explicitly free other lists, like the one + returned from get_default(). + + Therefore, the code needs to work with temporary lists, which are + registered in the THD to be automatically freed (and plugins similarly + automatically unlocked). This is why do_check() allocates a temporary + list, from which do_update() then makes a permanent copy. +*/ +class Sys_var_pluginlist: public sys_var +{ + int plugin_type; +public: + Sys_var_pluginlist(const char *name_arg, + const char *comment, int flag_args, ptrdiff_t off, size_t size, + CMD_LINE getopt, + char **def_val, PolyLock *lock=0, + enum binlog_status_enum binlog_status_arg=VARIABLE_NOT_IN_BINLOG, + on_check_function on_check_func=0, + on_update_function on_update_func=0, + const char *substitute=0) + : sys_var(&all_sys_vars, name_arg, comment, flag_args, off, getopt.id, + getopt.arg_type, SHOW_CHAR, (intptr)def_val, + lock, binlog_status_arg, on_check_func, on_update_func, + substitute) + { + option.var_type|= GET_STR; + SYSVAR_ASSERT(size == sizeof(plugin_ref)); + SYSVAR_ASSERT(getopt.id < 0); // force NO_CMD_LINE + } + bool do_check(THD *thd, set_var *var) + { + char buff[STRING_BUFFER_USUAL_SIZE]; + String str(buff,sizeof(buff), system_charset_info), *res; + plugin_ref *plugins; + + if (!(res=var->value->val_str(&str))) + plugins= resolve_engine_list(thd, "", 0, true, true); + else + plugins= resolve_engine_list(thd, res->ptr(), res->length(), true, true); + if (!plugins) + return true; + var->save_result.plugins= plugins; + return false; + } + void do_update(plugin_ref **valptr, plugin_ref* newval) + { + plugin_ref *oldval= *valptr; + *valptr= copy_engine_list(newval); + free_engine_list(oldval); + } + bool session_update(THD *thd, set_var *var) + { + do_update((plugin_ref**)session_var_ptr(thd), + var->save_result.plugins); + return false; + } + bool global_update(THD *thd, set_var *var) + { + do_update((plugin_ref**)global_var_ptr(), + var->save_result.plugins); + return false; + } + void session_save_default(THD *thd, set_var *var) + { + plugin_ref* plugins= global_var(plugin_ref *); + var->save_result.plugins= plugins ? temp_copy_engine_list(thd, plugins) : 0; + } + plugin_ref *get_default(THD *thd) + { + char *default_value= *reinterpret_cast<char**>(option.def_value); + if (!default_value) + return 0; + return resolve_engine_list(thd, default_value, strlen(default_value), + false, true); + } + + void global_save_default(THD *thd, set_var *var) + { + var->save_result.plugins= get_default(thd); + } + + uchar *valptr(THD *thd, plugin_ref *plugins) + { + return (uchar*)pretty_print_engine_list(thd, plugins); + } + uchar *session_value_ptr(THD *thd, const LEX_STRING *base) + { return valptr(thd, session_var(thd, plugin_ref*)); } + uchar *global_value_ptr(THD *thd, const LEX_STRING *base) + { return valptr(thd, global_var(plugin_ref*)); } + uchar *default_value_ptr(THD *thd) + { return valptr(thd, get_default(thd)); } +}; + #if defined(ENABLED_DEBUG_SYNC) #include "debug_sync.h" diff --git a/storage/tokudb/mysql-test/rpl/r/rpl_deadlock_tokudb.result b/storage/tokudb/mysql-test/rpl/r/rpl_deadlock_tokudb.result index 2348fd0d9d4..1698dc6df85 100644 --- a/storage/tokudb/mysql-test/rpl/r/rpl_deadlock_tokudb.result +++ b/storage/tokudb/mysql-test/rpl/r/rpl_deadlock_tokudb.result @@ -39,7 +39,9 @@ connection slave; BEGIN; SELECT * FROM t1 FOR UPDATE; a +connection slave1; START SLAVE; +connection slave; SELECT COUNT(*) FROM t2; COUNT(*) 0 @@ -61,8 +63,10 @@ BEGIN; SELECT * FROM t1 FOR UPDATE; a 1 +connection slave1; START SLAVE; include/wait_for_slave_sql_error.inc [errno=1205] +connection slave; SELECT COUNT(*) FROM t2; COUNT(*) 0 @@ -92,8 +96,10 @@ SELECT * FROM t1 FOR UPDATE; a 1 1 +connection slave1; START SLAVE; include/wait_for_slave_sql_error.inc [errno=1205] +connection slave; SELECT COUNT(*) FROM t2; COUNT(*) 0 diff --git a/storage/tokudb/mysql-test/tokudb_rpl/r/mdev12179.result b/storage/tokudb/mysql-test/tokudb_rpl/r/mdev12179.result new file mode 100644 index 00000000000..d4532eec4e2 --- /dev/null +++ b/storage/tokudb/mysql-test/tokudb_rpl/r/mdev12179.result @@ -0,0 +1,265 @@ +include/master-slave.inc +[connection master] +connection server_2; +include/stop_slave.inc +CHANGE MASTER TO master_use_gtid=slave_pos; +SET sql_log_bin=0; +CREATE TABLE mysql.gtid_slave_pos_innodb LIKE mysql.gtid_slave_pos; +ALTER TABLE mysql.gtid_slave_pos_innodb ENGINE=InnoDB; +CREATE TABLE mysql.gtid_slave_pos_tokudb LIKE mysql.gtid_slave_pos; +ALTER TABLE mysql.gtid_slave_pos_tokudb ENGINE=TokuDB; +CREATE TABLE mysql.gtid_slave_pos_myisam_redundant LIKE mysql.gtid_slave_pos; +CREATE TABLE mysql.gtid_slave_pos_innodb_redundant LIKE mysql.gtid_slave_pos; +ALTER TABLE mysql.gtid_slave_pos_innodb_redundant ENGINE=InnoDB; +call mtr.add_suppression("Ignoring redundant table.*since.*has the same storage engine"); +include/start_slave.inc +connection server_1; +CREATE TABLE t1 (a INT PRIMARY KEY); +CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=InnoDB; +CREATE TABLE t3 (a INT PRIMARY KEY) ENGINE=TokuDB; +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (1); +INSERT INTO t3 VALUES (1); +SELECT * FROM t1 ORDER BY a; +a +1 +SELECT * FROM t2 ORDER BY a; +a +1 +SELECT * FROM t3 ORDER BY a; +a +1 +connection server_2; +SELECT * FROM t1 ORDER BY a; +a +1 +SELECT * FROM t2 ORDER BY a; +a +1 +SELECT * FROM t3 ORDER BY a; +a +1 +SELECT * FROM mysql.gtid_slave_pos ORDER BY sub_id; +domain_id sub_id server_id seq_no +0 3 1 3 +0 4 1 4 +SELECT * FROM ( SELECT * FROM mysql.gtid_slave_pos_innodb +UNION ALL SELECT * FROM mysql.gtid_slave_pos_innodb_redundant) inner_select +ORDER BY sub_id; +domain_id sub_id server_id seq_no +0 5 1 5 +SELECT * FROM mysql.gtid_slave_pos_tokudb ORDER BY sub_id; +domain_id sub_id server_id seq_no +0 6 1 6 +connection server_2; +FLUSH NO_WRITE_TO_BINLOG STATUS; +SET sql_log_bin=0; +SHOW STATUS LIKE "Transactions_multi_engine"; +Variable_name Value +Transactions_multi_engine 0 +INSERT INTO t1 VALUES (100); +SHOW STATUS LIKE "Transactions_multi_engine"; +Variable_name Value +Transactions_multi_engine 0 +INSERT INTO t2 VALUES (101); +SHOW STATUS LIKE "Transactions_multi_engine"; +Variable_name Value +Transactions_multi_engine 0 +INSERT INTO t3 VALUES (101); +SHOW STATUS LIKE "Transactions_multi_engine"; +Variable_name Value +Transactions_multi_engine 0 +BEGIN; +INSERT INTO t3 VALUES (102); +INSERT INTO t2 VALUES (103); +COMMIT; +SHOW STATUS LIKE "Transactions_multi_engine"; +Variable_name Value +Transactions_multi_engine 1 +BEGIN; +INSERT INTO t2 VALUES (104); +INSERT INTO t3 VALUES (105); +COMMIT; +SHOW STATUS LIKE "Transactions_multi_engine"; +Variable_name Value +Transactions_multi_engine 2 +UPDATE t2, t3 SET t2.a=106, t3.a=107 WHERE t2.a=104 AND t3.a=105; +SHOW STATUS LIKE "Transactions_multi_engine"; +Variable_name Value +Transactions_multi_engine 3 +SET sql_log_bin=1; +INSERT INTO t1 VALUES (200); +SHOW STATUS LIKE "Transactions_multi_engine"; +Variable_name Value +Transactions_multi_engine 3 +INSERT INTO t2 VALUES (201); +SHOW STATUS LIKE "Transactions_multi_engine"; +Variable_name Value +Transactions_multi_engine 3 +INSERT INTO t3 VALUES (201); +SHOW STATUS LIKE "Transactions_multi_engine"; +Variable_name Value +Transactions_multi_engine 3 +BEGIN; +INSERT INTO t3 VALUES (202); +INSERT INTO t2 VALUES (203); +COMMIT; +SHOW STATUS LIKE "Transactions_multi_engine"; +Variable_name Value +Transactions_multi_engine 4 +BEGIN; +INSERT INTO t2 VALUES (204); +INSERT INTO t3 VALUES (205); +COMMIT; +SHOW STATUS LIKE "Transactions_multi_engine"; +Variable_name Value +Transactions_multi_engine 5 +UPDATE t2, t3 SET t2.a=206, t3.a=207 WHERE t2.a=204 AND t3.a=205; +SHOW STATUS LIKE "Transactions_multi_engine"; +Variable_name Value +Transactions_multi_engine 6 +DELETE FROM t1 WHERE a >= 100; +DELETE FROM t2 WHERE a >= 100; +DELETE FROM t3 WHERE a >= 100; +connection server_2; +include/stop_slave.inc +SET sql_log_bin=0; +DROP TABLE mysql.gtid_slave_pos_tokudb; +DROP TABLE mysql.gtid_slave_pos_myisam_redundant; +DROP TABLE mysql.gtid_slave_pos_innodb_redundant; +SET sql_log_bin=1; +FLUSH NO_WRITE_TO_BINLOG STATUS; +include/start_slave.inc +SHOW STATUS LIKE "%transactions%engine"; +Variable_name Value +Rpl_transactions_multi_engine 0 +Transactions_gtid_foreign_engine 0 +Transactions_multi_engine 0 +connection server_1; +INSERT INTO t1 VALUES (100); +connection server_2; +SHOW STATUS LIKE "%transactions%engine"; +Variable_name Value +Rpl_transactions_multi_engine 0 +Transactions_gtid_foreign_engine 0 +Transactions_multi_engine 0 +connection server_1; +INSERT INTO t2 VALUES (101); +connection server_2; +SHOW STATUS LIKE "%transactions%engine"; +Variable_name Value +Rpl_transactions_multi_engine 0 +Transactions_gtid_foreign_engine 0 +Transactions_multi_engine 0 +connection server_1; +INSERT INTO t3 VALUES (101); +connection server_2; +SHOW STATUS LIKE "%transactions%engine"; +Variable_name Value +Rpl_transactions_multi_engine 0 +Transactions_gtid_foreign_engine 1 +Transactions_multi_engine 0 +connection server_1; +BEGIN; +INSERT INTO t3 VALUES (102); +INSERT INTO t2 VALUES (103); +COMMIT; +connection server_2; +SHOW STATUS LIKE "%transactions%engine"; +Variable_name Value +Rpl_transactions_multi_engine 1 +Transactions_gtid_foreign_engine 1 +Transactions_multi_engine 1 +connection server_1; +BEGIN; +INSERT INTO t2 VALUES (104); +INSERT INTO t3 VALUES (105); +COMMIT; +connection server_2; +SHOW STATUS LIKE "%transactions%engine"; +Variable_name Value +Rpl_transactions_multi_engine 2 +Transactions_gtid_foreign_engine 1 +Transactions_multi_engine 2 +connection server_1; +UPDATE t2, t3 SET t2.a=106, t3.a=107 WHERE t2.a=104 AND t3.a=105; +connection server_2; +SHOW STATUS LIKE "%transactions%engine"; +Variable_name Value +Rpl_transactions_multi_engine 3 +Transactions_gtid_foreign_engine 1 +Transactions_multi_engine 3 +connection server_2; +connection server_2; +SHOW VARIABLES LIKE 'log_bin'; +Variable_name Value +log_bin OFF +include/start_slave.inc +SHOW STATUS LIKE "%transactions%engine"; +Variable_name Value +Rpl_transactions_multi_engine 0 +Transactions_gtid_foreign_engine 0 +Transactions_multi_engine 0 +connection server_1; +INSERT INTO t1 VALUES (200); +connection server_2; +SHOW STATUS LIKE "%transactions%engine"; +Variable_name Value +Rpl_transactions_multi_engine 0 +Transactions_gtid_foreign_engine 0 +Transactions_multi_engine 0 +connection server_1; +INSERT INTO t2 VALUES (201); +connection server_2; +SHOW STATUS LIKE "%transactions%engine"; +Variable_name Value +Rpl_transactions_multi_engine 0 +Transactions_gtid_foreign_engine 0 +Transactions_multi_engine 0 +connection server_1; +INSERT INTO t3 VALUES (201); +connection server_2; +SHOW STATUS LIKE "%transactions%engine"; +Variable_name Value +Rpl_transactions_multi_engine 0 +Transactions_gtid_foreign_engine 1 +Transactions_multi_engine 0 +connection server_1; +BEGIN; +INSERT INTO t3 VALUES (202); +INSERT INTO t2 VALUES (203); +COMMIT; +connection server_2; +SHOW STATUS LIKE "%transactions%engine"; +Variable_name Value +Rpl_transactions_multi_engine 1 +Transactions_gtid_foreign_engine 1 +Transactions_multi_engine 1 +connection server_1; +BEGIN; +INSERT INTO t2 VALUES (204); +INSERT INTO t3 VALUES (205); +COMMIT; +connection server_2; +SHOW STATUS LIKE "%transactions%engine"; +Variable_name Value +Rpl_transactions_multi_engine 2 +Transactions_gtid_foreign_engine 1 +Transactions_multi_engine 2 +connection server_1; +UPDATE t2, t3 SET t2.a=206, t3.a=207 WHERE t2.a=204 AND t3.a=205; +connection server_2; +SHOW STATUS LIKE "%transactions%engine"; +Variable_name Value +Rpl_transactions_multi_engine 3 +Transactions_gtid_foreign_engine 1 +Transactions_multi_engine 3 +connection server_2; +SET sql_log_bin=0; +DROP TABLE mysql.gtid_slave_pos_innodb; +SET sql_log_bin=1; +connection server_1; +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; +include/rpl_end.inc diff --git a/storage/tokudb/mysql-test/tokudb_rpl/t/mdev12179.test b/storage/tokudb/mysql-test/tokudb_rpl/t/mdev12179.test new file mode 100644 index 00000000000..ceb119cd0dc --- /dev/null +++ b/storage/tokudb/mysql-test/tokudb_rpl/t/mdev12179.test @@ -0,0 +1,232 @@ +--source include/have_tokudb.inc +--source include/have_innodb.inc +--source include/master-slave.inc + +--connection server_2 +--source include/stop_slave.inc +CHANGE MASTER TO master_use_gtid=slave_pos; +SET sql_log_bin=0; +CREATE TABLE mysql.gtid_slave_pos_innodb LIKE mysql.gtid_slave_pos; +ALTER TABLE mysql.gtid_slave_pos_innodb ENGINE=InnoDB; +CREATE TABLE mysql.gtid_slave_pos_tokudb LIKE mysql.gtid_slave_pos; +ALTER TABLE mysql.gtid_slave_pos_tokudb ENGINE=TokuDB; +CREATE TABLE mysql.gtid_slave_pos_myisam_redundant LIKE mysql.gtid_slave_pos; +CREATE TABLE mysql.gtid_slave_pos_innodb_redundant LIKE mysql.gtid_slave_pos; +ALTER TABLE mysql.gtid_slave_pos_innodb_redundant ENGINE=InnoDB; +call mtr.add_suppression("Ignoring redundant table.*since.*has the same storage engine"); +--source include/start_slave.inc + +--connection server_1 +CREATE TABLE t1 (a INT PRIMARY KEY); +CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=InnoDB; +CREATE TABLE t3 (a INT PRIMARY KEY) ENGINE=TokuDB; +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (1); +INSERT INTO t3 VALUES (1); +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; +SELECT * FROM t3 ORDER BY a; +--save_master_pos + +--connection server_2 +--sync_with_master +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; +SELECT * FROM t3 ORDER BY a; +SELECT * FROM mysql.gtid_slave_pos ORDER BY sub_id; +SELECT * FROM ( SELECT * FROM mysql.gtid_slave_pos_innodb + UNION ALL SELECT * FROM mysql.gtid_slave_pos_innodb_redundant) inner_select + ORDER BY sub_id; +SELECT * FROM mysql.gtid_slave_pos_tokudb ORDER BY sub_id; + + +# Test status variable Transactions_multi_engine. +--connection server_2 +FLUSH NO_WRITE_TO_BINLOG STATUS; +SET sql_log_bin=0; +SHOW STATUS LIKE "Transactions_multi_engine"; +INSERT INTO t1 VALUES (100); +SHOW STATUS LIKE "Transactions_multi_engine"; +INSERT INTO t2 VALUES (101); +SHOW STATUS LIKE "Transactions_multi_engine"; +INSERT INTO t3 VALUES (101); +SHOW STATUS LIKE "Transactions_multi_engine"; +BEGIN; +INSERT INTO t3 VALUES (102); +INSERT INTO t2 VALUES (103); +COMMIT; +SHOW STATUS LIKE "Transactions_multi_engine"; +BEGIN; +INSERT INTO t2 VALUES (104); +INSERT INTO t3 VALUES (105); +COMMIT; +SHOW STATUS LIKE "Transactions_multi_engine"; +UPDATE t2, t3 SET t2.a=106, t3.a=107 WHERE t2.a=104 AND t3.a=105; +SHOW STATUS LIKE "Transactions_multi_engine"; +# Try again with binlog enabled. +SET sql_log_bin=1; +INSERT INTO t1 VALUES (200); +SHOW STATUS LIKE "Transactions_multi_engine"; +INSERT INTO t2 VALUES (201); +SHOW STATUS LIKE "Transactions_multi_engine"; +INSERT INTO t3 VALUES (201); +SHOW STATUS LIKE "Transactions_multi_engine"; +BEGIN; +INSERT INTO t3 VALUES (202); +INSERT INTO t2 VALUES (203); +COMMIT; +SHOW STATUS LIKE "Transactions_multi_engine"; +BEGIN; +INSERT INTO t2 VALUES (204); +INSERT INTO t3 VALUES (205); +COMMIT; +SHOW STATUS LIKE "Transactions_multi_engine"; +UPDATE t2, t3 SET t2.a=206, t3.a=207 WHERE t2.a=204 AND t3.a=205; +SHOW STATUS LIKE "Transactions_multi_engine"; + +DELETE FROM t1 WHERE a >= 100; +DELETE FROM t2 WHERE a >= 100; +DELETE FROM t3 WHERE a >= 100; + + +# Test status variables Rpl_transactions_multi_engine and Transactions_gtid_foreign_engine. +# Have mysql.gtid_slave_pos* for myisam and innodb but not tokudb. +--connection server_2 +--source include/stop_slave.inc +SET sql_log_bin=0; +DROP TABLE mysql.gtid_slave_pos_tokudb; +DROP TABLE mysql.gtid_slave_pos_myisam_redundant; +DROP TABLE mysql.gtid_slave_pos_innodb_redundant; +SET sql_log_bin=1; +FLUSH NO_WRITE_TO_BINLOG STATUS; +--source include/start_slave.inc +SHOW STATUS LIKE "%transactions%engine"; + +--connection server_1 +INSERT INTO t1 VALUES (100); +--save_master_pos +--connection server_2 +--sync_with_master +SHOW STATUS LIKE "%transactions%engine"; + +--connection server_1 +INSERT INTO t2 VALUES (101); +--save_master_pos +--connection server_2 +--sync_with_master +SHOW STATUS LIKE "%transactions%engine"; + +--connection server_1 +INSERT INTO t3 VALUES (101); +--save_master_pos +--connection server_2 +--sync_with_master +SHOW STATUS LIKE "%transactions%engine"; + +--connection server_1 +BEGIN; +INSERT INTO t3 VALUES (102); +INSERT INTO t2 VALUES (103); +COMMIT; +--save_master_pos +--connection server_2 +--sync_with_master +SHOW STATUS LIKE "%transactions%engine"; + +--connection server_1 +BEGIN; +INSERT INTO t2 VALUES (104); +INSERT INTO t3 VALUES (105); +COMMIT; +--save_master_pos +--connection server_2 +--sync_with_master +SHOW STATUS LIKE "%transactions%engine"; + +--connection server_1 +UPDATE t2, t3 SET t2.a=106, t3.a=107 WHERE t2.a=104 AND t3.a=105; +--save_master_pos +--connection server_2 +--sync_with_master +SHOW STATUS LIKE "%transactions%engine"; + +# Now the same thing, but without binlogging on the slave. +--connection server_2 +--write_file $MYSQLTEST_VARDIR/tmp/mysqld.2.expect +wait +EOF +--shutdown_server 30 +--source include/wait_until_disconnected.inc + +# Restart without binary log. +--append_file $MYSQLTEST_VARDIR/tmp/mysqld.2.expect +restart: --skip-log-bin +EOF + +--connection server_2 +--enable_reconnect +--source include/wait_until_connected_again.inc +SHOW VARIABLES LIKE 'log_bin'; +--source include/start_slave.inc +SHOW STATUS LIKE "%transactions%engine"; + +--connection server_1 +INSERT INTO t1 VALUES (200); +--save_master_pos +--connection server_2 +--sync_with_master +SHOW STATUS LIKE "%transactions%engine"; + +--connection server_1 +INSERT INTO t2 VALUES (201); +--save_master_pos +--connection server_2 +--sync_with_master +SHOW STATUS LIKE "%transactions%engine"; + +--connection server_1 +INSERT INTO t3 VALUES (201); +--save_master_pos +--connection server_2 +--sync_with_master +SHOW STATUS LIKE "%transactions%engine"; + +--connection server_1 +BEGIN; +INSERT INTO t3 VALUES (202); +INSERT INTO t2 VALUES (203); +COMMIT; +--save_master_pos +--connection server_2 +--sync_with_master +SHOW STATUS LIKE "%transactions%engine"; + +--connection server_1 +BEGIN; +INSERT INTO t2 VALUES (204); +INSERT INTO t3 VALUES (205); +COMMIT; +--save_master_pos +--connection server_2 +--sync_with_master +SHOW STATUS LIKE "%transactions%engine"; + +--connection server_1 +UPDATE t2, t3 SET t2.a=206, t3.a=207 WHERE t2.a=204 AND t3.a=205; +--save_master_pos +--connection server_2 +--sync_with_master +SHOW STATUS LIKE "%transactions%engine"; + + +--connection server_2 +SET sql_log_bin=0; +DROP TABLE mysql.gtid_slave_pos_innodb; +SET sql_log_bin=1; + +--connection server_1 +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; + +--source include/rpl_end.inc |