diff --git a/mysql-test/suite/binlog/r/binlog_mdev37541.result b/mysql-test/suite/binlog/r/binlog_mdev37541.result new file mode 100644 index 0000000000000..0c9f162a8f446 --- /dev/null +++ b/mysql-test/suite/binlog/r/binlog_mdev37541.result @@ -0,0 +1,41 @@ +create table t1 (a int primary key, b text) engine=innodb; +connect trx1_rollback,localhost,root,,; +CREATE TABLE t_x (a int) engine=MEMORY; +SET binlog_format=row; +CREATE TEMPORARY TABLE tt_tmp ( id INT ) ENGINE = Memory; +BEGIN; +insert into t_x values (1); +drop temporary table tt_tmp; +insert into t1 values (99, "gotta log first"); +SET DEBUG_SYNC= 'reset'; +SET DEBUG_SYNC= "before_group_commit_queue SIGNAL trx1_at_log WAIT_FOR trx1_go"; +ROLLBACK; +connect trx2_commit,localhost,root,,; +insert into t1 values (99, "second best in binlog"); +connection default; +SET DEBUG_SYNC= "now WAIT_FOR trx1_at_log"; +SET DEBUG_SYNC= "now SIGNAL trx1_go"; +connection trx2_commit; +select * from t1; +a b +99 second best in binlog +# Prove the logging order is Trx1, Trx2 +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Annotate_rows # # insert into t_x values (1) +master-bin.000001 # Table_map # # table_id: # (test.t_x) +master-bin.000001 # Write_rows_v1 # # table_id: # flags: STMT_END_F +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Annotate_rows # # insert into t1 values (99, "gotta log first") +master-bin.000001 # Table_map # # table_id: # (test.t1) +master-bin.000001 # Write_rows_v1 # # table_id: # flags: STMT_END_F +master-bin.000001 # Query # # ROLLBACK +master-bin.000001 # Gtid # # BEGIN GTID #-#-# +master-bin.000001 # Query # # use `test`; insert into t1 values (99, "second best in binlog") +master-bin.000001 # Xid # # COMMIT /* XID */ +drop table t_x, t1; +disconnect trx1_rollback; +disconnect trx2_commit; +# end of the tests diff --git a/mysql-test/suite/binlog/t/binlog_mdev37541.test b/mysql-test/suite/binlog/t/binlog_mdev37541.test new file mode 100644 index 0000000000000..de94f5a5ebddd --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_mdev37541.test @@ -0,0 +1,50 @@ +--source include/have_debug_sync.inc +--source include/have_innodb.inc +--source include/have_binlog_format_mixed.inc + +create table t1 (a int primary key, b text) engine=innodb; + +connect(trx1_rollback,localhost,root,,); +CREATE TABLE t_x (a int) engine=MEMORY; + +--let $master_file= query_get_value(SHOW MASTER STATUS, File, 1) +--let $binlog_start=query_get_value(SHOW MASTER STATUS, Position, 1) +SET binlog_format=row; +CREATE TEMPORARY TABLE tt_tmp ( id INT ) ENGINE = Memory; +BEGIN; + insert into t_x values (1); + drop temporary table tt_tmp; + insert into t1 values (99, "gotta log first"); + +SET DEBUG_SYNC= 'reset'; +SET DEBUG_SYNC= "before_group_commit_queue SIGNAL trx1_at_log WAIT_FOR trx1_go"; + +--send ROLLBACK + +connect(trx2_commit,localhost,root,,); +--send insert into t1 values (99, "second best in binlog") + +connection default; + +# Make sure ROLLBACK is in the binlogging phase .. +SET DEBUG_SYNC= "now WAIT_FOR trx1_at_log"; +# .. and the contender trx2 in the locking phase .. +let $wait_condition= + select count(*) = 1 from information_schema.innodb_trx + where trx_state = "LOCK WAIT" and trx_query like "%insert into t1 values%"; +source include/wait_condition.inc; +# .. when both provided unfreeze the trx:s .. +SET DEBUG_SYNC= "now SIGNAL trx1_go"; + +connection trx2_commit; +reap; +select * from t1; + +# .. to observe proper binlogging. +--echo # Prove the logging order is Trx1, Trx2 +--source include/show_binlog_events.inc + +drop table t_x, t1; +disconnect trx1_rollback; +disconnect trx2_commit; +--echo # end of the tests diff --git a/sql/handler.cc b/sql/handler.cc index 339a28cc6411a..e8d352e94e67c 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -2150,12 +2150,37 @@ static bool is_ro_1pc_trans(THD *thd, Ha_trx_info *ha_info, bool all, return !rw_trans; } -static bool has_binlog_hton(Ha_trx_info *ha_info) +inline Ha_trx_info* get_binlog_hton(Ha_trx_info *ha_info) { - bool rc; - for (rc= false; ha_info && !rc; ha_info= ha_info->next()) - rc= ha_info->ht() == binlog_hton; + for (; ha_info; ha_info= ha_info->next()) + if (ha_info->ht() == binlog_hton) + return ha_info; + + return ha_info; +} + +static int run_binlog_first(THD *thd, bool all, THD_TRANS *trans, + bool is_real_trans, bool is_commit) +{ + int rc= 0; + Ha_trx_info *ha_info= trans->ha_list; + if (mysql_bin_log.is_open()) + { + if ((ha_info= get_binlog_hton(ha_info))) + { + int err; + if ((err= is_commit ? + binlog_commit(thd, all, + is_ro_1pc_trans(thd, ha_info, all, is_real_trans)) : + binlog_rollback(ha_info->ht(), thd, all))) + { + my_error(is_commit? ER_ERROR_DURING_COMMIT : ER_ERROR_DURING_ROLLBACK, + MYF(0), err); + rc= 1; + } + } + } return rc; } @@ -2172,18 +2197,17 @@ commit_one_phase_2(THD *thd, bool all, THD_TRANS *trans, bool is_real_trans) if (ha_info) { int err= 0; - - if (has_binlog_hton(ha_info) && - (err= binlog_commit(thd, all, - is_ro_1pc_trans(thd, ha_info, all, is_real_trans)))) + /* + Binlog hton must be called first regardless of its position + in trans->ha_list. + */ + for (int binlog_err= error= + run_binlog_first(thd, all, trans, is_real_trans, true); + ha_info; ha_info= ha_info_next) { - my_error(ER_ERROR_DURING_COMMIT, MYF(0), err); - error= 1; + if (binlog_err) + goto err; - goto err; - } - for (; ha_info; ha_info= ha_info_next) - { handlerton *ht= ha_info->ht(); if ((err= ht->commit(ht, thd, all))) { @@ -2309,11 +2333,16 @@ int ha_rollback_trans(THD *thd, bool all) if (is_real_trans) /* not a statement commit */ thd->stmt_map.close_transient_cursors(); - for (; ha_info; ha_info= ha_info_next) + /* + Binlog hton must be called first regardless of its position + in trans->ha_list. + */ + for (error= run_binlog_first(thd, all, trans, is_real_trans, false); + ha_info; ha_info= ha_info_next) { int err; handlerton *ht= ha_info->ht(); - if ((err= ht->rollback(ht, thd, all))) + if (ht != binlog_hton && (err= ht->rollback(ht, thd, all))) { // cannot happen my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err); diff --git a/sql/log.cc b/sql/log.cc index 8339d7c5b2395..1039aa019cc00 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -97,7 +97,6 @@ static int binlog_savepoint_set(handlerton *hton, THD *thd, void *sv); static int binlog_savepoint_rollback(handlerton *hton, THD *thd, void *sv); static bool binlog_savepoint_rollback_can_release_mdl(handlerton *hton, THD *thd); -static int binlog_rollback(handlerton *hton, THD *thd, bool all); static int binlog_prepare(handlerton *hton, THD *thd, bool all); static int binlog_start_consistent_snapshot(handlerton *hton, THD *thd); static int binlog_flush_cache(THD *thd, binlog_cache_mngr *cache_mngr, @@ -2439,7 +2438,7 @@ int binlog_commit(THD *thd, bool all, bool ro_1pc) @see handlerton::rollback */ -static int binlog_rollback(handlerton *hton, THD *thd, bool all) +int binlog_rollback(handlerton *hton, THD *thd, bool all) { DBUG_ENTER("binlog_rollback"); @@ -8471,6 +8470,7 @@ MYSQL_BIN_LOG::queue_for_group_commit(group_commit_entry *orig_entry) bool MYSQL_BIN_LOG::write_transaction_to_binlog_events(group_commit_entry *entry) { + DEBUG_SYNC(entry->thd, "before_group_commit_queue"); int is_leader= queue_for_group_commit(entry); #ifdef WITH_WSREP /* commit order was released in queue_for_group_commit() call, diff --git a/sql/log.h b/sql/log.h index e779271c7f58e..d8af49168046d 100644 --- a/sql/log.h +++ b/sql/log.h @@ -1277,6 +1277,7 @@ const char * get_gtid_list_event(IO_CACHE *cache, Gtid_list_log_event **out_gtid_list); int binlog_commit(THD *thd, bool all, bool is_ro_1pc= false); +int binlog_rollback(handlerton *hton, THD *thd, bool all); int binlog_commit_by_xid(handlerton *hton, XID *xid); int binlog_rollback_by_xid(handlerton *hton, XID *xid); bool write_bin_log_start_alter(THD *thd, bool& partial_alter,