Skip to content

Commit cbc1898

Browse files
committed
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>
1 parent 834c013 commit cbc1898

File tree

4 files changed

+123
-2
lines changed

4 files changed

+123
-2
lines changed

mysql-test/suite/rpl/r/rpl_mdev382.result

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ a`
355355
show binlog events in 'master-bin.000002' from <binlog_start>;
356356
Log_name Pos Event_type Server_id End_log_pos Info
357357
master-bin.000002 # Gtid 1 # GTID #-#-#
358-
master-bin.000002 # Query 1 # DELETE FROM `db1``; select 'oops!'`.`t``1`
358+
master-bin.000002 # Query 1 # TRUNCATE TABLE `db1``; select 'oops!'`.`t``1`
359359
connection slave;
360360
include/start_slave.inc
361361
connection master;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
include/master-slave.inc
2+
[connection master]
3+
connection master;
4+
create table t (val int) engine=MEMORY;
5+
# DELETE trigger should never be activated
6+
create trigger tr after delete on t for each row update t2 set val = 1;
7+
insert into t values (1),(2);
8+
include/save_master_gtid.inc
9+
connection slave;
10+
include/sync_with_master_gtid.inc
11+
# Check pre-restart values
12+
include/diff_tables.inc [master:test.t,slave:test.t]
13+
# Restarting master should empty master and slave `t`
14+
connection master;
15+
include/rpl_restart_server.inc [server_number=1]
16+
connection master;
17+
# Validating MEMORY table on master is empty after restart
18+
# MYSQL_BINLOG datadir/binlog_file --result-file=assert_file
19+
include/assert_grep.inc [Query to truncate the MEMORY table should be the contents of the new event]
20+
# Ensuring slave MEMORY table is empty
21+
connection master;
22+
include/save_master_gtid.inc
23+
connection slave;
24+
include/sync_with_master_gtid.inc
25+
include/diff_tables.inc [master:test.t,slave:test.t]
26+
# Ensure new events replicate correctly
27+
connection master;
28+
insert into t values (3),(4);
29+
include/save_master_gtid.inc
30+
connection slave;
31+
include/sync_with_master_gtid.inc
32+
# Validate values on slave, after master restart, do not include those inserted previously
33+
include/diff_tables.inc [master:test.t,slave:test.t]
34+
#
35+
# Cleanup
36+
connection master;
37+
drop table t;
38+
include/rpl_end.inc
39+
# End of rpl_memory_engine_truncate_on_restart.test
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#
2+
# This test ensures that a table with engine=memory is kept consistent with
3+
# the slave when the master restarts. That is, when the master is restarted, it
4+
# should binlog a new TRUNCATE TABLE command for tables with MEMORY engines,
5+
# such that after the slave executes these events, its MEMORY-engine tables
6+
# should be empty.
7+
#
8+
# References:
9+
# MDEV-25607: Auto-generated DELETE from HEAP table can break replication
10+
#
11+
--source include/master-slave.inc
12+
13+
--connection master
14+
create table t (val int) engine=MEMORY;
15+
16+
-- echo # DELETE trigger should never be activated
17+
create trigger tr after delete on t for each row update t2 set val = 1;
18+
19+
insert into t values (1),(2);
20+
--source include/save_master_gtid.inc
21+
--connection slave
22+
--source include/sync_with_master_gtid.inc
23+
24+
-- echo # Check pre-restart values
25+
--let $diff_tables= master:test.t,slave:test.t
26+
--source include/diff_tables.inc
27+
28+
--echo # Restarting master should empty master and slave `t`
29+
--connection master
30+
--let $seq_no_before_restart= `SELECT REGEXP_REPLACE(@@global.gtid_binlog_pos, "0-1-", "")`
31+
--let $rpl_server_number= 1
32+
--source include/rpl_restart_server.inc
33+
34+
--connection master
35+
--echo # Validating MEMORY table on master is empty after restart
36+
--let $table_size= `select count(*) from t`
37+
if ($table_size)
38+
{
39+
--echo # MEMORY table is not empty
40+
--die MEMORY table is not empty
41+
}
42+
--let $seq_no_after_restart= `SELECT REGEXP_REPLACE(@@global.gtid_binlog_pos, "0-1-", "")`
43+
if ($seq_no_before_restart == $seq_no_after_restart)
44+
{
45+
--echo # Event to empty MEMORY table was not binlogged
46+
--die Event to empty MEMORY table was not binlogged
47+
}
48+
49+
--let $binlog_file= query_get_value(SHOW MASTER STATUS, File, 1)
50+
--let $datadir=`select @@datadir`
51+
--let assert_file= $MYSQLTEST_VARDIR/tmp/binlog_decoded.out
52+
--echo # MYSQL_BINLOG datadir/binlog_file --result-file=assert_file
53+
--exec $MYSQL_BINLOG $datadir/$binlog_file --result-file=$assert_file
54+
55+
--let assert_text= Query to truncate the MEMORY table should be the contents of the new event
56+
--let assert_count= 1
57+
--let assert_select= TRUNCATE TABLE
58+
--source include/assert_grep.inc
59+
60+
--echo # Ensuring slave MEMORY table is empty
61+
--connection master
62+
--source include/save_master_gtid.inc
63+
--connection slave
64+
--source include/sync_with_master_gtid.inc
65+
--source include/diff_tables.inc
66+
67+
--echo # Ensure new events replicate correctly
68+
--connection master
69+
insert into t values (3),(4);
70+
--source include/save_master_gtid.inc
71+
--connection slave
72+
--source include/sync_with_master_gtid.inc
73+
74+
--echo # Validate values on slave, after master restart, do not include those inserted previously
75+
--source include/diff_tables.inc
76+
77+
--echo #
78+
--echo # Cleanup
79+
--connection master
80+
drop table t;
81+
--source include/rpl_end.inc
82+
--echo # End of rpl_memory_engine_truncate_on_restart.test

sql/sql_base.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2993,7 +2993,7 @@ static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry)
29932993
String query(query_buf, sizeof(query_buf), system_charset_info);
29942994

29952995
query.length(0);
2996-
query.append("DELETE FROM ");
2996+
query.append("TRUNCATE TABLE ");
29972997
append_identifier(thd, &query, &share->db);
29982998
query.append(".");
29992999
append_identifier(thd, &query, &share->table_name);

0 commit comments

Comments
 (0)