MDEV-12179: Per-engine mysql.gtid_slave_pos table
Intermediate commit. This commit implements that record_gtid() selects a gtid_slave_posXXX table with a storage engine already in use by current transaction, if any. The default table mysql.gtid_slave_pos is used if no match can be found on storage engine, or for GTID position updates with no specific storage engine. Table discovery of mysql.gtid_slave_pos* happens on initial GTID state load as well as on every START SLAVE. Some effort is made to make this possible without additional locking. New tables are added using lock-free atomics. Removing tables requires stopping all slaves first. A warning is given in the error log when a table is removed but a non-stopped slave still has a reference to it. If multiple mysql.gtid_slave_posXXX tables with same storage engine exist, one is chosen arbitrarily to be used, with a warning in the error log. GTID data from all tables is still read, but only one among redundant tables with same storage engine will be updated.
This commit is contained in:
parent
3501a5356e
commit
6a84473c28
141
mysql-test/suite/multi_source/gtid_slave_pos.result
Normal file
141
mysql-test/suite/multi_source/gtid_slave_pos.result
Normal file
@ -0,0 +1,141 @@
|
||||
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 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 5
|
||||
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");
|
||||
include/save_master_gtid.inc
|
||||
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 5
|
||||
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
|
||||
set default_master_connection = 'slave2';
|
||||
START SLAVE;
|
||||
include/wait_for_slave_to_start.inc
|
||||
include/sync_with_master_gtid.inc
|
||||
SELECT * FROM t1 ORDER BY a;
|
||||
a b
|
||||
1 initial
|
||||
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 5
|
||||
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;
|
156
mysql-test/suite/multi_source/gtid_slave_pos.test
Normal file
156
mysql-test/suite/multi_source/gtid_slave_pos.test
Normal file
@ -0,0 +1,156 @@
|
||||
--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
|
||||
|
||||
--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");
|
||||
--source include/save_master_gtid.inc
|
||||
|
||||
--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
|
||||
set default_master_connection = 'slave2';
|
||||
START SLAVE;
|
||||
--source include/wait_for_slave_to_start.inc
|
||||
|
||||
--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
|
@ -1,6 +1,6 @@
|
||||
--source include/have_innodb.inc
|
||||
--let $rpl_topology=1->2
|
||||
--source include/rpl_init.inc
|
||||
--source include/have_innodb.inc
|
||||
|
||||
--connection server_2
|
||||
--source include/stop_slave.inc
|
||||
|
@ -470,6 +470,48 @@ 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= my_atomic_loadptr_explicit(>id_pos_tables, MY_MEMORY_ORDER_ACQUIRE);
|
||||
|
||||
Ha_trx_info *ha_info= thd->transaction.all.ha_list;
|
||||
while (ha_info)
|
||||
{
|
||||
void *trx_hton= ha_info->ht();
|
||||
table_entry= list;
|
||||
while (table_entry)
|
||||
{
|
||||
if (table_entry->table_hton == trx_hton)
|
||||
{
|
||||
*out_tablename= table_entry->table_name;
|
||||
return;
|
||||
}
|
||||
table_entry= table_entry->next;
|
||||
}
|
||||
ha_info= ha_info->next();
|
||||
}
|
||||
/*
|
||||
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= my_atomic_loadptr_explicit(&default_gtid_pos_table,
|
||||
MY_MEMORY_ORDER_ACQUIRE);
|
||||
*out_tablename= default_entry->table_name;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Write a gtid to the replication slave state table.
|
||||
|
||||
@ -500,6 +542,7 @@ rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id,
|
||||
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;
|
||||
@ -517,6 +560,7 @@ rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id,
|
||||
|
||||
if (!in_statement)
|
||||
thd->reset_for_next_command();
|
||||
select_gtid_pos_table(thd, >id_pos_table_name);
|
||||
|
||||
DBUG_EXECUTE_IF("gtid_inject_record_gtid",
|
||||
{
|
||||
@ -547,10 +591,8 @@ 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;
|
||||
@ -1168,18 +1210,35 @@ rpl_slave_state::free_gtid_pos_tables(struct rpl_slave_state::gtid_pos_table *li
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
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(struct rpl_slave_state::gtid_pos_table *new_list)
|
||||
rpl_slave_state::set_gtid_pos_tables_list(rpl_slave_state::gtid_pos_table *new_list,
|
||||
rpl_slave_state::gtid_pos_table *default_entry)
|
||||
{
|
||||
struct gtid_pos_table *old_list;
|
||||
gtid_pos_table *old_list;
|
||||
|
||||
mysql_mutex_assert_owner(&LOCK_slave_state);
|
||||
old_list= gtid_pos_tables;
|
||||
gtid_pos_tables= new_list;
|
||||
my_atomic_storeptr_explicit(>id_pos_tables, new_list, MY_MEMORY_ORDER_RELEASE);
|
||||
my_atomic_storeptr_explicit(&default_gtid_pos_table, default_entry,
|
||||
MY_MEMORY_ORDER_RELEASE);
|
||||
free_gtid_pos_tables(old_list);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
rpl_slave_state::add_gtid_pos_table(rpl_slave_state::gtid_pos_table *entry)
|
||||
{
|
||||
mysql_mutex_assert_owner(&LOCK_slave_state);
|
||||
entry->next= gtid_pos_tables;
|
||||
my_atomic_storeptr_explicit(>id_pos_tables, entry, MY_MEMORY_ORDER_RELEASE);
|
||||
}
|
||||
|
||||
|
||||
struct rpl_slave_state::gtid_pos_table *
|
||||
rpl_slave_state::alloc_gtid_pos_table(LEX_STRING *table_name, void *hton)
|
||||
{
|
||||
|
@ -177,7 +177,27 @@ 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.
|
||||
*/
|
||||
struct gtid_pos_table *gtid_pos_tables;
|
||||
/* The default entry in gtid_pos_tables, mysql.gtid_slave_pos. */
|
||||
struct gtid_pos_table *default_gtid_pos_table;
|
||||
bool loaded;
|
||||
|
||||
rpl_slave_state();
|
||||
@ -188,6 +208,7 @@ struct rpl_slave_state
|
||||
int update(uint32 domain_id, uint32 server_id, uint64 sub_id,
|
||||
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, void **out_hton);
|
||||
uint64 next_sub_id(uint32 domain_id);
|
||||
@ -208,7 +229,9 @@ struct rpl_slave_state
|
||||
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(struct gtid_pos_table *new_list);
|
||||
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);
|
||||
void free_gtid_pos_tables(struct gtid_pos_table *list);
|
||||
};
|
||||
|
184
sql/rpl_rli.cc
184
sql/rpl_rli.cc
@ -1647,24 +1647,70 @@ 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;
|
||||
}
|
||||
|
||||
if (!(p= rpl_global_gtid_slave_state->alloc_gtid_pos_table(table_name, hton)))
|
||||
return 1;
|
||||
p->next= data->table_list;
|
||||
data->table_list= p;
|
||||
if (is_default)
|
||||
data->default_entry= p;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
struct rpl_slave_state::gtid_pos_table *p;
|
||||
void *hton;
|
||||
|
||||
if ((err= scan_one_gtid_slave_pos_table(thd, data->hash, data->array,
|
||||
table_name, &hton)))
|
||||
return err;
|
||||
if (!(p= rpl_global_gtid_slave_state->alloc_gtid_pos_table(table_name, hton)))
|
||||
return 1;
|
||||
p->next= data->table_list;
|
||||
data->table_list= p;
|
||||
return 0;
|
||||
return process_gtid_pos_table(thd, table_name, hton, data);
|
||||
}
|
||||
|
||||
|
||||
@ -1687,6 +1733,7 @@ rpl_load_gtid_slave_state(THD *thd)
|
||||
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);
|
||||
@ -1706,6 +1753,23 @@ rpl_load_gtid_slave_state(THD *thd)
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (!cb_data.table_list)
|
||||
{
|
||||
my_error(ER_NO_SUCH_TABLE, MYF(0), "mysql",
|
||||
rpl_gtid_slave_state_table_name.str);
|
||||
err= 1;
|
||||
goto end;
|
||||
}
|
||||
else 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;
|
||||
}
|
||||
|
||||
for (i= 0; i < array.elements; ++i)
|
||||
{
|
||||
get_dynamic(&array, (uchar *)&tmp_entry, i);
|
||||
@ -1735,7 +1799,8 @@ rpl_load_gtid_slave_state(THD *thd)
|
||||
}
|
||||
}
|
||||
|
||||
rpl_global_gtid_slave_state->set_gtid_pos_tables_list(cb_data.table_list);
|
||||
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);
|
||||
@ -1753,12 +1818,10 @@ end:
|
||||
static int
|
||||
find_gtid_pos_tables_cb(THD *thd, LEX_STRING *table_name, void *arg)
|
||||
{
|
||||
struct rpl_slave_state::gtid_pos_table **table_list_ptr=
|
||||
static_cast<struct rpl_slave_state::gtid_pos_table **>(arg);
|
||||
load_gtid_state_cb_data *data= static_cast<load_gtid_state_cb_data *>(arg);
|
||||
TABLE_LIST tlist;
|
||||
TABLE *table= NULL;
|
||||
int err;
|
||||
struct rpl_slave_state::gtid_pos_table *p;
|
||||
|
||||
thd->reset_for_next_command();
|
||||
tlist.init_one_table(STRING_WITH_LEN("mysql"), table_name->str,
|
||||
@ -1769,14 +1832,7 @@ find_gtid_pos_tables_cb(THD *thd, LEX_STRING *table_name, void *arg)
|
||||
|
||||
if ((err= gtid_check_rpl_slave_state_table(table)))
|
||||
goto end;
|
||||
|
||||
if (!(p= rpl_global_gtid_slave_state->alloc_gtid_pos_table(table_name, table->s->db_type())))
|
||||
err= 1;
|
||||
else
|
||||
{
|
||||
p->next= *table_list_ptr;
|
||||
*table_list_ptr= p;
|
||||
}
|
||||
err= process_gtid_pos_table(thd, table_name, table->s->db_type(), data);
|
||||
|
||||
end:
|
||||
if (table)
|
||||
@ -1801,7 +1857,8 @@ int
|
||||
find_gtid_slave_pos_tables(THD *thd)
|
||||
{
|
||||
int err= 0;
|
||||
struct rpl_slave_state::gtid_pos_table *table_list;
|
||||
load_gtid_state_cb_data cb_data;
|
||||
bool any_running;
|
||||
|
||||
mysql_mutex_lock(&rpl_global_gtid_slave_state->LOCK_slave_state);
|
||||
bool loaded= rpl_global_gtid_slave_state->loaded;
|
||||
@ -1809,18 +1866,95 @@ find_gtid_slave_pos_tables(THD *thd)
|
||||
if (!loaded)
|
||||
return 0;
|
||||
|
||||
table_list= NULL;
|
||||
if ((err= scan_all_gtid_slave_pos_table(thd, find_gtid_pos_tables_cb, &table_list)))
|
||||
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;
|
||||
}
|
||||
else 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;
|
||||
}
|
||||
|
||||
any_running= any_slave_sql_running();
|
||||
mysql_mutex_lock(&rpl_global_gtid_slave_state->LOCK_slave_state);
|
||||
rpl_global_gtid_slave_state->set_gtid_pos_tables_list(table_list);
|
||||
table_list= NULL;
|
||||
if (!any_running)
|
||||
{
|
||||
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_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_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);
|
||||
|
||||
end:
|
||||
if (table_list)
|
||||
rpl_global_gtid_slave_state->free_gtid_pos_tables(table_list);
|
||||
if (cb_data.table_list)
|
||||
rpl_global_gtid_slave_state->free_gtid_pos_tables(cb_data.table_list);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
60
storage/tokudb/mysql-test/tokudb_rpl/r/mdev12179.result
Normal file
60
storage/tokudb/mysql-test/tokudb_rpl/r/mdev12179.result
Normal file
@ -0,0 +1,60 @@
|
||||
include/master-slave.inc
|
||||
[connection master]
|
||||
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
|
||||
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
|
||||
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
|
||||
SET sql_log_bin=0;
|
||||
DROP TABLE mysql.gtid_slave_pos_innodb;
|
||||
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;
|
||||
DROP TABLE t1;
|
||||
DROP TABLE t2;
|
||||
DROP TABLE t3;
|
||||
include/rpl_end.inc
|
56
storage/tokudb/mysql-test/tokudb_rpl/t/mdev12179.test
Normal file
56
storage/tokudb/mysql-test/tokudb_rpl/t/mdev12179.test
Normal file
@ -0,0 +1,56 @@
|
||||
--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;
|
||||
|
||||
|
||||
--connection server_2
|
||||
SET sql_log_bin=0;
|
||||
DROP TABLE mysql.gtid_slave_pos_innodb;
|
||||
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;
|
||||
|
||||
--connection server_1
|
||||
DROP TABLE t1;
|
||||
DROP TABLE t2;
|
||||
DROP TABLE t3;
|
||||
|
||||
--source include/rpl_end.inc
|
Loading…
x
Reference in New Issue
Block a user