Skip to content

Commit bd6e256

Browse files
committed
MDEV-38243 Write binlog row events for changes done by cascading FK operations
This commit implements a feature which changes the handling of cascading foreign key operations to write the changes of cascading operations into binlog. The applying of such transaction, in the slave node, will apply just the binlog events, and does not execute the actual foreign key cascade operation. This will simplify the slave side replication applying and make it more predictable in terms of potential interference with other parallel applying happning in the node. This feature can be turned ON/OFF by new variable: rpl_use_binlog_events_for_fk_cascade, with default value OFF The actual implementation is largely by windsurf. The commit has also two mtr tests for testing rpl_use_binlog_events_for_fk_cascade feature: rpl.rpl_fk_cascade_binlog_row and rpl.rpl_fk_set_null_binlog_row
1 parent dc89915 commit bd6e256

26 files changed

+1027
-15
lines changed

include/mysql/plugin.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,7 @@ int thd_in_lock_tables(const MYSQL_THD thd);
687687
int thd_tablespace_op(const MYSQL_THD thd);
688688
long long thd_test_options(const MYSQL_THD thd, long long test_options);
689689
int thd_sql_command(const MYSQL_THD thd);
690+
690691
struct DDL_options_st;
691692
struct DDL_options_st *thd_ddl_options(const MYSQL_THD thd);
692693
void thd_storage_lock_wait(MYSQL_THD thd, long long value);

include/mysql/service_thd_binlog.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/* Copyright (c) 2026, MariaDB Corporation.
2+
3+
This program is free software; you can redistribute it and/or modify
4+
it under the terms of the GNU General Public License as published by
5+
the Free Software Foundation; version 2 of the License.
6+
7+
This program is distributed in the hope that it will be useful,
8+
but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+
GNU General Public License for more details.
11+
12+
You should have received a copy of the GNU General Public License
13+
along with this program; if not, write to the Free Software
14+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
15+
16+
#pragma once
17+
18+
#ifdef __cplusplus
19+
extern "C" {
20+
#endif
21+
22+
struct TABLE;
23+
class Event_log;
24+
class binlog_cache_data;
25+
26+
int thd_is_current_stmt_binlog_format_row(const MYSQL_THD thd);
27+
28+
int thd_rpl_use_binlog_events_for_fk_cascade(const MYSQL_THD thd);
29+
30+
void thd_binlog_mark_fk_cascade_events(MYSQL_THD thd);
31+
32+
int thd_binlog_update_row(MYSQL_THD thd, struct TABLE *table,
33+
class Event_log *bin_log,
34+
class binlog_cache_data *cache_data,
35+
int is_trans, unsigned long row_image,
36+
const unsigned char *before_record,
37+
const unsigned char *after_record);
38+
39+
int thd_binlog_delete_row(MYSQL_THD thd, struct TABLE *table,
40+
class Event_log *bin_log,
41+
class binlog_cache_data *cache_data,
42+
int is_trans, unsigned long row_image,
43+
const unsigned char *before_record);
44+
45+
#ifdef __cplusplus
46+
}
47+
#endif

include/mysql/service_wsrep.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ extern struct wsrep_service_st {
146146
#define wsrep_report_bf_lock_wait(T,I) wsrep_service->wsrep_report_bf_lock_wait(T,I)
147147
#define wsrep_thd_set_PA_unsafe(T) wsrep_service->wsrep_thd_set_PA_unsafe_func(T)
148148
#define wsrep_get_domain_id(T) wsrep_service->wsrep_get_domain_id_func(T)
149+
#define wsrep_emulate_binlog(T) wsrep_service->wsrep_emulate_binlog_func(T)
149150
#else
150151

151152
#define MYSQL_SERVICE_WSREP_STATIC_INCLUDED
@@ -256,5 +257,6 @@ extern "C" void wsrep_report_bf_lock_wait(const THD *thd,
256257
/* declare parallel applying unsafety for the THD */
257258
extern "C" void wsrep_thd_set_PA_unsafe(MYSQL_THD thd);
258259
extern "C" uint32 wsrep_get_domain_id();
260+
extern "C" my_bool wsrep_emulate_binlog(const MYSQL_THD thd);
259261
#endif
260262
#endif /* MYSQL_SERVICE_WSREP_INCLUDED */

mysql-test/main/mysqld--help.result

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,6 +1274,9 @@ The following specify which files/extra groups are read (specified before remain
12741274
play when stop slave is executed.
12751275
--rpl-semi-sync-slave-trace-level=#
12761276
The tracing level for semi-sync replication.
1277+
--rpl-use-binlog-events-for-fk-cascade
1278+
If enabled, the master will write row events for foreign
1279+
key cascade operations.
12771280
--safe-mode Skip some optimize stages (for testing). Deprecated.
12781281
--safe-user-create Don't allow new user creation by the user who has no
12791282
write privileges to the mysql.user table.
@@ -1952,6 +1955,7 @@ rpl-semi-sync-slave-delay-master FALSE
19521955
rpl-semi-sync-slave-enabled FALSE
19531956
rpl-semi-sync-slave-kill-conn-timeout 5
19541957
rpl-semi-sync-slave-trace-level 32
1958+
rpl-use-binlog-events-for-fk-cascade FALSE
19551959
safe-user-create FALSE
19561960
secure-auth TRUE
19571961
secure-file-priv (No default value)
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
include/master-slave.inc
2+
[connection master]
3+
SET @old_binlog_format := @@session.binlog_format;
4+
SET @old_default_storage_engine := @@session.default_storage_engine;
5+
SET @old_rpl_use_binlog_events_for_fk_cascade := @@session.rpl_use_binlog_events_for_fk_cascade;
6+
SET SESSION default_storage_engine=InnoDB;
7+
SET SESSION binlog_format=ROW;
8+
SET SESSION rpl_use_binlog_events_for_fk_cascade=0;
9+
CREATE TABLE p (
10+
id INT PRIMARY KEY,
11+
v INT
12+
) ENGINE=InnoDB;
13+
CREATE TABLE c (
14+
id INT PRIMARY KEY,
15+
pid INT,
16+
v INT,
17+
KEY(pid),
18+
CONSTRAINT fk FOREIGN KEY (pid) REFERENCES p(id)
19+
ON DELETE CASCADE
20+
ON UPDATE CASCADE
21+
) ENGINE=InnoDB;
22+
INSERT INTO p VALUES (1, 10);
23+
INSERT INTO c VALUES (100, 1, 20);
24+
UPDATE p SET id=2 WHERE id=1;
25+
connection slave;
26+
connection slave;
27+
SELECT pid FROM c ORDER BY id;
28+
pid
29+
2
30+
connection master;
31+
DELETE FROM p WHERE id=2;
32+
connection slave;
33+
connection slave;
34+
SELECT COUNT(*) FROM p;
35+
COUNT(*)
36+
0
37+
SELECT COUNT(*) FROM c;
38+
COUNT(*)
39+
0
40+
connection master;
41+
FLUSH BINARY LOGS;
42+
NOT FOUND /### UPDATE `test`.`c`/ in fk_cascade_binlog_row_off.sql
43+
NOT FOUND /### DELETE FROM `test`.`c`/ in fk_cascade_binlog_row_off.sql
44+
DROP TABLE c, p;
45+
SET SESSION rpl_use_binlog_events_for_fk_cascade=1;
46+
CREATE TABLE p (
47+
id INT PRIMARY KEY,
48+
v INT
49+
) ENGINE=InnoDB;
50+
CREATE TABLE c (
51+
id INT PRIMARY KEY,
52+
pid INT,
53+
v INT,
54+
KEY(pid),
55+
CONSTRAINT fk FOREIGN KEY (pid) REFERENCES p(id)
56+
ON DELETE CASCADE
57+
ON UPDATE CASCADE
58+
) ENGINE=InnoDB;
59+
INSERT INTO p VALUES (1, 10);
60+
INSERT INTO c VALUES (100, 1, 20);
61+
UPDATE p SET id=2 WHERE id=1;
62+
connection slave;
63+
connection slave;
64+
SELECT pid FROM c ORDER BY id;
65+
pid
66+
2
67+
connection master;
68+
DELETE FROM p WHERE id=2;
69+
connection slave;
70+
connection slave;
71+
SELECT COUNT(*) FROM p;
72+
COUNT(*)
73+
0
74+
SELECT COUNT(*) FROM c;
75+
COUNT(*)
76+
0
77+
connection master;
78+
FLUSH BINARY LOGS;
79+
FOUND 1 /### UPDATE `test`.`c`/ in fk_cascade_binlog_row_on.sql
80+
FOUND 1 /### DELETE FROM `test`.`c`/ in fk_cascade_binlog_row_on.sql
81+
DROP TABLE c, p;
82+
SET SESSION default_storage_engine=@old_default_storage_engine;
83+
SET SESSION binlog_format=@old_binlog_format;
84+
SET SESSION rpl_use_binlog_events_for_fk_cascade=@old_rpl_use_binlog_events_for_fk_cascade;
85+
include/rpl_end.inc
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
include/master-slave.inc
2+
[connection master]
3+
SET @old_binlog_format := @@session.binlog_format;
4+
SET @old_default_storage_engine := @@session.default_storage_engine;
5+
SET @old_rpl_use_binlog_events_for_fk_cascade := @@session.rpl_use_binlog_events_for_fk_cascade;
6+
SET SESSION default_storage_engine=InnoDB;
7+
SET SESSION binlog_format=ROW;
8+
SET SESSION rpl_use_binlog_events_for_fk_cascade=0;
9+
CREATE TABLE p (
10+
id INT PRIMARY KEY,
11+
v INT
12+
) ENGINE=InnoDB;
13+
CREATE TABLE c (
14+
id INT PRIMARY KEY,
15+
pid INT NULL,
16+
v INT,
17+
KEY(pid),
18+
CONSTRAINT fk FOREIGN KEY (pid) REFERENCES p(id)
19+
ON DELETE SET NULL
20+
ON UPDATE SET NULL
21+
) ENGINE=InnoDB;
22+
INSERT INTO p VALUES (1, 10);
23+
INSERT INTO c VALUES (100, 1, 20);
24+
UPDATE p SET id=2 WHERE id=1;
25+
connection slave;
26+
SELECT pid FROM c ORDER BY id;
27+
pid
28+
NULL
29+
connection master;
30+
FLUSH BINARY LOGS;
31+
NOT FOUND /### UPDATE `test`.`c`/ in fk_set_null_binlog_row_off.sql
32+
DROP TABLE c, p;
33+
SET SESSION rpl_use_binlog_events_for_fk_cascade=1;
34+
CREATE TABLE p (
35+
id INT PRIMARY KEY,
36+
v INT
37+
) ENGINE=InnoDB;
38+
CREATE TABLE c (
39+
id INT PRIMARY KEY,
40+
pid INT NULL,
41+
v INT,
42+
KEY(pid),
43+
CONSTRAINT fk FOREIGN KEY (pid) REFERENCES p(id)
44+
ON DELETE SET NULL
45+
ON UPDATE SET NULL
46+
) ENGINE=InnoDB;
47+
INSERT INTO p VALUES (1, 10);
48+
INSERT INTO c VALUES (100, 1, 20);
49+
UPDATE p SET id=2 WHERE id=1;
50+
connection slave;
51+
SELECT pid FROM c ORDER BY id;
52+
pid
53+
NULL
54+
connection master;
55+
FLUSH BINARY LOGS;
56+
FOUND 1 /### UPDATE `test`.`c`/ in fk_set_null_binlog_row_on.sql
57+
DROP TABLE c, p;
58+
SET SESSION default_storage_engine=@old_default_storage_engine;
59+
SET SESSION binlog_format=@old_binlog_format;
60+
SET SESSION rpl_use_binlog_events_for_fk_cascade=@old_rpl_use_binlog_events_for_fk_cascade;
61+
include/rpl_end.inc
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
--source include/have_innodb.inc
2+
--source include/have_log_bin.inc
3+
--source include/have_binlog_format_row.inc
4+
5+
--source include/master-slave.inc
6+
7+
SET @old_binlog_format := @@session.binlog_format;
8+
SET @old_default_storage_engine := @@session.default_storage_engine;
9+
SET @old_rpl_use_binlog_events_for_fk_cascade := @@session.rpl_use_binlog_events_for_fk_cascade;
10+
11+
SET SESSION default_storage_engine=InnoDB;
12+
SET SESSION binlog_format=ROW;
13+
14+
# Phase A: feature OFF (no explicit FK cascade row events in binlog)
15+
SET SESSION rpl_use_binlog_events_for_fk_cascade=0;
16+
17+
CREATE TABLE p (
18+
id INT PRIMARY KEY,
19+
v INT
20+
) ENGINE=InnoDB;
21+
22+
CREATE TABLE c (
23+
id INT PRIMARY KEY,
24+
pid INT,
25+
v INT,
26+
KEY(pid),
27+
CONSTRAINT fk FOREIGN KEY (pid) REFERENCES p(id)
28+
ON DELETE CASCADE
29+
ON UPDATE CASCADE
30+
) ENGINE=InnoDB;
31+
32+
INSERT INTO p VALUES (1, 10);
33+
INSERT INTO c VALUES (100, 1, 20);
34+
35+
UPDATE p SET id=2 WHERE id=1;
36+
37+
sync_slave_with_master;
38+
connection slave;
39+
SELECT pid FROM c ORDER BY id;
40+
41+
connection master;
42+
DELETE FROM p WHERE id=2;
43+
44+
sync_slave_with_master;
45+
connection slave;
46+
SELECT COUNT(*) FROM p;
47+
SELECT COUNT(*) FROM c;
48+
49+
connection master;
50+
51+
--let $binlog = query_get_value(SHOW MASTER STATUS, File, 1)
52+
--let $datadir = `SELECT @@datadir`
53+
54+
FLUSH BINARY LOGS;
55+
56+
--exec $MYSQL_BINLOG --verbose --verbose --base64-output=DECODE-ROWS $datadir/$binlog > $MYSQLTEST_VARDIR/tmp/fk_cascade_binlog_row_off.sql
57+
58+
--let SEARCH_PATTERN= ### UPDATE `test`.`c`
59+
--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/fk_cascade_binlog_row_off.sql
60+
--let SEARCH_ABORT= FOUND
61+
--source include/search_pattern_in_file.inc
62+
63+
--let SEARCH_PATTERN= ### DELETE FROM `test`.`c`
64+
--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/fk_cascade_binlog_row_off.sql
65+
--let SEARCH_ABORT= FOUND
66+
--source include/search_pattern_in_file.inc
67+
68+
DROP TABLE c, p;
69+
70+
# Phase B: feature ON (explicit FK cascade row events in binlog)
71+
SET SESSION rpl_use_binlog_events_for_fk_cascade=1;
72+
73+
CREATE TABLE p (
74+
id INT PRIMARY KEY,
75+
v INT
76+
) ENGINE=InnoDB;
77+
78+
CREATE TABLE c (
79+
id INT PRIMARY KEY,
80+
pid INT,
81+
v INT,
82+
KEY(pid),
83+
CONSTRAINT fk FOREIGN KEY (pid) REFERENCES p(id)
84+
ON DELETE CASCADE
85+
ON UPDATE CASCADE
86+
) ENGINE=InnoDB;
87+
88+
INSERT INTO p VALUES (1, 10);
89+
INSERT INTO c VALUES (100, 1, 20);
90+
91+
UPDATE p SET id=2 WHERE id=1;
92+
93+
sync_slave_with_master;
94+
connection slave;
95+
SELECT pid FROM c ORDER BY id;
96+
97+
connection master;
98+
DELETE FROM p WHERE id=2;
99+
100+
sync_slave_with_master;
101+
connection slave;
102+
SELECT COUNT(*) FROM p;
103+
SELECT COUNT(*) FROM c;
104+
105+
connection master;
106+
107+
--let $binlog = query_get_value(SHOW MASTER STATUS, File, 1)
108+
--let $datadir = `SELECT @@datadir`
109+
110+
FLUSH BINARY LOGS;
111+
112+
--exec $MYSQL_BINLOG --verbose --verbose --base64-output=DECODE-ROWS $datadir/$binlog > $MYSQLTEST_VARDIR/tmp/fk_cascade_binlog_row_on.sql
113+
114+
--let SEARCH_PATTERN= ### UPDATE `test`.`c`
115+
--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/fk_cascade_binlog_row_on.sql
116+
--let SEARCH_ABORT= NOT FOUND|FOUND 0|FOUND [2-9]
117+
--source include/search_pattern_in_file.inc
118+
119+
--let SEARCH_PATTERN= ### DELETE FROM `test`.`c`
120+
--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/fk_cascade_binlog_row_on.sql
121+
--let SEARCH_ABORT= NOT FOUND|FOUND 0|FOUND [2-9]
122+
--source include/search_pattern_in_file.inc
123+
124+
DROP TABLE c, p;
125+
126+
SET SESSION default_storage_engine=@old_default_storage_engine;
127+
SET SESSION binlog_format=@old_binlog_format;
128+
SET SESSION rpl_use_binlog_events_for_fk_cascade=@old_rpl_use_binlog_events_for_fk_cascade;
129+
130+
--remove_file $MYSQLTEST_VARDIR/tmp/fk_cascade_binlog_row_off.sql
131+
--remove_file $MYSQLTEST_VARDIR/tmp/fk_cascade_binlog_row_on.sql
132+
133+
--source include/rpl_end.inc

0 commit comments

Comments
 (0)