Commit 94946c68 authored by Sven Sandberg's avatar Sven Sandberg

BUG#49222: Mark RAND() as unsafe

Problem: When RAND() is binlogged in statement mode, the seed is
binlogged too, so the replication slave generates the same
sequence of random numbers. This makes replication work in many
cases, but not in all cases: the order of rows is not guaranteed
for, e.g., UPDATE or INSERT...SELECT statements, so the row data
will be different if master and slave retrieve the rows in
different orders.
Fix: Mark RAND() as unsafe. It will generate a warning if
binlog_format=STATEMENT and switch to row-logging if
binlog_format=ROW.
parent 30212033
...@@ -18,6 +18,8 @@ ...@@ -18,6 +18,8 @@
# Vs slave. # # Vs slave. #
############################################################################# #############################################################################
CALL mtr.add_suppression('Statement may not be safe to log in statement format.');
# Begin clean up test section # Begin clean up test section
connection master; connection master;
--disable_warnings --disable_warnings
...@@ -43,10 +45,12 @@ RETURN tmp; ...@@ -43,10 +45,12 @@ RETURN tmp;
END| END|
delimiter ;| delimiter ;|
--disable_warnings
INSERT INTO test.t1 VALUES (null,test.f1()),(null,test.f1()),(null,test.f1()); INSERT INTO test.t1 VALUES (null,test.f1()),(null,test.f1()),(null,test.f1());
sleep 6; sleep 6;
INSERT INTO test.t1 VALUES (null,test.f1()),(null,test.f1()),(null,test.f1()); INSERT INTO test.t1 VALUES (null,test.f1()),(null,test.f1()),(null,test.f1());
sleep 6; sleep 6;
--enable_warnings
#Select in this test are used for debugging #Select in this test are used for debugging
#select * from test.t1; #select * from test.t1;
...@@ -56,7 +60,9 @@ sleep 6; ...@@ -56,7 +60,9 @@ sleep 6;
connection master; connection master;
SET AUTOCOMMIT=0; SET AUTOCOMMIT=0;
START TRANSACTION; START TRANSACTION;
--disable_warnings
INSERT INTO test.t1 VALUES (null,test.f1()); INSERT INTO test.t1 VALUES (null,test.f1());
--enable_warnings
ROLLBACK; ROLLBACK;
SET AUTOCOMMIT=1; SET AUTOCOMMIT=1;
#select * from test.t1; #select * from test.t1;
......
...@@ -379,6 +379,9 @@ Note 1592 Statement may not be safe to log in statement format. ...@@ -379,6 +379,9 @@ Note 1592 Statement may not be safe to log in statement format.
INSERT INTO t1 VALUES (VERSION()); INSERT INTO t1 VALUES (VERSION());
Warnings: Warnings:
Note 1592 Statement may not be safe to log in statement format. Note 1592 Statement may not be safe to log in statement format.
INSERT INTO t1 VALUES (RAND());
Warnings:
Note 1592 Statement may not be safe to log in statement format.
DELETE FROM t1; DELETE FROM t1;
SET TIMESTAMP=1000000; SET TIMESTAMP=1000000;
INSERT INTO t1 VALUES INSERT INTO t1 VALUES
......
...@@ -47,6 +47,8 @@ ...@@ -47,6 +47,8 @@
# BUG#34768: nondeterministic INSERT using LIMIT logged in stmt mode if binlog_format=mixed # BUG#34768: nondeterministic INSERT using LIMIT logged in stmt mode if binlog_format=mixed
# BUG#41980, SBL, INSERT .. SELECT .. LIMIT = ERROR, even when @@SQL_LOG_BIN is 0 # BUG#41980, SBL, INSERT .. SELECT .. LIMIT = ERROR, even when @@SQL_LOG_BIN is 0
# BUG#42640: mysqld crashes when unsafe statements are executed (STRICT_TRANS_TABLES mode) # BUG#42640: mysqld crashes when unsafe statements are executed (STRICT_TRANS_TABLES mode)
# BUG#47995: Mark user functions as unsafe
# BUG#49222: Mare RAND() unsafe
# #
# ==== Related test cases ==== # ==== Related test cases ====
# #
...@@ -391,6 +393,7 @@ SET @@SESSION.SQL_MODE = @save_sql_mode; ...@@ -391,6 +393,7 @@ SET @@SESSION.SQL_MODE = @save_sql_mode;
# #
# BUG#47995: Mark user functions as unsafe # BUG#47995: Mark user functions as unsafe
# BUG#49222: Mare RAND() unsafe
# #
# Test that the system functions that are supposed to be marked unsafe # Test that the system functions that are supposed to be marked unsafe
# generate a warning. Each INSERT statement below should generate a # generate a warning. Each INSERT statement below should generate a
...@@ -400,27 +403,28 @@ SET @@SESSION.SQL_MODE = @save_sql_mode; ...@@ -400,27 +403,28 @@ SET @@SESSION.SQL_MODE = @save_sql_mode;
CREATE TABLE t1 (a VARCHAR(1000)); CREATE TABLE t1 (a VARCHAR(1000));
INSERT INTO t1 VALUES (CURRENT_USER()); #marked unsafe before BUG#47995 INSERT INTO t1 VALUES (CURRENT_USER()); #marked unsafe before BUG#47995
INSERT INTO t1 VALUES (FOUND_ROWS()); #marked unsafe before BUG#47995 INSERT INTO t1 VALUES (FOUND_ROWS()); #marked unsafe before BUG#47995
INSERT INTO t1 VALUES (GET_LOCK('tmp', 1)); INSERT INTO t1 VALUES (GET_LOCK('tmp', 1)); #marked unsafe in BUG#47995
INSERT INTO t1 VALUES (IS_FREE_LOCK('tmp')); INSERT INTO t1 VALUES (IS_FREE_LOCK('tmp')); #marked unsafe in BUG#47995
INSERT INTO t1 VALUES (IS_USED_LOCK('tmp')); INSERT INTO t1 VALUES (IS_USED_LOCK('tmp')); #marked unsafe in BUG#47995
INSERT INTO t1 VALUES (LOAD_FILE('../../std_data/words2.dat')); #marked unsafe before BUG#47995 INSERT INTO t1 VALUES (LOAD_FILE('../../std_data/words2.dat')); #marked unsafe in BUG#39701
INSERT INTO t1 VALUES (MASTER_POS_WAIT('dummy arg', 4711, 1)); INSERT INTO t1 VALUES (MASTER_POS_WAIT('dummy arg', 4711, 1));
INSERT INTO t1 VALUES (RELEASE_LOCK('tmp')); INSERT INTO t1 VALUES (RELEASE_LOCK('tmp')); #marked unsafe in BUG#47995
INSERT INTO t1 VALUES (ROW_COUNT()); #marked unsafe before BUG#47995 INSERT INTO t1 VALUES (ROW_COUNT()); #marked unsafe before BUG#47995
INSERT INTO t1 VALUES (SESSION_USER()); #marked unsafe before BUG#47995 INSERT INTO t1 VALUES (SESSION_USER()); #marked unsafe before BUG#47995
INSERT INTO t1 VALUES (SLEEP(1)); INSERT INTO t1 VALUES (SLEEP(1)); #marked unsafe in BUG#47995
INSERT INTO t1 VALUES (SYSDATE()); INSERT INTO t1 VALUES (SYSDATE()); #marked unsafe in BUG#47995
INSERT INTO t1 VALUES (SYSTEM_USER()); #marked unsafe before BUG#47995 INSERT INTO t1 VALUES (SYSTEM_USER()); #marked unsafe before BUG#47995
INSERT INTO t1 VALUES (USER()); #marked unsafe before BUG#47995 INSERT INTO t1 VALUES (USER()); #marked unsafe before BUG#47995
INSERT INTO t1 VALUES (UUID()); #marked unsafe before BUG#47995 INSERT INTO t1 VALUES (UUID()); #marked unsafe before BUG#47995
INSERT INTO t1 VALUES (UUID_SHORT()); #marked unsafe before BUG#47995 INSERT INTO t1 VALUES (UUID_SHORT()); #marked unsafe before BUG#47995
INSERT INTO t1 VALUES (VERSION()); INSERT INTO t1 VALUES (VERSION()); #marked unsafe in BUG#47995
INSERT INTO t1 VALUES (RAND()); #marked unsafe in BUG#49222
DELETE FROM t1; DELETE FROM t1;
# Since we replicate the TIMESTAMP variable, functions affected by the # Since we replicate the TIMESTAMP variable, functions affected by the
# TIMESTAMP variable are safe to replicate. So we check that the # TIMESTAMP variable are safe to replicate. So we check that the
# following following functions depend on the TIMESTAMP variable and # following following functions that depend on the TIMESTAMP variable
# don't generate a warning. # are not unsafe and don't generate a warning.
SET TIMESTAMP=1000000; SET TIMESTAMP=1000000;
INSERT INTO t1 VALUES INSERT INTO t1 VALUES
......
...@@ -4,6 +4,7 @@ reset master; ...@@ -4,6 +4,7 @@ reset master;
reset slave; reset slave;
drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
start slave; start slave;
CALL mtr.add_suppression('Statement may not be safe to log in statement format.');
create table t1(id int, i int, r1 int, r2 int, p varchar(100)); create table t1(id int, i int, r1 int, r2 int, p varchar(100));
insert into t1 values(1, connection_id(), 0, 0, ""); insert into t1 values(1, connection_id(), 0, 0, "");
insert into t1 values(2, 0, rand()*1000, rand()*1000, ""); insert into t1 values(2, 0, rand()*1000, rand()*1000, "");
......
...@@ -4,6 +4,7 @@ reset master; ...@@ -4,6 +4,7 @@ reset master;
reset slave; reset slave;
drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
start slave; start slave;
CALL mtr.add_suppression('Statement may not be safe to log in statement format.');
CREATE TABLE t1 (a VARCHAR(1000)); CREATE TABLE t1 (a VARCHAR(1000));
INSERT INTO t1 VALUES (CONNECTION_ID()); INSERT INTO t1 VALUES (CONNECTION_ID());
INSERT INTO t1 VALUES (CONNECTION_ID()); INSERT INTO t1 VALUES (CONNECTION_ID());
......
...@@ -4,6 +4,7 @@ reset master; ...@@ -4,6 +4,7 @@ reset master;
reset slave; reset slave;
drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
start slave; start slave;
CALL mtr.add_suppression('Statement may not be safe to log in statement format.');
create table t1 (a int not null auto_increment primary key, b int, key(b)); create table t1 (a int not null auto_increment primary key, b int, key(b));
INSERT INTO t1 (a) VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10); INSERT INTO t1 (a) VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10);
INSERT INTO t1 (a) SELECT null FROM t1; INSERT INTO t1 (a) SELECT null FROM t1;
......
...@@ -4,6 +4,7 @@ reset master; ...@@ -4,6 +4,7 @@ reset master;
reset slave; reset slave;
drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
start slave; start slave;
CALL mtr.add_suppression('Statement may not be safe to log in statement format.');
DROP FUNCTION IF EXISTS test.f1; DROP FUNCTION IF EXISTS test.f1;
DROP TABLE IF EXISTS test.t1; DROP TABLE IF EXISTS test.t1;
CREATE TABLE test.t1 (a INT NOT NULL AUTO_INCREMENT, c CHAR(16),PRIMARY KEY(a))ENGINE=INNODB; CREATE TABLE test.t1 (a INT NOT NULL AUTO_INCREMENT, c CHAR(16),PRIMARY KEY(a))ENGINE=INNODB;
......
...@@ -3,12 +3,16 @@ ...@@ -3,12 +3,16 @@
# #
source include/master-slave.inc; source include/master-slave.inc;
CALL mtr.add_suppression('Statement may not be safe to log in statement format.');
create table t1(id int, i int, r1 int, r2 int, p varchar(100)); create table t1(id int, i int, r1 int, r2 int, p varchar(100));
insert into t1 values(1, connection_id(), 0, 0, ""); insert into t1 values(1, connection_id(), 0, 0, "");
# don't put rand and password in the same query, to see if they replicate # don't put rand and password in the same query, to see if they replicate
# independently # independently
# Pure rand test # Pure rand test
--disable_warnings
insert into t1 values(2, 0, rand()*1000, rand()*1000, ""); insert into t1 values(2, 0, rand()*1000, rand()*1000, "");
--enable_warnings
# change the rand suite on the master (we do this because otherwise password() # change the rand suite on the master (we do this because otherwise password()
# benefits from the fact that the above rand() is well replicated : # benefits from the fact that the above rand() is well replicated :
# it picks the same sequence element, which hides a possible bug in password() replication. # it picks the same sequence element, which hides a possible bug in password() replication.
...@@ -19,7 +23,9 @@ set sql_log_bin=1; ...@@ -19,7 +23,9 @@ set sql_log_bin=1;
# Pure password test # Pure password test
insert into t1 values(3, 0, 0, 0, password('does_this_work?')); insert into t1 values(3, 0, 0, 0, password('does_this_work?'));
# "altogether now" # "altogether now"
--disable_warnings
insert into t1 values(4, connection_id(), rand()*1000, rand()*1000, password('does_this_still_work?')); insert into t1 values(4, connection_id(), rand()*1000, rand()*1000, password('does_this_still_work?'));
--enable_warnings
select * into outfile 'rpl_misc_functions.outfile' from t1; select * into outfile 'rpl_misc_functions.outfile' from t1;
let $MYSQLD_DATADIR= `select @@datadir`; let $MYSQLD_DATADIR= `select @@datadir`;
sync_slave_with_master; sync_slave_with_master;
...@@ -73,11 +79,13 @@ DELIMITER ;| ...@@ -73,11 +79,13 @@ DELIMITER ;|
# Exercise the functions and procedures then compare the results on # Exercise the functions and procedures then compare the results on
# the master to those on the slave. # the master to those on the slave.
--disable_warnings
CALL test_replication_sp1(); CALL test_replication_sp1();
CALL test_replication_sp2(); CALL test_replication_sp2();
INSERT INTO t1 (col_a) VALUES (test_replication_sf()); INSERT INTO t1 (col_a) VALUES (test_replication_sf());
INSERT INTO t1 (col_a) VALUES (test_replication_sf()); INSERT INTO t1 (col_a) VALUES (test_replication_sf());
INSERT INTO t1 (col_a) VALUES (test_replication_sf()); INSERT INTO t1 (col_a) VALUES (test_replication_sf());
--enable_warnings
--sync_slave_with_master --sync_slave_with_master
......
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
--source include/master-slave.inc --source include/master-slave.inc
CALL mtr.add_suppression('Statement may not be safe to log in statement format.');
CREATE TABLE t1 (a VARCHAR(1000)); CREATE TABLE t1 (a VARCHAR(1000));
# We replicate the connection_id in the query_log_event # We replicate the connection_id in the query_log_event
...@@ -41,7 +43,9 @@ INSERT INTO t1 VALUES ...@@ -41,7 +43,9 @@ INSERT INTO t1 VALUES
(UTC_TIMESTAMP()); (UTC_TIMESTAMP());
# We replicate the random seed in a rand_log_event # We replicate the random seed in a rand_log_event
--disable_warnings
INSERT INTO t1 VALUES (RAND()); INSERT INTO t1 VALUES (RAND());
--enable_warnings
# We replicate the last_insert_id in an intvar_log_event # We replicate the last_insert_id in an intvar_log_event
INSERT INTO t1 VALUES (LAST_INSERT_ID()); INSERT INTO t1 VALUES (LAST_INSERT_ID());
......
...@@ -13,6 +13,8 @@ ...@@ -13,6 +13,8 @@
-- source include/not_ndb_default.inc -- source include/not_ndb_default.inc
-- source include/master-slave.inc -- source include/master-slave.inc
CALL mtr.add_suppression('Statement may not be safe to log in statement format.');
create table t1 (a int not null auto_increment primary key, b int, key(b)); create table t1 (a int not null auto_increment primary key, b int, key(b));
INSERT INTO t1 (a) VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10); INSERT INTO t1 (a) VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10);
INSERT INTO t1 (a) SELECT null FROM t1; INSERT INTO t1 (a) SELECT null FROM t1;
...@@ -30,8 +32,8 @@ INSERT INTO t1 (a) SELECT null FROM t1; ...@@ -30,8 +32,8 @@ INSERT INTO t1 (a) SELECT null FROM t1;
INSERT INTO t1 (a) SELECT null FROM t1; INSERT INTO t1 (a) SELECT null FROM t1;
save_master_pos; save_master_pos;
# a few updates to force OPTIMIZE to do something # a few updates to force OPTIMIZE to do something
update t1 set b=(a/2*rand());
--disable_warnings --disable_warnings
update t1 set b=(a/2*rand());
delete from t1 order by b limit 10000; delete from t1 order by b limit 10000;
--enable_warnings --enable_warnings
......
...@@ -40,10 +40,12 @@ insert into t3 values(100,"log",0,0,0); ...@@ -40,10 +40,12 @@ insert into t3 values(100,"log",0,0,0);
SET @@RAND_SEED1=658490765, @@RAND_SEED2=635893186; SET @@RAND_SEED1=658490765, @@RAND_SEED2=635893186;
# Emulate that we have rows 2-9 deleted on the slave # Emulate that we have rows 2-9 deleted on the slave
--disable_warnings
insert into t1 values(1,1,rand()),(NULL,2,rand()); insert into t1 values(1,1,rand()),(NULL,2,rand());
insert into t2 (b) values(last_insert_id()); insert into t2 (b) values(last_insert_id());
insert into t2 values(3,0),(NULL,0); insert into t2 values(3,0),(NULL,0);
insert into t2 values(NULL,0),(500,0); insert into t2 values(NULL,0),(500,0);
--enable_warnings
select a,b, truncate(rand_value,4) from t1; select a,b, truncate(rand_value,4) from t1;
select * from t2; select * from t2;
......
...@@ -4,6 +4,7 @@ reset master; ...@@ -4,6 +4,7 @@ reset master;
reset slave; reset slave;
drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
start slave; start slave;
CALL mtr.add_suppression('Statement may not be safe to log in statement format.');
DROP FUNCTION IF EXISTS test.f1; DROP FUNCTION IF EXISTS test.f1;
DROP TABLE IF EXISTS test.t1; DROP TABLE IF EXISTS test.t1;
CREATE TABLE test.t1 (a INT NOT NULL AUTO_INCREMENT, c CHAR(16),PRIMARY KEY(a))ENGINE=NDB; CREATE TABLE test.t1 (a INT NOT NULL AUTO_INCREMENT, c CHAR(16),PRIMARY KEY(a))ENGINE=NDB;
......
...@@ -4178,6 +4178,16 @@ Create_func_rand::create_native(THD *thd, LEX_STRING name, ...@@ -4178,6 +4178,16 @@ Create_func_rand::create_native(THD *thd, LEX_STRING name,
if (item_list != NULL) if (item_list != NULL)
arg_count= item_list->elements; arg_count= item_list->elements;
/*
When RAND() is binlogged, the seed is binlogged too. So the
sequence of random numbers is the same on a replication slave as
on the master. However, if several RAND() values are inserted
into a table, the order in which the rows are modified may differ
between master and slave, because the order is undefined. Hence,
the statement is unsafe to log in statement format.
*/
thd->lex->set_stmt_unsafe();
switch (arg_count) { switch (arg_count) {
case 0: case 0:
{ {
......
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