Skip to content

Commit a5dc12e

Browse files
committed
MDEV-28310 Missing binlog data for INSERT .. ON DUPLICATE KEY UPDATE
MDEV-21810 MBR: Unexpected "Unsafe statement" warning for unsafe IODKU MDEV-17614 fixes to replication unsafety for INSERT ON DUP KEY UPDATE on two or more unique key table left a flaw. The fixes checked the safety condition per each inserted record with the idea to catch a user-created value to an autoincrement column and when that succeeds the autoincrement column would become the source of unsafety too. It was not expected that after a duplicate error the next record's write_set may become different and the unsafe decision for that specific record will be computed to screw the Query's binlogging state and when @@binlog_format is MIXED nothing gets bin-logged. This case has been already fixed in 10.5.2 by 91ab42a that relocated/optimized THD::decide_logging_format_low() out of the record insert loop. The safety decision is computed once and at the right time. Pertinent parts of the commit are cherry-picked. Also a spurious warning about unsafety is removed when MIXED @@binlog_format; original MDEV-17614 test result corrected. The original test of MDEV-17614 is extended and made more readable.
1 parent 141ab97 commit a5dc12e

10 files changed

+322
-54
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--- r/rpl_iodku.result 2022-05-04 18:51:24.956414404 +0300
2+
+++ r/rpl_iodku,stmt.reject 2022-05-04 18:51:49.520106231 +0300
3+
@@ -1,10 +1,15 @@
4+
include/master-slave.inc
5+
[connection master]
6+
+call mtr.add_suppression("Unsafe statement written to the binary log using statement");
7+
CREATE TABLE t1 (id INT PRIMARY KEY AUTO_INCREMENT, a INT, b INT, c INT,
8+
UNIQUE (a), UNIQUE (b)) ENGINE=innodb;
9+
INSERT INTO t1 (`a`,`c`) VALUES (1,1), (2,1) ON DUPLICATE KEY UPDATE c = 1;
10+
+Warnings:
11+
+Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. INSERT... ON DUPLICATE KEY UPDATE on a table with more than one UNIQUE KEY is unsafe
12+
# UNSAFE
13+
INSERT INTO t1 (`a`,`c`) VALUES (3, 1),(2,1), (1,1) ON DUPLICATE KEY UPDATE c = a * 10 + VALUES(c);
14+
+Warnings:
15+
+Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. INSERT... ON DUPLICATE KEY UPDATE on a table with more than one UNIQUE KEY is unsafe
16+
SELECT * from t1;
17+
id a b c
18+
1 1 NULL 11
19+
@@ -17,6 +22,8 @@
20+
INSERT INTO t1 VALUES (1,10,1);
21+
# eligable for the statement format run unsafe warning
22+
INSERT INTO t1 VALUES (2,20,2) ON DUPLICATE KEY UPDATE c = 100;
23+
+Warnings:
24+
+Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. INSERT... ON DUPLICATE KEY UPDATE on a table with more than one UNIQUE KEY is unsafe
25+
# not eligable: no warning in the statement format run
26+
INSERT INTO t1 (`a`,`c`) VALUES (3, 1) ON DUPLICATE KEY UPDATE c = 99;
27+
SELECT * from t1;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
include/master-slave.inc
2+
[connection master]
3+
CREATE TABLE t1 (id INT PRIMARY KEY AUTO_INCREMENT, a INT, b INT, c INT,
4+
UNIQUE (a), UNIQUE (b)) ENGINE=innodb;
5+
INSERT INTO t1 (`a`,`c`) VALUES (1,1), (2,1) ON DUPLICATE KEY UPDATE c = 1;
6+
# UNSAFE
7+
INSERT INTO t1 (`a`,`c`) VALUES (3, 1),(2,1), (1,1) ON DUPLICATE KEY UPDATE c = a * 10 + VALUES(c);
8+
SELECT * from t1;
9+
id a b c
10+
1 1 NULL 11
11+
2 2 NULL 21
12+
3 3 NULL 1
13+
connection slave;
14+
include/diff_tables.inc [master:t1,slave:t1]
15+
connection master;
16+
CREATE OR REPLACE TABLE t1 (a INT, b INT, c INT, UNIQUE (a), UNIQUE (b)) ENGINE=innodb;
17+
INSERT INTO t1 VALUES (1,10,1);
18+
# eligable for the statement format run unsafe warning
19+
INSERT INTO t1 VALUES (2,20,2) ON DUPLICATE KEY UPDATE c = 100;
20+
# not eligable: no warning in the statement format run
21+
INSERT INTO t1 (`a`,`c`) VALUES (3, 1) ON DUPLICATE KEY UPDATE c = 99;
22+
SELECT * from t1;
23+
a b c
24+
1 10 1
25+
2 20 2
26+
3 NULL 1
27+
connection slave;
28+
include/diff_tables.inc [master:t1,slave:t1]
29+
connection master;
30+
DROP TABLE t1;
31+
connection slave;
32+
include/rpl_end.inc

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

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
include/master-slave.inc
22
[connection master]
3+
# Case 1: UNSAFE
34
call mtr.add_suppression("Unsafe statement written to the binary log using statement format");
45
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY , b INT,
56
UNIQUE(b), c int) engine=innodb;
@@ -37,6 +38,7 @@ drop table t1;
3738
connection slave;
3839
start slave;
3940
include/wait_for_slave_to_start.inc
41+
# Case 2: UNSAFE
4042
connection master;
4143
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY auto_increment, b INT,
4244
UNIQUE(b), c int) engine=innodb;
@@ -45,8 +47,12 @@ connection master;
4547
INSERT INTO t1 VALUES (default, 1, 1);
4648
BEGIN;
4749
INSERT INTO t1 VALUES (default, 1, 2) ON DUPLICATE KEY UPDATE b=VALUES(b), c=VALUES(c);
50+
Warnings:
51+
Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. INSERT... ON DUPLICATE KEY UPDATE on a table with more than one UNIQUE KEY is unsafe
4852
connection master1;
4953
INSERT INTO t1 VALUES(default, 2, 3) ON DUPLICATE KEY UPDATE b=VALUES(b), c=VALUES(c);
54+
Warnings:
55+
Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. INSERT... ON DUPLICATE KEY UPDATE on a table with more than one UNIQUE KEY is unsafe
5056
connection master;
5157
COMMIT;
5258
SELECT * FROM t1;
@@ -62,6 +68,7 @@ a b c
6268
connection master;
6369
drop table t1;
6470
connection slave;
71+
# Case 3A: UNSAFE
6572
connection master;
6673
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY, b INT,
6774
UNIQUE(b), c int, d int ) engine=innodb;
@@ -93,6 +100,67 @@ a b c d
93100
connection master;
94101
drop table t1;
95102
connection slave;
103+
# Case 3B: UNSAFE - all column specified.
104+
connection master;
105+
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY, b INT,
106+
UNIQUE(b), c int, d int ) engine=innodb;
107+
connection slave;
108+
connection master;
109+
INSERT INTO t1 VALUES (1, 1, 1, 1);
110+
BEGIN;
111+
INSERT INTO t1 VALUES (2, NULL, 2, 2) ON DUPLICATE KEY UPDATE c=VALUES(c);
112+
Warnings:
113+
Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. INSERT... ON DUPLICATE KEY UPDATE on a table with more than one UNIQUE KEY is unsafe
114+
connection master1;
115+
INSERT INTO t1 VALUES(3, NULL, 2, 3) ON DUPLICATE KEY UPDATE c=VALUES(c);
116+
Warnings:
117+
Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. INSERT... ON DUPLICATE KEY UPDATE on a table with more than one UNIQUE KEY is unsafe
118+
connection master;
119+
COMMIT;
120+
SELECT * FROM t1;
121+
a b c d
122+
1 1 1 1
123+
2 NULL 2 2
124+
3 NULL 2 3
125+
connection slave;
126+
#same data as master
127+
SELECT * FROM t1;
128+
a b c d
129+
1 1 1 1
130+
2 NULL 2 2
131+
3 NULL 2 3
132+
connection master;
133+
drop table t1;
134+
connection slave;
135+
# Case 3C: SAFE - only one unique key (PK) specified.
136+
connection master;
137+
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY, b INT,
138+
UNIQUE(b), c int, d int ) engine=innodb;
139+
connection slave;
140+
connection master;
141+
INSERT INTO t1 VALUES (1, 1, 1, 1);
142+
BEGIN;
143+
INSERT INTO t1 (`a`, `c`, `d`) VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE c=99;
144+
connection master1;
145+
INSERT INTO t1 (`a`, `c`, `d`) VALUES(3, 2, 3) ON DUPLICATE KEY UPDATE c=100;
146+
connection master;
147+
COMMIT;
148+
SELECT * FROM t1;
149+
a b c d
150+
1 1 1 1
151+
2 NULL 2 2
152+
3 NULL 2 3
153+
connection slave;
154+
#same data as master
155+
SELECT * FROM t1;
156+
a b c d
157+
1 1 1 1
158+
2 NULL 2 2
159+
3 NULL 2 3
160+
connection master;
161+
drop table t1;
162+
connection slave;
163+
# Case 4: UNSAFE
96164
connection master;
97165
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY auto_increment, b INT,
98166
UNIQUE(b), c int) engine=innodb;
@@ -101,8 +169,12 @@ connection master;
101169
INSERT INTO t1 VALUES (1, 1, 1);
102170
BEGIN;
103171
INSERT INTO t1 VALUES (2, 1, 2) ON DUPLICATE KEY UPDATE b=VALUES(b), c=VALUES(c);
172+
Warnings:
173+
Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. INSERT... ON DUPLICATE KEY UPDATE on a table with more than one UNIQUE KEY is unsafe
104174
connection master1;
105175
INSERT INTO t1 VALUES(2, 2, 3) ON DUPLICATE KEY UPDATE b=VALUES(b), c=VALUES(c);
176+
Warnings:
177+
Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. INSERT... ON DUPLICATE KEY UPDATE on a table with more than one UNIQUE KEY is unsafe
106178
connection master;
107179
COMMIT;
108180
SELECT * FROM t1;

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
include/master-slave.inc
22
[connection master]
3-
call mtr.add_suppression("Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT");
43
CREATE TABLE t1(id INT AUTO_INCREMENT, i INT, PRIMARY KEY (id)) ENGINE=INNODB;
54
CREATE TABLE t2(id INT AUTO_INCREMENT, i INT, PRIMARY KEY (id)) ENGINE=INNODB;
65
CREATE TRIGGER trig1 AFTER INSERT ON t1
@@ -50,13 +49,9 @@ connection master;
5049
DROP TABLE t1;
5150
CREATE TABLE t1(i INT, j INT, UNIQUE KEY(i), UNIQUE KEY(j)) ENGINE=INNODB;
5251
INSERT INTO t1 (i,j) VALUES (1,2) ON DUPLICATE KEY UPDATE j=j+1;
53-
Warnings:
54-
Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. INSERT... ON DUPLICATE KEY UPDATE on a table with more than one UNIQUE KEY is unsafe
5552
START TRANSACTION;
5653
LOCK TABLES t1 WRITE;
5754
INSERT INTO t1 (i,j) VALUES (1,2) ON DUPLICATE KEY UPDATE j=j+1;
58-
Warnings:
59-
Note 1592 Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. INSERT... ON DUPLICATE KEY UPDATE on a table with more than one UNIQUE KEY is unsafe
6055
UNLOCK TABLES;
6156
COMMIT;
6257
connection slave;

mysql-test/suite/rpl/t/rpl_iodku.test

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
--source include/have_innodb.inc
2+
--source include/master-slave.inc
3+
4+
if (`select @@binlog_format = "statement"`)
5+
{
6+
call mtr.add_suppression("Unsafe statement written to the binary log using statement");
7+
}
8+
9+
## MDEV-28310 loss of binlog event for multi-record IODKU
10+
# Check that the duplicate key error does not cause
11+
# loss of replication event for IODKU that specifies values
12+
# for at least two unique columns per record.
13+
# "Implicit" NULL value of the auto-increment column also counts.
14+
15+
CREATE TABLE t1 (id INT PRIMARY KEY AUTO_INCREMENT, a INT, b INT, c INT,
16+
UNIQUE (a), UNIQUE (b)) ENGINE=innodb;
17+
INSERT INTO t1 (`a`,`c`) VALUES (1,1), (2,1) ON DUPLICATE KEY UPDATE c = 1;
18+
--echo # UNSAFE
19+
# because of two keys involved: a UK and PK even though implicitly via auto-inc
20+
INSERT INTO t1 (`a`,`c`) VALUES (3, 1),(2,1), (1,1) ON DUPLICATE KEY UPDATE c = a * 10 + VALUES(c);
21+
SELECT * from t1;
22+
23+
--sync_slave_with_master
24+
--let $diff_tables = master:t1,slave:t1
25+
--source include/diff_tables.inc
26+
27+
## MDEV-21810 MBR: Unexpected "Unsafe statement" warning for unsafe IODKU
28+
# Unnecessary unsafe statement warning is not error-logged anymore.
29+
30+
31+
--connection master
32+
CREATE OR REPLACE TABLE t1 (a INT, b INT, c INT, UNIQUE (a), UNIQUE (b)) ENGINE=innodb;
33+
INSERT INTO t1 VALUES (1,10,1);
34+
--echo # eligable for the statement format run unsafe warning
35+
INSERT INTO t1 VALUES (2,20,2) ON DUPLICATE KEY UPDATE c = 100;
36+
--echo # not eligable: no warning in the statement format run
37+
INSERT INTO t1 (`a`,`c`) VALUES (3, 1) ON DUPLICATE KEY UPDATE c = 99;
38+
SELECT * from t1;
39+
40+
--sync_slave_with_master
41+
--let $diff_tables = master:t1,slave:t1
42+
--source include/diff_tables.inc
43+
44+
# Cleanup
45+
--connection master
46+
DROP TABLE t1;
47+
--sync_slave_with_master
48+
49+
50+
--source include/rpl_end.inc

mysql-test/suite/rpl/t/rpl_mdev_17614.test

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,22 @@ source include/have_debug.inc;
22
source include/have_innodb.inc;
33
-- source include/have_binlog_format_statement.inc
44
source include/master-slave.inc;
5-
# MDEV-17614
6-
# INSERT on dup key update is replication unsafe
7-
# There can be three case
8-
# 1. 2 unique key, Replication is unsafe.
9-
# 2. 2 unique key , with one auto increment key, Safe to replicate because Innodb will acquire gap lock
10-
# 3. n no of unique keys (n>1) but insert is only in 1 unique key
11-
# 4. 2 unique key , with one auto increment key(but user gives auto inc value), unsafe to replicate
5+
# MDEV-17614 INSERT on dup key update is replication unsafe
6+
#
7+
# The following cases are tested below:
8+
# 1. 2 unique key, replication is UNSAFE
9+
# 2. 2 unique key, with one auto increment key and implicit value to it.
10+
# It is UNSAFE because autoinc column values of being inserted records
11+
# are revealed dynamically, so unknown at the binlog-format decision time
12+
# and hence this pessimistic expectation
13+
# 3. 2 unique keys
14+
# A. insert is only in 1 unique key, still all colums are specified => UNSAFE
15+
# B. both unique keys are specified => UNSAFE
16+
# C. only one unique key is specified => SAFE (motivated by MDEV-28310)
17+
# 4. 2 unique key, with one auto increment key(but user gives auto inc value) =>
18+
# UNSAFE to replicate
1219

13-
# Case 1
20+
--echo # Case 1: UNSAFE
1421
call mtr.add_suppression("Unsafe statement written to the binary log using statement format");
1522
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY , b INT,
1623
UNIQUE(b), c int) engine=innodb;
@@ -42,7 +49,8 @@ drop table t1;
4249
connection slave;
4350
start slave;
4451
--source include/wait_for_slave_to_start.inc
45-
# Case 2
52+
53+
--echo # Case 2: UNSAFE
4654
--connection master
4755
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY auto_increment, b INT,
4856
UNIQUE(b), c int) engine=innodb;
@@ -64,7 +72,7 @@ connection master;
6472
drop table t1;
6573
--sync_slave_with_master
6674

67-
# Case 3
75+
--echo # Case 3A: UNSAFE
6876
--connection master
6977
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY, b INT,
7078
UNIQUE(b), c int, d int ) engine=innodb;
@@ -85,7 +93,50 @@ connection master;
8593
drop table t1;
8694
--sync_slave_with_master
8795

88-
# Case 4
96+
--echo # Case 3B: UNSAFE - all column specified.
97+
--connection master
98+
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY, b INT,
99+
UNIQUE(b), c int, d int ) engine=innodb;
100+
sync_slave_with_master;
101+
connection master;
102+
INSERT INTO t1 VALUES (1, 1, 1, 1);
103+
BEGIN;
104+
INSERT INTO t1 VALUES (2, NULL, 2, 2) ON DUPLICATE KEY UPDATE c=VALUES(c);
105+
--connection master1
106+
INSERT INTO t1 VALUES(3, NULL, 2, 3) ON DUPLICATE KEY UPDATE c=VALUES(c);
107+
--connection master
108+
COMMIT;
109+
SELECT * FROM t1;
110+
--sync_slave_with_master
111+
--echo #same data as master
112+
SELECT * FROM t1;
113+
connection master;
114+
drop table t1;
115+
--sync_slave_with_master
116+
117+
118+
--echo # Case 3C: SAFE - only one unique key (PK) specified.
119+
--connection master
120+
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY, b INT,
121+
UNIQUE(b), c int, d int ) engine=innodb;
122+
sync_slave_with_master;
123+
connection master;
124+
INSERT INTO t1 VALUES (1, 1, 1, 1);
125+
BEGIN;
126+
INSERT INTO t1 (`a`, `c`, `d`) VALUES (2, 2, 2) ON DUPLICATE KEY UPDATE c=99;
127+
--connection master1
128+
INSERT INTO t1 (`a`, `c`, `d`) VALUES(3, 2, 3) ON DUPLICATE KEY UPDATE c=100;
129+
--connection master
130+
COMMIT;
131+
SELECT * FROM t1;
132+
--sync_slave_with_master
133+
--echo #same data as master
134+
SELECT * FROM t1;
135+
connection master;
136+
drop table t1;
137+
--sync_slave_with_master
138+
139+
--echo # Case 4: UNSAFE
89140
--connection master
90141
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY auto_increment, b INT,
91142
UNIQUE(b), c int) engine=innodb;

mysql-test/suite/rpl/t/rpl_unsafe_statements.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
--source include/have_innodb.inc
2525
--source include/have_binlog_format_mixed.inc
2626
--source include/master-slave.inc
27-
call mtr.add_suppression("Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT");
27+
2828
# Case-1: BINLOG_STMT_UNSAFE_AUTOINC_COLUMNS
2929
# Statement is unsafe because it invokes a trigger or a
3030
# stored function that inserts into an AUTO_INCREMENT column.

0 commit comments

Comments
 (0)