MDEV-12179: Per-engine mysql.gtid_slave_pos table

Merge into MariaDB 10.3.
This commit is contained in:
Kristian Nielsen 2017-07-03 09:33:41 +02:00
commit 1d91910b94
50 changed files with 2969 additions and 161 deletions

View File

@ -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

View File

@ -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)
{

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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';

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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";

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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);

View File

@ -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;

View File

@ -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, &gtid, 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, &gtid, rgi);
rpl_global_gtid_slave_state->update_state_hash(sub_id, &gtid, 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)))
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, &gtid, 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, &gtid, rgi);
rpl_global_gtid_slave_state->update_state_hash(sub_id, &gtid, hton, rgi);
/*
Increment the global status commit count variable

View File

@ -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",
&gtid_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;

View File

@ -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;

View File

@ -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(&gtid_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)
@ -465,6 +471,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(&gtid_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.
@ -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, &gtid_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;
}
*next_ptr_ptr= cur;
next_ptr_ptr= &cur->next;
}
cur= next;
}
*next_ptr_ptr= NULL;
/*
Delete the highest sub_id element from the old list, and put it back as
the single-element new list.
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, &gtid) ||
!(sub_id= next_sub_id(gtid.domain_id)) ||
record_gtid(thd, &gtid, sub_id, false, in_statement) ||
update(gtid.domain_id, gtid.server_id, sub_id, gtid.seq_no, NULL))
record_gtid(thd, &gtid, 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(&gtid_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(&gtid_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),

View File

@ -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);
};

View File

@ -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,18 +1570,18 @@ 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");
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)
{
@ -1586,6 +1589,8 @@ uint any_slave_sql_running()
if (mi->rli.slave_running != MYSQL_SLAVE_NOT_RUN)
count++;
}
}
if (!already_locked)
mysql_mutex_unlock(&LOCK_active_mi);
DBUG_RETURN(count);
}

View File

@ -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 */

View File

@ -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;
}

View File

@ -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,25 +1529,27 @@ 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),
if (!(entry= (struct gtid_pos_element *)my_malloc(sizeof(*entry),
MYF(MY_WME))))
{
my_error(ER_OUTOFMEMORY, MYF(0), (int)sizeof(*entry));
@ -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,45 +1569,6 @@ rpl_load_gtid_slave_state(THD *thd)
}
}
}
mysql_mutex_lock(&rpl_global_gtid_slave_state->LOCK_slave_state);
if (rpl_global_gtid_slave_state->loaded)
{
mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state);
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)))
{
mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state);
my_error(ER_OUT_OF_RESOURCES, MYF(0));
goto end;
}
}
for (i= 0; i < hash.records; ++i)
{
entry= (struct local_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))
{
mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state);
my_error(ER_OUT_OF_RESOURCES, MYF(0));
goto end;
}
}
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:
@ -1633,16 +1580,456 @@ end:
}
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)
{
mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state);
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,
tmp_entry.hton,
NULL)))
{
mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state);
my_error(ER_OUT_OF_RESOURCES, MYF(0));
goto end;
}
}
for (i= 0; i < hash.records; ++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))
{
mysql_mutex_unlock(&rpl_global_gtid_slave_state->LOCK_slave_state);
my_error(ER_OUT_OF_RESOURCES, MYF(0));
goto end;
}
}
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);
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)
{
ha_commit_trans(thd, FALSE);
ha_commit_trans(thd, TRUE);
close_thread_tables(thd);
thd->mdl_context.release_transactional_locks();
}
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;
}
void
rpl_group_info::reinit(Relay_log_info *rli)
{

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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);
@ -391,6 +570,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.
@ -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)

View File

@ -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 */

View File

@ -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;

View File

@ -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(&gtid_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.

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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