BUG#39934: Slave stops for engine that only support row-based logging
General overview:
The logic for switching to row format when binlog_format=MIXED had
numerous flaws. The underlying problem was the lack of a consistent
architecture.
General purpose of this changeset:
This changeset introduces an architecture for switching to row format
when binlog_format=MIXED. It enforces the architecture where it has
to. It leaves some bugs to be fixed later. It adds extensive tests to
verify that unsafe statements work as expected and that appropriate
errors are produced by problems with the selection of binlog format.
It was not practical to split this into smaller pieces of work.
Problem 1:
To determine the logging mode, the code has to take several parameters
into account (namely: (1) the value of binlog_format; (2) the
capabilities of the engines; (3) the type of the current statement:
normal, unsafe, or row injection). These parameters may conflict in
several ways, namely:
- binlog_format=STATEMENT for a row injection
- binlog_format=STATEMENT for an unsafe statement
- binlog_format=STATEMENT for an engine only supporting row logging
- binlog_format=ROW for an engine only supporting statement logging
- statement is unsafe and engine does not support row logging
- row injection in a table that does not support statement logging
- statement modifies one table that does not support row logging and
one that does not support statement logging
Several of these conflicts were not detected, or were detected with
an inappropriate error message. The problem of BUG#39934 was that no
appropriate error message was written for the case when an engine
only supporting row logging executed a row injection with
binlog_format=ROW. However, all above cases must be handled.
Fix 1:
Introduce new error codes (sql/share/errmsg.txt). Ensure that all
conditions are detected and handled in decide_logging_format()
Problem 2:
The binlog format shall be determined once per statement, in
decide_logging_format(). It shall not be changed before or after that.
Before decide_logging_format() is called, all information necessary to
determine the logging format must be available. This principle ensures
that all unsafe statements are handled in a consistent way.
However, this principle is not followed:
thd->set_current_stmt_binlog_row_based_if_mixed() is called in several
places, including from code executing UPDATE..LIMIT,
INSERT..SELECT..LIMIT, DELETE..LIMIT, INSERT DELAYED, and
SET @@binlog_format. After Problem 1 was fixed, that caused
inconsistencies where these unsafe statements would not print the
appropriate warnings or errors for some of the conflicts.
Fix 2:
Remove calls to THD::set_current_stmt_binlog_row_based_if_mixed() from
code executed after decide_logging_format(). Compensate by calling the
set_current_stmt_unsafe() at parse time. This way, all unsafe statements
are detected by decide_logging_format().
Problem 3:
INSERT DELAYED is not unsafe: it is logged in statement format even if
binlog_format=MIXED, and no warning is printed even if
binlog_format=STATEMENT. This is BUG#45825.
Fix 3:
Made INSERT DELAYED set itself to unsafe at parse time. This allows
decide_logging_format() to detect that a warning should be printed or
the binlog_format changed.
Problem 4:
LIMIT clause were not marked as unsafe when executed inside stored
functions/triggers/views/prepared statements. This is
BUG#45785.
Fix 4:
Make statements containing the LIMIT clause marked as unsafe at
parse time, instead of at execution time. This allows propagating
unsafe-ness to the view.
2009-07-14 21:31:19 +02:00
|
|
|
# Every statement in this test is either executing under ROW or
|
|
|
|
# STATEMENT format, which requires the slave thread to be able to apply
|
|
|
|
# both statement and row events. Hence, we only need to execute this
|
|
|
|
# test for MIXED mode.
|
|
|
|
source include/have_binlog_format_mixed.inc;
|
|
|
|
|
2007-06-01 07:13:20 +02:00
|
|
|
source include/master-slave.inc;
|
2007-10-19 14:18:41 +02:00
|
|
|
source include/have_innodb.inc;
|
2007-06-01 07:13:20 +02:00
|
|
|
|
|
|
|
--echo **** On Slave ****
|
|
|
|
connection slave;
|
2007-10-19 14:18:41 +02:00
|
|
|
source include/have_innodb.inc;
|
2007-06-01 07:13:20 +02:00
|
|
|
STOP SLAVE;
|
2009-11-28 02:34:47 +03:00
|
|
|
--source include/wait_for_slave_to_stop.inc
|
2007-06-01 07:13:20 +02:00
|
|
|
|
|
|
|
--echo **** On Master ****
|
|
|
|
connection master;
|
|
|
|
SET SESSION BINLOG_FORMAT=ROW;
|
|
|
|
|
|
|
|
CREATE TABLE t1 (a INT, b INT);
|
|
|
|
CREATE TABLE t2 (c INT, d INT);
|
|
|
|
INSERT INTO t1 VALUES (1,1),(2,4),(3,9);
|
|
|
|
INSERT INTO t2 VALUES (1,1),(2,8),(3,27);
|
|
|
|
UPDATE t1,t2 SET b = d, d = b * 2 WHERE a = c;
|
|
|
|
source include/show_binlog_events.inc;
|
|
|
|
|
|
|
|
# These tables should be changed
|
|
|
|
SELECT * FROM t1;
|
|
|
|
SELECT * FROM t2;
|
|
|
|
save_master_pos;
|
|
|
|
|
|
|
|
--echo **** On Slave ****
|
|
|
|
connection slave;
|
|
|
|
|
|
|
|
# Stop when reaching the the first table map event.
|
2008-03-28 13:16:41 +01:00
|
|
|
START SLAVE UNTIL MASTER_LOG_FILE='master-bin.000001', MASTER_LOG_POS=762;
|
2009-11-28 02:34:47 +03:00
|
|
|
--source include/wait_for_slave_sql_to_stop.inc
|
2007-06-01 07:13:20 +02:00
|
|
|
--replace_result $MASTER_MYPORT MASTER_PORT
|
2007-06-13 15:16:33 +02:00
|
|
|
--replace_column 1 # 8 # 9 # 23 # 33 # 35 # 36 #
|
2007-06-01 07:13:20 +02:00
|
|
|
query_vertical SHOW SLAVE STATUS;
|
|
|
|
|
|
|
|
# Now we skip *one* table map event. If the execution starts right
|
|
|
|
# after that table map event, *one* of the involved tables will be
|
|
|
|
# changed.
|
|
|
|
SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1;
|
|
|
|
START SLAVE;
|
2009-11-28 02:34:47 +03:00
|
|
|
--source include/wait_for_slave_to_start.inc
|
2007-06-01 07:13:20 +02:00
|
|
|
sync_with_master;
|
|
|
|
|
|
|
|
# These values should be what was inserted, not what was
|
|
|
|
# updated. Since we are skipping the first table map of the group
|
|
|
|
# representing the UPDATE statement above, we should skip the entire
|
|
|
|
# group and not start executing at the first table map.
|
|
|
|
SELECT * FROM t1;
|
|
|
|
SELECT * FROM t2;
|
|
|
|
|
|
|
|
STOP SLAVE;
|
2009-11-28 02:34:47 +03:00
|
|
|
--source include/wait_for_slave_to_stop.inc
|
2007-06-01 07:13:20 +02:00
|
|
|
RESET SLAVE;
|
|
|
|
connection master;
|
|
|
|
RESET MASTER;
|
|
|
|
|
|
|
|
SET SESSION BINLOG_FORMAT=STATEMENT;
|
|
|
|
SET @foo = 12;
|
|
|
|
INSERT INTO t1 VALUES(@foo, 2*@foo);
|
|
|
|
save_master_pos;
|
|
|
|
source include/show_binlog_events.inc;
|
|
|
|
|
|
|
|
connection slave;
|
|
|
|
START SLAVE UNTIL MASTER_LOG_FILE='master-bin.000001', MASTER_LOG_POS=106;
|
2009-11-28 02:34:47 +03:00
|
|
|
--source include/wait_for_slave_sql_to_stop.inc
|
2007-06-01 07:13:20 +02:00
|
|
|
SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1;
|
|
|
|
START SLAVE;
|
2009-11-28 02:34:47 +03:00
|
|
|
--source include/wait_for_slave_to_start.inc
|
2007-06-01 07:13:20 +02:00
|
|
|
sync_with_master;
|
|
|
|
--replace_result $MASTER_MYPORT MASTER_PORT
|
2007-06-13 15:16:33 +02:00
|
|
|
--replace_column 1 # 8 # 9 # 23 # 33 # 35 # 36 #
|
2007-06-01 07:13:20 +02:00
|
|
|
query_vertical SHOW SLAVE STATUS;
|
|
|
|
|
|
|
|
--echo **** On Master ****
|
|
|
|
connection master;
|
|
|
|
DROP TABLE t1, t2;
|
|
|
|
sync_slave_with_master;
|
2007-10-19 14:18:41 +02:00
|
|
|
|
|
|
|
#
|
|
|
|
# More tests for BUG#28618
|
|
|
|
#
|
|
|
|
# Case 1.
|
|
|
|
# ROW binlog format and non-transactional tables.
|
|
|
|
# Create the group of events via triggers and try to skip
|
|
|
|
# some items of that group.
|
|
|
|
#
|
|
|
|
|
|
|
|
connection master;
|
|
|
|
SET SESSION BINLOG_FORMAT=ROW;
|
|
|
|
SET AUTOCOMMIT=0;
|
|
|
|
|
|
|
|
CREATE TABLE t1 (a INT, b VARCHAR(20)) ENGINE=myisam;
|
|
|
|
CREATE TABLE t2 (a INT, b VARCHAR(20)) ENGINE=myisam;
|
|
|
|
CREATE TABLE t3 (a INT, b VARCHAR(20)) ENGINE=myisam;
|
|
|
|
|
|
|
|
INSERT INTO t1 VALUES (1,'master/slave');
|
|
|
|
INSERT INTO t2 VALUES (1,'master/slave');
|
|
|
|
INSERT INTO t3 VALUES (1,'master/slave');
|
|
|
|
|
|
|
|
DELIMITER |;
|
|
|
|
|
|
|
|
CREATE TRIGGER tr1 AFTER UPDATE on t1 FOR EACH ROW
|
|
|
|
BEGIN
|
|
|
|
INSERT INTO t2 VALUES (NEW.a,NEW.b);
|
|
|
|
DELETE FROM t2 WHERE a < NEW.a;
|
|
|
|
END|
|
|
|
|
|
|
|
|
CREATE TRIGGER tr2 AFTER INSERT on t2 FOR EACH ROW
|
|
|
|
BEGIN
|
|
|
|
UPDATE t3 SET a =2, b = 'master only';
|
|
|
|
END|
|
|
|
|
|
|
|
|
DELIMITER ;|
|
|
|
|
|
|
|
|
--echo **** On Slave ****
|
|
|
|
sync_slave_with_master;
|
2008-07-10 18:09:39 +02:00
|
|
|
source include/stop_slave.inc;
|
2007-10-19 14:18:41 +02:00
|
|
|
|
|
|
|
--echo **** On Master ****
|
|
|
|
connection master;
|
|
|
|
UPDATE t1 SET a = 2, b = 'master only' WHERE a = 1;
|
|
|
|
DROP TRIGGER tr1;
|
|
|
|
DROP TRIGGER tr2;
|
|
|
|
INSERT INTO t1 VALUES (3,'master/slave');
|
|
|
|
INSERT INTO t2 VALUES (3,'master/slave');
|
|
|
|
INSERT INTO t3 VALUES (3,'master/slave');
|
2008-12-03 20:55:49 +01:00
|
|
|
COMMIT;
|
2007-10-19 14:18:41 +02:00
|
|
|
|
|
|
|
SELECT * FROM t1 ORDER BY a;
|
|
|
|
SELECT * FROM t2 ORDER BY a;
|
|
|
|
SELECT * FROM t3 ORDER BY a;
|
|
|
|
|
|
|
|
save_master_pos;
|
|
|
|
|
|
|
|
--echo *** On Slave ***
|
|
|
|
connection slave;
|
|
|
|
SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1;
|
2008-07-10 18:09:39 +02:00
|
|
|
source include/start_slave.inc;
|
2007-10-19 14:18:41 +02:00
|
|
|
sync_with_master;
|
|
|
|
|
|
|
|
SELECT * FROM t1 ORDER BY a;
|
|
|
|
SELECT * FROM t2 ORDER BY a;
|
|
|
|
SELECT * FROM t3 ORDER BY a;
|
|
|
|
|
|
|
|
connection master;
|
|
|
|
DROP TABLE t1, t2, t3;
|
|
|
|
sync_slave_with_master;
|
|
|
|
|
|
|
|
--echo **** Case 2: Row binlog format and transactional tables ****
|
|
|
|
|
|
|
|
# Create the transaction and try to skip some
|
|
|
|
# queries from one.
|
|
|
|
|
|
|
|
--echo *** On Master ***
|
|
|
|
connection master;
|
|
|
|
CREATE TABLE t4 (a INT, b VARCHAR(20)) ENGINE=innodb;
|
|
|
|
CREATE TABLE t5 (a INT, b VARCHAR(20)) ENGINE=innodb;
|
|
|
|
CREATE TABLE t6 (a INT, b VARCHAR(20)) ENGINE=innodb;
|
|
|
|
|
|
|
|
--echo **** On Slave ****
|
|
|
|
sync_slave_with_master;
|
2008-07-10 18:09:39 +02:00
|
|
|
source include/stop_slave.inc;
|
2007-10-19 14:18:41 +02:00
|
|
|
|
|
|
|
--echo *** On Master ***
|
|
|
|
connection master;
|
|
|
|
BEGIN;
|
|
|
|
INSERT INTO t4 VALUES (2, 'master only');
|
|
|
|
INSERT INTO t5 VALUES (2, 'master only');
|
|
|
|
INSERT INTO t6 VALUES (2, 'master only');
|
|
|
|
COMMIT;
|
|
|
|
|
|
|
|
BEGIN;
|
|
|
|
INSERT INTO t4 VALUES (3, 'master/slave');
|
|
|
|
INSERT INTO t5 VALUES (3, 'master/slave');
|
|
|
|
INSERT INTO t6 VALUES (3, 'master/slave');
|
|
|
|
COMMIT;
|
|
|
|
|
|
|
|
SELECT * FROM t4 ORDER BY a;
|
|
|
|
SELECT * FROM t5 ORDER BY a;
|
|
|
|
SELECT * FROM t6 ORDER BY a;
|
|
|
|
|
|
|
|
save_master_pos;
|
|
|
|
|
|
|
|
--echo *** On Slave ***
|
|
|
|
connection slave;
|
|
|
|
SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1;
|
2008-07-10 18:09:39 +02:00
|
|
|
source include/start_slave.inc;
|
2007-10-19 14:18:41 +02:00
|
|
|
sync_with_master;
|
|
|
|
|
|
|
|
SELECT * FROM t4 ORDER BY a;
|
|
|
|
SELECT * FROM t5 ORDER BY a;
|
|
|
|
SELECT * FROM t6 ORDER BY a;
|
|
|
|
|
|
|
|
# Test skipping two groups
|
|
|
|
|
|
|
|
--echo **** On Slave ****
|
|
|
|
connection slave;
|
2008-07-10 18:09:39 +02:00
|
|
|
source include/stop_slave.inc;
|
2007-10-19 14:18:41 +02:00
|
|
|
|
|
|
|
--echo *** On Master ***
|
|
|
|
connection master;
|
|
|
|
BEGIN;
|
|
|
|
INSERT INTO t4 VALUES (6, 'master only');
|
|
|
|
INSERT INTO t5 VALUES (6, 'master only');
|
|
|
|
INSERT INTO t6 VALUES (6, 'master only');
|
|
|
|
COMMIT;
|
|
|
|
|
|
|
|
BEGIN;
|
|
|
|
INSERT INTO t4 VALUES (7, 'master only');
|
|
|
|
INSERT INTO t5 VALUES (7, 'master only');
|
|
|
|
INSERT INTO t6 VALUES (7, 'master only');
|
|
|
|
COMMIT;
|
|
|
|
|
|
|
|
SELECT * FROM t4 ORDER BY a;
|
|
|
|
SELECT * FROM t5 ORDER BY a;
|
|
|
|
SELECT * FROM t6 ORDER BY a;
|
|
|
|
|
|
|
|
save_master_pos;
|
|
|
|
|
|
|
|
--echo *** On Slave ***
|
|
|
|
connection slave;
|
|
|
|
SET GLOBAL SQL_SLAVE_SKIP_COUNTER=10;
|
2008-07-10 18:09:39 +02:00
|
|
|
source include/start_slave.inc;
|
2007-10-19 14:18:41 +02:00
|
|
|
sync_with_master;
|
|
|
|
|
|
|
|
SELECT * FROM t4 ORDER BY a;
|
|
|
|
SELECT * FROM t5 ORDER BY a;
|
|
|
|
SELECT * FROM t6 ORDER BY a;
|
|
|
|
|
|
|
|
#
|
|
|
|
# And the same, but with autocommit = 0
|
|
|
|
#
|
|
|
|
connection slave;
|
2008-07-10 18:09:39 +02:00
|
|
|
source include/stop_slave.inc;
|
2007-10-19 14:18:41 +02:00
|
|
|
|
|
|
|
connection master;
|
|
|
|
SET AUTOCOMMIT=0;
|
|
|
|
|
|
|
|
INSERT INTO t4 VALUES (4, 'master only');
|
|
|
|
INSERT INTO t5 VALUES (4, 'master only');
|
|
|
|
INSERT INTO t6 VALUES (4, 'master only');
|
|
|
|
COMMIT;
|
|
|
|
|
|
|
|
INSERT INTO t4 VALUES (5, 'master/slave');
|
|
|
|
INSERT INTO t5 VALUES (5, 'master/slave');
|
|
|
|
INSERT INTO t6 VALUES (5, 'master/slave');
|
|
|
|
COMMIT;
|
|
|
|
|
|
|
|
SELECT * FROM t4 ORDER BY a;
|
|
|
|
SELECT * FROM t5 ORDER BY a;
|
|
|
|
SELECT * FROM t6 ORDER BY a;
|
|
|
|
|
|
|
|
save_master_pos;
|
|
|
|
|
|
|
|
--echo *** On Slave ***
|
|
|
|
connection slave;
|
|
|
|
SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1;
|
2008-07-10 18:09:39 +02:00
|
|
|
source include/start_slave.inc;
|
2007-10-19 14:18:41 +02:00
|
|
|
sync_with_master;
|
|
|
|
|
|
|
|
SELECT * FROM t4 ORDER BY a;
|
|
|
|
SELECT * FROM t5 ORDER BY a;
|
|
|
|
SELECT * FROM t6 ORDER BY a;
|
|
|
|
|
|
|
|
connection master;
|
|
|
|
DROP TABLE t4, t5, t6;
|
|
|
|
sync_slave_with_master;
|
|
|
|
|
|
|
|
--echo **** Case 3: Statement logging format and LOAD DATA with non-transactional table ****
|
|
|
|
|
|
|
|
# LOAD DATA creates two events in binary log for statement binlog format.
|
|
|
|
# Try to skip the first.
|
|
|
|
|
|
|
|
--echo *** On Master ***
|
|
|
|
connection master;
|
|
|
|
CREATE TABLE t10 (a INT, b VARCHAR(20)) ENGINE=myisam;
|
|
|
|
|
|
|
|
--echo *** On Slave ***
|
|
|
|
sync_slave_with_master;
|
2008-07-10 18:09:39 +02:00
|
|
|
source include/stop_slave.inc;
|
2007-10-19 14:18:41 +02:00
|
|
|
|
|
|
|
--echo *** On Master ***
|
|
|
|
connection master;
|
|
|
|
SET SESSION BINLOG_FORMAT=STATEMENT;
|
2008-07-04 11:33:34 +02:00
|
|
|
|
2007-10-19 14:18:41 +02:00
|
|
|
--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
|
2008-07-04 11:33:34 +02:00
|
|
|
eval LOAD DATA INFILE '$MYSQLTEST_VARDIR/std_data/rpl_bug28618.dat' INTO TABLE t10 FIELDS TERMINATED BY '|';
|
2007-10-19 14:18:41 +02:00
|
|
|
|
|
|
|
SELECT * FROM t10 ORDER BY a;
|
|
|
|
|
|
|
|
save_master_pos;
|
|
|
|
|
|
|
|
--echo *** On Slave ***
|
|
|
|
connection slave;
|
|
|
|
SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1;
|
2008-07-10 18:09:39 +02:00
|
|
|
source include/start_slave.inc;
|
2007-10-19 14:18:41 +02:00
|
|
|
sync_with_master;
|
|
|
|
|
|
|
|
SELECT * FROM t10 ORDER BY a;
|
|
|
|
|
|
|
|
connection master;
|
|
|
|
DROP TABLE t10;
|
|
|
|
sync_slave_with_master;
|
|
|
|
|