Skip to content

Commit 44a901a

Browse files
author
Venkatesh Duggirala
committed
BUG#17650326 MYSQLBINLOG PRINTS INVALID SQL FROM RELAY LOGS WHEN GTID IS ENABLED
Problem (1) : Replaying a relaylog which ends with GTID_LOG_EVENT causes error "ERROR 1790 (HY000) @@SESSION.GTID_NEXT cannot be changed by a client that owns a GTID. The client owns <UUID:GTID> Ownership is released on COMMIT or ROLLBACK." Analysis: mysqlbinlog tool appends SET @@SESSION.GTID_NEXT='AUTOMATIC' to output when it finds a rotate log event and if it is not inside a transaction which is the right thing to do. The tool has in_transaction flag which will be set true upon processing any transaction event. But a GTID_LOG_EVENT is not treated as a transaction event, hence in_transaction is false even after processing GTID_LOG_EVENT. If a relaylog rotate happens (either through IO thread restart/ROTATE command), exactly after writing GTID_LOG_EVENT and if we are replaying such a relaylog's end ROTATE_EVENT, logic was mistakenly thinking that it was not inside a transaction but actually the transaction was started after GTID_LOG_EVENT. Hence tool generates SET @@SESSION.GTID_NEXT='AUTOMATIC'. This issue resulted in having two GTID_NEXT statements next to each other and replaying such output against 'mysql' client tool was causing above specified error (ERROR:1790). Fix: One way to fix this issue is to set in_transaction flag to true when it encounters GTID_LOG_EVENT. But this change will disturb DDL transaction detection logic (mysqlbinlog tool logic assumes that DDL transactions will have only one event per transaction). So instead of disturbing that logic, we have followed another approach to solve the issue. 'mysqlbinlog' tool generates "SET @@SESSION.GTID_NEXT='AUTOMATIC'" only when in_transaction is false and is_gtid_next_valid is false (which represents that we are not in transaction and did not even see GTID_LOG_EVENT. Problem (2): 'ROLLBACK' is not getting generated by mysqlbinlog tool when concatenating and replaying two consecutive relaylogs (the first relaylog contains partial transaction and second relaylog contains full transaction again). Analysis: If IO thread is restarted when it is in the middle of transferring a tranasction from Master then it will retrieve the full transaction again upon restart. In that case, the first relaylog contains partial GTID transaction and the second relaylog will contain full GTID transaction again. If mysqlbinlog tool is used to merge such two relaylogs it should generate a 'ROLLBACK' statement after partial transaction. Otherwise it will cause above said error (ERROR:1790). Fix: If IO thread is restarted, the new generated relaylog will have FD event received from Master and will have log_pos greater than zero if the AUTO_POSITION is true i.e., this will indicate that if we were in transaction in first relaylog, Master would have sent the transaction again from the beginning. In this case, upon reading such a FD event, generate 'ROLLBACK' statement in generated output. If we have seen only GTID_LOG_EVENT of the transaction and not seen 'BEGIN' statement, then generate BEGIN and ROLLBACK statements in generated output.
1 parent ba082ab commit 44a901a

File tree

5 files changed

+184
-3
lines changed

5 files changed

+184
-3
lines changed

client/mysqlbinlog.cc

+72-1
Original file line numberDiff line numberDiff line change
@@ -1087,9 +1087,66 @@ Exit_status process_event(PRINT_EVENT_INFO *print_event_info, Log_event *ev,
10871087
case FORMAT_DESCRIPTION_EVENT:
10881088
delete glob_description_event;
10891089
glob_description_event= (Format_description_log_event*) ev;
1090+
/*
1091+
The first FD event in log is always generated
1092+
from the local server. So if it is first FD event to be
1093+
processed (i.e., if server_id_from_fd_event is 0),
1094+
get server_id from the FD event and keep it in
1095+
server_id_from_fd_event to differentiate between FDs
1096+
(originated from local server vs another server).
1097+
*/
1098+
if (print_event_info->server_id_from_fd_event == 0)
1099+
print_event_info->server_id_from_fd_event= ev->server_id;
1100+
10901101
print_event_info->common_header_len=
10911102
glob_description_event->common_header_len;
10921103
ev->print(result_file, print_event_info);
1104+
/*
1105+
At this point, if we are in transaction that means
1106+
we are reading a relay log file (transaction cannot
1107+
spawn across two binary log files, they are writen
1108+
at once in binlog). When AUTO_POSITION is enabled
1109+
and if IO thread stopped in between the GTID transaction,
1110+
upon IO thread restart, Master will send the GTID events
1111+
again from the begin of the transaction. Hence, we should
1112+
rollback the old transaction.
1113+
1114+
If you are reading FD event that came from Master
1115+
(first FD event is from the server that owns the relaylog
1116+
and second one is from Master) and if it's log_pos is > 0
1117+
then it represents the begin of a master's binary log
1118+
(any unfinished transaction will not be finished) or that
1119+
auto_position is enabled (any partial transaction left will
1120+
not be finished but will be fully retrieved again). On both
1121+
cases, the next transaction in the relay log will start from the
1122+
beginning and we must rollback any unfinished transaction
1123+
*/
1124+
if (ev->server_id !=0 &&
1125+
ev->server_id != print_event_info->server_id_from_fd_event &&
1126+
ev->log_pos > 0)
1127+
{
1128+
if (in_transaction)
1129+
{
1130+
my_b_printf(&print_event_info->head_cache,
1131+
"ROLLBACK /* added by mysqlbinlog */ %s\n",
1132+
print_event_info->delimiter);
1133+
}
1134+
else if (print_event_info->is_gtid_next_set &&
1135+
print_event_info->is_gtid_next_valid)
1136+
{
1137+
/*
1138+
If we are here, then we have seen only GTID_LOG_EVENT
1139+
of a transaction and did not see even a BEGIN event
1140+
(in_transaction flag is false). So generate BEGIN event
1141+
also along with ROLLBACK event.
1142+
*/
1143+
my_b_printf(&print_event_info->head_cache,
1144+
"BEGIN /*added by mysqlbinlog */ %s\n"
1145+
"ROLLBACK /* added by mysqlbinlog */ %s\n",
1146+
print_event_info->delimiter,
1147+
print_event_info->delimiter);
1148+
}
1149+
}
10931150
if (head->error == -1)
10941151
goto err;
10951152
if (opt_remote_proto == BINLOG_LOCAL)
@@ -1331,7 +1388,21 @@ Exit_status process_event(PRINT_EVENT_INFO *print_event_info, Log_event *ev,
13311388
Fake rotate events have 'when' set to zero. @c fake_rotate_event(...).
13321389
*/
13331390
bool is_fake= (rev->when.tv_sec == 0);
1334-
if (!in_transaction && !is_fake)
1391+
/*
1392+
'in_transaction' flag is not set to true even after GTID_LOG_EVENT
1393+
of a transaction is seen. ('mysqlbinlog' tool assumes that there
1394+
is only one event per DDL transaction other than BEGIN and COMMIT
1395+
events. Using 'in_transaction' flag and 'starts_group', 'ends_group'
1396+
flags, DDL transaction generation is handled. Hence 'in_transaction'
1397+
cannot be set to true after seeing GTID_LOG_EVENT). So in order to
1398+
see if we are out of a transaction or not, we should check that
1399+
'in_transaction' is false and we have not seen GTID_LOG_EVENT.
1400+
To see if a GTID_LOG_EVENT of a transaction is seen or not,
1401+
we should check is_gtid_next_valid flag is false.
1402+
*/
1403+
if (!is_fake && !in_transaction &&
1404+
print_event_info->is_gtid_next_set &&
1405+
!print_event_info->is_gtid_next_valid)
13351406
{
13361407
/*
13371408
If processing multiple files, we must reset this flag,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
include/master-slave.inc
2+
Warnings:
3+
Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
4+
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.
5+
[connection master]
6+
CREATE TABLE t1(i INT) ENGINE=InnoDB;
7+
INSERT INTO t1 VALUES (1);
8+
include/sync_slave_sql_with_master.inc
9+
SET @save_debug=@@global.debug;
10+
SET GLOBAL DEBUG='d,stop_io_after_reading_gtid_log_event';
11+
[connection master]
12+
INSERT INTO t1 VALUES (2);
13+
[connection slave]
14+
include/wait_for_slave_io_to_stop.inc
15+
include/assert.inc [Slave MASTER_AUTO_POSITION should be enabled for this test]
16+
SET GLOBAL DEBUG= @save_debug;
17+
include/start_slave.inc
18+
[connection master]
19+
include/sync_slave_sql_with_master.inc
20+
DROP TABLE t1;
21+
RESET MASTER;
22+
include/assert.inc [Check that there is one tuple in the table]
23+
DROP TABLE t1;
24+
RESET MASTER;
25+
include/assert.inc [Check that there are two tuples in the table]
26+
[connection master]
27+
DROP TABLE t1;
28+
include/rpl_end.inc
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
############################################################################
2+
# Bug #17650326 MYSQLBINLOG PRINTS INVALID SQL FROM RELAY LOGS WHEN GTID IS
3+
# ENABLED
4+
# Problem: Replaying a relaylog which ends with GTID_LOG_EVENT causes error
5+
# "ERROR 1790 (HY000) @@SESSION.GTID_NEXT cannot be changed by a client that
6+
# owns a GTID. The client owns <UUID:GNO>. Ownership is released on COMMIT
7+
# or ROLLBACK."
8+
#
9+
# Steps to reproduce:
10+
# 1) Stop IO thread after reading gtid_log_event (relaylog 1 contains
11+
# GTID_LOG_EVENT at the end)
12+
# 2) Restart IO thread which rotates relaylog file (relaylog 2)
13+
# 3) Replay relaylog1's 'mysqlbinlog' output against 'mysql' client tool,
14+
# it should not cause any problems.
15+
# 4) Replay (relaylog1 + relaylog2)'s 'mysqlbinlog' output against
16+
# 'mysql' client tool, it should not cause any problems.
17+
#
18+
############################################################################
19+
--source include/have_debug.inc
20+
# This test is not need against MTS setup
21+
--source include/not_mts_slave_parallel_workers.inc
22+
--source include/have_gtid.inc
23+
--source include/have_binlog_format_statement.inc
24+
--source include/master-slave.inc
25+
26+
# Initial setup
27+
CREATE TABLE t1(i INT) ENGINE=InnoDB;
28+
INSERT INTO t1 VALUES (1);
29+
--source include/sync_slave_sql_with_master.inc
30+
--let MYSQLD_DATADIR=`select @@datadir`
31+
SET @save_debug=@@global.debug;
32+
33+
# Step 1: Stop I/O thread after reading GTID_LOG_EVENT
34+
# This will leave the relaylog in incomplete state(i.e., it ends
35+
# with GTID_LOG_EVENT)
36+
SET GLOBAL DEBUG='d,stop_io_after_reading_gtid_log_event';
37+
38+
--source include/rpl_connection_master.inc
39+
INSERT INTO t1 VALUES (2);
40+
41+
--source include/rpl_connection_slave.inc
42+
--source include/wait_for_slave_io_to_stop.inc
43+
--let $relay_file1 = query_get_value( SHOW SLAVE STATUS, Relay_Log_File, 1 )
44+
45+
--let $auto_pos = query_get_value( SHOW SLAVE STATUS, Auto_Position, 1 )
46+
--let $assert_text= Slave MASTER_AUTO_POSITION should be enabled for this test
47+
--let $assert_cond= $auto_pos= 1
48+
--source include/assert.inc
49+
50+
# Step 2: Restart I/O thread and execute the pending INSERT(2)
51+
SET GLOBAL DEBUG= @save_debug;
52+
--source include/start_slave.inc
53+
54+
--source include/rpl_connection_master.inc
55+
--source include/sync_slave_sql_with_master.inc
56+
--let $relay_file2 = query_get_value( SHOW SLAVE STATUS, Relay_Log_File, 1 )
57+
58+
# Step 3: Replay the incomplete Relaylog's mysqlbinlog output against mysql and see that
59+
# there are no issues.
60+
DROP TABLE t1;
61+
RESET MASTER;
62+
--exec $MYSQL_BINLOG --force-if-open $MYSQLD_DATADIR/$relay_file1 | $MYSQL -uroot -S$SLAVE_MYSOCK
63+
64+
--let $assert_text= Check that there is one tuple in the table
65+
--let $assert_cond= [SELECT COUNT(*) AS Val FROM t1 where i=1, Val, 1] = 1
66+
--source include/assert.inc
67+
68+
# Step 4: Concat two relay logs and then replay mysqlbinlog ouput against mysql and see
69+
# that there are no issues.
70+
DROP TABLE t1;
71+
RESET MASTER;
72+
--exec $MYSQL_BINLOG --force-if-open $MYSQLD_DATADIR/$relay_file1 $MYSQLD_DATADIR/$relay_file2 | $MYSQL -uroot -S$SLAVE_MYSOCK
73+
74+
--let $assert_text= Check that there are two tuples in the table
75+
--let $assert_cond= [SELECT COUNT(*) AS Val FROM t1 where i=1 or i=2, Val, 1] = 2
76+
--source include/assert.inc
77+
78+
# Cleanup
79+
--source include/rpl_connection_master.inc
80+
DROP TABLE t1;
81+
--source include/rpl_end.inc

sql/log_event.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -13781,7 +13781,7 @@ st_print_event_info::st_print_event_info()
1378113781
auto_increment_increment(0),auto_increment_offset(0), charset_inited(0),
1378213782
lc_time_names_number(~0),
1378313783
charset_database_number(ILLEGAL_CHARSET_INFO_NUMBER),
13784-
thread_id(0), thread_id_printed(false),
13784+
thread_id(0), thread_id_printed(false),server_id_from_fd_event(0),
1378513785
base64_output_mode(BASE64_OUTPUT_UNSPEC), printed_fd_event(FALSE),
1378613786
have_unflushed_events(false), skipped_event_in_transaction(false),
1378713787
is_gtid_next_set(false), is_gtid_next_valid(true)

sql/log_event.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
1+
/* Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
22
33
This program is free software; you can redistribute it and/or modify
44
it under the terms of the GNU General Public License as published by
@@ -788,6 +788,7 @@ typedef struct st_print_event_info
788788
uint charset_database_number;
789789
uint thread_id;
790790
bool thread_id_printed;
791+
uint32 server_id_from_fd_event;
791792

792793
st_print_event_info();
793794

0 commit comments

Comments
 (0)