Skip to content

Commit

Permalink
Bug#26414532: MYSQLRPLSYNC ERRORS OUT BECAUSE SLAVE IS USING
Browse files Browse the repository at this point in the history
--SUPER-READ-ONLY OPTION

Problem:
========
After upgrading MySQL server from 5.7.17 to 5.7.18
'mysqlrplsync' utility tool fails with following error.

ERROR: Query failed. 1290 (HY000): The MySQL server is
running with the --super-read-only option so it cannot
execute this statement

When this occurs, the Slave_SQL_Running on the slave server
stops running and needs to be manually started again. The
slave server does have the super-read-only option set to
true. When running MySQL 5.7.17 this wasn't an issue.

Analysis:
========
When replication information repository is set to 'TABLE'
few replication specific administration commands listed
below, will try to update the repository tables.

1) START SLAVE
2) STOP SLAVE
3) CHANGE MASTER
4) RESET SLAVE
5) RESET MASTER
6) SET GLOBAL GTID_PURGED
7) FLUSH BINARY LOGS
8) 'gtid_executed' TABLE compression thread.

First three commands were fixed as part of Bug#22097534:
SUPER_READ_ONLY ABORTS STOP SLAVE IF
RELAY_LOG_INFO_REPOSITORY=TABLE, DBG CRASH

The reset of the commands were fixed as part of
Bug#22857926: ASSERTION `! IS_SET()' AT SQL_ERROR.CC:38 IN
READ_ONLY MODE FOR MANY RPL CMDS.

During the Bug#22857926 fix, the previous Bug#22097534 fix
specific changes were reverted a new approach was taken.

The new approach used a flag named 'ignore_global_read_lock'
whose intention is to ignore the read only option and allow
commits to replication specific system tables.  If this flag
is set then the additional check for 'read only' option is
not done.

But the above fix works fine only when 'AUTOCOMMIT=ON'.  In
the case of AUTOCOMMIT=OFF the 'ignore_global_read_lock'
flag is not set. Hence commands fail to ignore read_only
restriction.

STOP SLAVE command behavior explained in detail:

STOP SLAVE command is an IMPLICIT COMMIT command, i.e
parser will append an explicit BEGIN and COMMIT for this
command.

BEGIN
STOP SLAVE;
COMMIT;

STOP SLAVE command will try to stop both the applier thread
and receiver thread and update their positions in
'slave_relay_log_info' and 'slave_master_info' tables.

Case: AUTOCOMMIT=ON.

1) BEGIN

2) Update 'slave_relay_log_info' table and commit as a real
transaction.

System_table_access::close_table will invoke
ha_commit_trans with 'ignore_global_read_lock=true'.

Since autocommit is 'ON' each statement will do a complete
commit and clean up the transaction context.

3) Update 'slave_master_info' also does the same.

4) COMMIT.

COMMIT will invoke 'trans_commit_implicit'. It will check
if there any active transactions which needs to be committed
implicitly. Since step 2-3 are real commit transactions this
final commit has nothing to do. It will exit without
proceeding further.

Case: AUTOCOMMIT=OFF.

1) BEGIN

2) Update 'slave_relay_log_info' table. This transaction is
considered as a multi statement transaction mode, it will
not commit on its own. It will wait for final COMMIT
execution.

3) Update 'slave_master_info' also does the same.

4) COMMIT.

COMMIT will invoke 'trans_commit_implicit'.

It will check if there any active transactions which needs
to be committed . Since there are active transaction
sessions from step 2-3 'ha_commit_trans' will be called as
shown below.

'trans_commit_implicit' will invoke
ha_commit_trans (thd,all=true,ignore_global_read_lock=false)

This will not pass the read_only check and the command will
fail.

This issue happens for commands that update info tables
other than 'gtid_executed' table. In case of 'gtid_executed'
table irrespective of autocommit variable all the
transactions are considered as 'real' transactions and they
will do complete commit.

Fix:
===
Introduced a new variable, to flag the sql command under
execution to ignore the read_only/super_read_only. If the
variable is set then the command is allowed to execute even
though read_only/super_read_only is enabled otherwise
command is blocked.
  • Loading branch information
Sujatha Sivakumar committed Aug 24, 2017
1 parent 1c582ac commit dee592d
Show file tree
Hide file tree
Showing 16 changed files with 437 additions and 147 deletions.
134 changes: 134 additions & 0 deletions mysql-test/extra/rpl_tests/rpl_ignore_super_read_only.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# ==== Purpose ====
#
# Verify that we permit replication operations with super_read_only=ON.
#
# ==== Implementation ====
#
# 1) SET GLOBAL super_read_only to 1 on slave
# 2) Execute 'STOP SLAVE' command
# 3) Execute 'CHANGE REPLICATION FILTER' command
# 4) Execute 'CHANGE MASTER TO' command
# 5) Execute 'RESET SLAVE FOR CHANNEL <channel_name>' command
# 7) Execute 'RESET SLAVE ALL FOR CHANNEL <channel_name>' command
# 8) Execute 'RESET SLAVE' command
# 9) Execute 'START SLAVE' command
# 10) Execute 'RESET MASTER' command (with GTID_MODE=ON will assert)
# 11) Execute 'FLUSH BINARY LOGS' command (with GTID_MODE=ON will assert)
# 12) Execute 'SET GLOBAL gtid_purged='aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1''
# command
# 14) Check that replication works fine
# 15) Restore GLOBAL super_read_only to 0
#
# ==== References ====
#
# Bug#22097534: SUPER_READ_ONLY ABORTS STOP SLAVE IF
# RELAY_LOG_INFO_REPOSITORY=TABLE, DBG CRASH
# Bug#22857926: ASSERTION `! IS_SET()' AT SQL_ERROR.CC:38 IN READ_ONLY MODE FOR
# MANY RPL CMDS.
# Bug#26414532: MYSQLRPLSYNC ERRORS OUT BECAUSE SLAVE IS USING --SUPER-READ-ONLY
# OPTION
#

if (!$autocommit_opt)
{
--die !!!ERROR IN TEST: you must set $autocommit_opt
}

--source include/rpl_connection_master.inc
CREATE TABLE t1(a INT);
INSERT INTO t1 VALUES(1);
DROP TABLE t1;
--source include/sync_slave_sql_with_master.inc

SET @saved_value_super= @@GLOBAL.super_read_only;
SET @saved_value= @@GLOBAL.read_only;
SET @saved_autocommit= @@GLOBAL.autocommit;
SET GLOBAL super_read_only= 1;
--eval SET AUTOCOMMIT= $autocommit_opt
SHOW VARIABLES like '%autocommit%';

--echo ####################################################################
--echo # Test Case1: STOP SLAVE command
--echo ####################################################################
--source include/stop_slave.inc

--echo ####################################################################
--echo # Test Case2: CHANGE REPLICATION FILTER command
--echo ####################################################################
CHANGE REPLICATION FILTER REPLICATE_DO_DB=(test);
CHANGE REPLICATION FILTER REPLICATE_DO_DB=();

--echo ####################################################################
--echo # Test Case3: CHANGE MASTER command
--echo ####################################################################
CHANGE MASTER TO MASTER_CONNECT_RETRY=20;
CHANGE MASTER TO MASTER_HOST='dummy' FOR CHANNEL 'aaa';

--echo ####################################################################
--echo # Test Case4: RESET SLAVE FOR CHANNEL/RESET SLAVE ALL/RESET SLAVE
--echo # commands
--echo ####################################################################
RESET SLAVE FOR CHANNEL 'aaa';
RESET SLAVE ALL FOR CHANNEL 'aaa';
RESET SLAVE;

--echo ####################################################################
--echo # Test Case5: START SLAVE command
--echo ####################################################################
--source include/start_slave.inc
--source include/rpl_connection_master.inc
--source include/sync_slave_sql_with_master.inc

# Check that mysql.gtid_executed table is empty.
--let $assert_text= mysql.gtid_executed table must have zero records
--let $assert_cond= [SELECT count(*) FROM mysql.gtid_executed] = 0
--source include/assert.inc

--echo ####################################################################
--echo # Test Case6: FLUSH BINARY LOGS command
--echo ####################################################################
# FLUSH BINARY LOGS asserts when it is trying to update gtid_executed table
# during binary log rotation.
FLUSH BINARY LOGS;

# Check that an entry is updated in gtid_executed table without causing any
# assert.
--disable_query_log
if ( `SELECT @@GLOBAL.GTID_MODE = "ON"` )
{
--let $table=mysql.gtid_executed
--let $count=1
--source include/wait_until_rows_count.inc
}
--enable_query_log

--echo ####################################################################
--echo # Test Case7: RESET MASTER command
--echo ####################################################################
RESET MASTER;

--echo ####################################################################
--echo # Test Case8: SET GLOBAL GTID_PURGED command
--echo ####################################################################
--let $master_uuid=`SELECT @@SERVER_UUID`
--replace_result $master_uuid MASTER_UUID
--eval SET GLOBAL gtid_purged= '$master_uuid:1'


--echo "Clean up"
# Reset slave to clean state
SET AUTOCOMMIT= @saved_autocommit;
SET GLOBAL super_read_only= @saved_value_super;
SET GLOBAL read_only= @saved_value;

# Start fresh slave
--source include/stop_slave.inc
--replace_result $MASTER_MYPORT MASTER_PORT
--replace_column 2 ####
--eval CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT=$MASTER_MYPORT, MASTER_USER='root';
--source include/start_slave.inc

--source include/rpl_connection_master.inc
--source include/sync_slave_sql_with_master.inc

--source include/rpl_reset.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
include/master-slave.inc
Warnings:
Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
[connection master]
[connection slave]
call mtr.add_suppression("You need to use --log-bin*");
SET @saved_value_super= @@GLOBAL.super_read_only;
SET @saved_value= @@GLOBAL.read_only;
SET GLOBAL super_read_only= 1;
[connection master]
CREATE TABLE t1(a INT) ENGINE=INNODB;
INSERT INTO t1 VALUES(1);
INSERT INTO t1 VALUES(2);
INSERT INTO t1 VALUES(3);
INSERT INTO t1 VALUES(4);
DROP TABLE t1;
include/sync_slave_sql_with_master.inc
#
# Verify that gtid_executed table has MASTER_UUID:1:6
#
SELECT * FROM mysql.gtid_executed;
source_uuid interval_start interval_end
MASTER_UUID 1 6
"Clean up"
SET GLOBAL super_read_only= @saved_value_super;
SET GLOBAL read_only= @saved_value;
include/rpl_end.inc
117 changes: 104 additions & 13 deletions mysql-test/suite/rpl/r/rpl_operation_ignore_super_read_only.result
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,136 @@ Warnings:
Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
[connection master]
[connection master]
CREATE TABLE t1(a INT);
INSERT INTO t1 VALUES(1);
DROP TABLE t1;
include/sync_slave_sql_with_master.inc
SET @saved_value_super= @@GLOBAL.super_read_only;
SET @saved_value= @@GLOBAL.read_only;
SET @saved_autocommit= @@GLOBAL.autocommit;
SET GLOBAL super_read_only= 1;
SET AUTOCOMMIT= ON;
SHOW VARIABLES like '%autocommit%';
Variable_name Value
autocommit ON
####################################################################
# Test Case1: STOP SLAVE command
####################################################################
include/stop_slave.inc
####################################################################
# Test Case2: CHANGE REPLICATION FILTER command
####################################################################
CHANGE REPLICATION FILTER REPLICATE_DO_DB=(test);
CHANGE REPLICATION FILTER REPLICATE_DO_DB=();
####################################################################
# Test Case3: CHANGE MASTER command
####################################################################
CHANGE MASTER TO MASTER_CONNECT_RETRY=20;
CHANGE MASTER TO MASTER_HOST='dummy' FOR CHANNEL 'aaa';
####################################################################
# Test Case4: RESET SLAVE FOR CHANNEL/RESET SLAVE ALL/RESET SLAVE
# commands
####################################################################
RESET SLAVE FOR CHANNEL 'aaa';
RESET SLAVE ALL FOR CHANNEL 'aaa';
RESET SLAVE;
####################################################################
# Test Case5: START SLAVE command
####################################################################
include/start_slave.inc
[connection master]
INSERT INTO t1 VALUES(2);
include/sync_slave_sql_with_master.inc
include/assert.inc [Table t1 must have two records]
include/stop_slave.inc
include/assert.inc [mysql.gtid_executed table must have zero records]
####################################################################
# Test Case6: FLUSH BINARY LOGS command
####################################################################
FLUSH BINARY LOGS;
CHANGE MASTER TO MASTER_HOST='dummy' FOR CHANNEL 'aaa';
RESET SLAVE FOR CHANNEL 'aaa';
RESET SLAVE ALL FOR CHANNEL 'aaa';
RESET SLAVE;
RESET MASTER;
SET GLOBAL gtid_purged='aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1';
include/assert.inc [Found a record with source_uuid 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' in gtid_executed table.]
####################################################################
# Test Case7: RESET MASTER command
####################################################################
RESET MASTER;
####################################################################
# Test Case8: SET GLOBAL GTID_PURGED command
####################################################################
SET GLOBAL gtid_purged= 'MASTER_UUID:1';
"Clean up"
SET AUTOCOMMIT= @saved_autocommit;
SET GLOBAL super_read_only= @saved_value_super;
SET GLOBAL read_only= @saved_value;
SET GLOBAL gtid_purged='aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1';
include/stop_slave.inc
CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT=MASTER_PORT, MASTER_USER='root';;
Warnings:
Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
include/start_slave.inc
[connection master]
include/sync_slave_sql_with_master.inc
include/rpl_reset.inc
[connection master]
CREATE TABLE t1(a INT);
INSERT INTO t1 VALUES(1);
DROP TABLE t1;
include/sync_slave_sql_with_master.inc
SET @saved_value_super= @@GLOBAL.super_read_only;
SET @saved_value= @@GLOBAL.read_only;
SET @saved_autocommit= @@GLOBAL.autocommit;
SET GLOBAL super_read_only= 1;
SET AUTOCOMMIT= OFF;
SHOW VARIABLES like '%autocommit%';
Variable_name Value
autocommit OFF
####################################################################
# Test Case1: STOP SLAVE command
####################################################################
include/stop_slave.inc
####################################################################
# Test Case2: CHANGE REPLICATION FILTER command
####################################################################
CHANGE REPLICATION FILTER REPLICATE_DO_DB=(test);
CHANGE REPLICATION FILTER REPLICATE_DO_DB=();
####################################################################
# Test Case3: CHANGE MASTER command
####################################################################
CHANGE MASTER TO MASTER_CONNECT_RETRY=20;
CHANGE MASTER TO MASTER_HOST='dummy' FOR CHANNEL 'aaa';
####################################################################
# Test Case4: RESET SLAVE FOR CHANNEL/RESET SLAVE ALL/RESET SLAVE
# commands
####################################################################
RESET SLAVE FOR CHANNEL 'aaa';
RESET SLAVE ALL FOR CHANNEL 'aaa';
RESET SLAVE;
####################################################################
# Test Case5: START SLAVE command
####################################################################
include/start_slave.inc
[connection master]
include/sync_slave_sql_with_master.inc
include/assert.inc [mysql.gtid_executed table must have zero records]
####################################################################
# Test Case6: FLUSH BINARY LOGS command
####################################################################
FLUSH BINARY LOGS;
####################################################################
# Test Case7: RESET MASTER command
####################################################################
RESET MASTER;
####################################################################
# Test Case8: SET GLOBAL GTID_PURGED command
####################################################################
SET GLOBAL gtid_purged= 'MASTER_UUID:1';
"Clean up"
SET AUTOCOMMIT= @saved_autocommit;
SET GLOBAL super_read_only= @saved_value_super;
SET GLOBAL read_only= @saved_value;
DROP TABLE t1;
include/stop_slave.inc
CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT=MASTER_PORT, MASTER_USER='root';;
Warnings:
Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
include/start_slave.inc
[connection master]
DROP TABLE t1;
include/sync_slave_sql_with_master.inc
include/rpl_reset.inc
include/rpl_end.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--skip-log-bin --gtid_executed_compression_period=5
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# ==== Purpose ====
#
# Verify that we permit replication operations with super_read_only=ON.
#
# ==== Implementation ====
#
# 1) Have gtid_mode on and binlog less slave.
# 2) set super_read_only=on on slave server.
# 3) Set gtid_executed_compression_period=5 so that compression thread wakes
# up after 5 gtid transactions are executed.
# 4) Execute 6 transaction on master so that it triggers compression thread
# on slave to update mysql.gtid_executed table.
# 5) Verify that Slave server is up and running.
# 6) Check that replication works fine
# 7) Restore GLOBAL super_read_only to 0
#
# ==== References ====
#
# Bug#22097534: SUPER_READ_ONLY ABORTS STOP SLAVE IF
# RELAY_LOG_INFO_REPOSITORY=TABLE, DBG CRASH
# Bug#22857926: ASSERTION `! IS_SET()' AT SQL_ERROR.CC:38 IN READ_ONLY MODE
# FOR MANY RPL CMDS.
# Bug#26414532: MYSQLRPLSYNC ERRORS OUT BECAUSE SLAVE IS USING
# --SUPER-READ-ONLY OPTION
#

# Test in this file only makes sense in standard replication,
# so it is skipped in group replication.
--source include/not_group_replication_plugin.inc
# Gtids are mandatory for the test.
--source include/have_gtid.inc
# Test in this file is binlog format agnostic, thus no need
# to rerun it for every format.
--source include/have_binlog_format_row.inc
--source include/master-slave.inc

--source include/rpl_connection_slave.inc
call mtr.add_suppression("You need to use --log-bin*");
SET @saved_value_super= @@GLOBAL.super_read_only;
SET @saved_value= @@GLOBAL.read_only;
SET GLOBAL super_read_only= 1;

--source include/rpl_connection_master.inc
--let $master_uuid= `SELECT @@GLOBAL.SERVER_UUID`
CREATE TABLE t1(a INT) ENGINE=INNODB;
INSERT INTO t1 VALUES(1);
INSERT INTO t1 VALUES(2);
INSERT INTO t1 VALUES(3);
INSERT INTO t1 VALUES(4);
DROP TABLE t1;
--source include/sync_slave_sql_with_master.inc

--echo #
--echo # Verify that gtid_executed table has MASTER_UUID:1:6
--echo #
--replace_result $master_uuid MASTER_UUID
SELECT * FROM mysql.gtid_executed;

--echo "Clean up"
# Reset slave to clean state
SET GLOBAL super_read_only= @saved_value_super;
SET GLOBAL read_only= @saved_value;

--source include/rpl_end.inc
Loading

0 comments on commit dee592d

Please sign in to comment.