Commit cbc1898e authored by Brandon Nesterenko's avatar Brandon Nesterenko

MDEV-25607: Auto-generated DELETE from HEAP table can break replication

The special logic used by the memory storage engine
to keep slaves in sync with the master on a restart can
break replication. In particular, after a restart, the
master writes DELETE statements in the binlog for
each MEMORY-based table so the slave can empty its
data. If the DELETE is not executable, e.g. due to
invalid triggers, the slave will error and fail, whereas
the master will never see the problem.

Instead of DELETE statements, use TRUNCATE to
keep slaves in-sync with the master, thereby bypassing
triggers.

Reviewed By:
===========
Kristian Nielsen <knielsen@knielsen-hq.org>
Andrei Elkin <andrei.elkin@mariadb.com>
parent 834c013b
......@@ -355,7 +355,7 @@ a`
show binlog events in 'master-bin.000002' from <binlog_start>;
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000002 # Gtid 1 # GTID #-#-#
master-bin.000002 # Query 1 # DELETE FROM `db1``; select 'oops!'`.`t``1`
master-bin.000002 # Query 1 # TRUNCATE TABLE `db1``; select 'oops!'`.`t``1`
connection slave;
include/start_slave.inc
connection master;
......
include/master-slave.inc
[connection master]
connection master;
create table t (val int) engine=MEMORY;
# DELETE trigger should never be activated
create trigger tr after delete on t for each row update t2 set val = 1;
insert into t values (1),(2);
include/save_master_gtid.inc
connection slave;
include/sync_with_master_gtid.inc
# Check pre-restart values
include/diff_tables.inc [master:test.t,slave:test.t]
# Restarting master should empty master and slave `t`
connection master;
include/rpl_restart_server.inc [server_number=1]
connection master;
# Validating MEMORY table on master is empty after restart
# MYSQL_BINLOG datadir/binlog_file --result-file=assert_file
include/assert_grep.inc [Query to truncate the MEMORY table should be the contents of the new event]
# Ensuring slave MEMORY table is empty
connection master;
include/save_master_gtid.inc
connection slave;
include/sync_with_master_gtid.inc
include/diff_tables.inc [master:test.t,slave:test.t]
# Ensure new events replicate correctly
connection master;
insert into t values (3),(4);
include/save_master_gtid.inc
connection slave;
include/sync_with_master_gtid.inc
# Validate values on slave, after master restart, do not include those inserted previously
include/diff_tables.inc [master:test.t,slave:test.t]
#
# Cleanup
connection master;
drop table t;
include/rpl_end.inc
# End of rpl_memory_engine_truncate_on_restart.test
#
# This test ensures that a table with engine=memory is kept consistent with
# the slave when the master restarts. That is, when the master is restarted, it
# should binlog a new TRUNCATE TABLE command for tables with MEMORY engines,
# such that after the slave executes these events, its MEMORY-engine tables
# should be empty.
#
# References:
# MDEV-25607: Auto-generated DELETE from HEAP table can break replication
#
--source include/master-slave.inc
--connection master
create table t (val int) engine=MEMORY;
-- echo # DELETE trigger should never be activated
create trigger tr after delete on t for each row update t2 set val = 1;
insert into t values (1),(2);
--source include/save_master_gtid.inc
--connection slave
--source include/sync_with_master_gtid.inc
-- echo # Check pre-restart values
--let $diff_tables= master:test.t,slave:test.t
--source include/diff_tables.inc
--echo # Restarting master should empty master and slave `t`
--connection master
--let $seq_no_before_restart= `SELECT REGEXP_REPLACE(@@global.gtid_binlog_pos, "0-1-", "")`
--let $rpl_server_number= 1
--source include/rpl_restart_server.inc
--connection master
--echo # Validating MEMORY table on master is empty after restart
--let $table_size= `select count(*) from t`
if ($table_size)
{
--echo # MEMORY table is not empty
--die MEMORY table is not empty
}
--let $seq_no_after_restart= `SELECT REGEXP_REPLACE(@@global.gtid_binlog_pos, "0-1-", "")`
if ($seq_no_before_restart == $seq_no_after_restart)
{
--echo # Event to empty MEMORY table was not binlogged
--die Event to empty MEMORY table was not binlogged
}
--let $binlog_file= query_get_value(SHOW MASTER STATUS, File, 1)
--let $datadir=`select @@datadir`
--let assert_file= $MYSQLTEST_VARDIR/tmp/binlog_decoded.out
--echo # MYSQL_BINLOG datadir/binlog_file --result-file=assert_file
--exec $MYSQL_BINLOG $datadir/$binlog_file --result-file=$assert_file
--let assert_text= Query to truncate the MEMORY table should be the contents of the new event
--let assert_count= 1
--let assert_select= TRUNCATE TABLE
--source include/assert_grep.inc
--echo # Ensuring slave MEMORY table is empty
--connection master
--source include/save_master_gtid.inc
--connection slave
--source include/sync_with_master_gtid.inc
--source include/diff_tables.inc
--echo # Ensure new events replicate correctly
--connection master
insert into t values (3),(4);
--source include/save_master_gtid.inc
--connection slave
--source include/sync_with_master_gtid.inc
--echo # Validate values on slave, after master restart, do not include those inserted previously
--source include/diff_tables.inc
--echo #
--echo # Cleanup
--connection master
drop table t;
--source include/rpl_end.inc
--echo # End of rpl_memory_engine_truncate_on_restart.test
......@@ -2993,7 +2993,7 @@ static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry)
String query(query_buf, sizeof(query_buf), system_charset_info);
query.length(0);
query.append("DELETE FROM ");
query.append("TRUNCATE TABLE ");
append_identifier(thd, &query, &share->db);
query.append(".");
append_identifier(thd, &query, &share->table_name);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment