Skip to content
/ server Public

Commit 2fd25d7

Browse files
committed
MDEV-37541 Race of rolling back and committing transaction to binlog
Two transactions could binlog their completions in opposite to how it is done in Engine. That is is rare situations ROLLBACK in Engine of the dependency parent transaction could be scheduled by the transaction before its binlogging. That give a follower dependency child one get binlogged ahead of the parent. For fixing this bug its necessary to ensure the binlogging phase is always first one in the internal one-phase rollback protocol. The commit makes sure the binlog handlerton always rollbacks as first handlerton in no-2pc cases. An added test demonstrates how the child could otherwise reach binlog before its parent.
1 parent 5b05829 commit 2fd25d7

File tree

4 files changed

+101
-2
lines changed

4 files changed

+101
-2
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
create table t1 (a int primary key, b text) engine=innodb;
2+
connect trx1_rollback,localhost,root,,;
3+
CREATE TABLE t_x (a int) engine=MEMORY;
4+
SET binlog_format=row;
5+
CREATE TEMPORARY TABLE tt_tmp ( id INT ) ENGINE = Memory;
6+
BEGIN;
7+
insert into t_x values (1);
8+
drop temporary table tt_tmp;
9+
insert into t1 values (99, "gotta log first");
10+
SET DEBUG_SYNC= 'reset';
11+
SET DEBUG_SYNC= "before_group_commit_queue SIGNAL trx1_at_log WAIT_FOR trx1_go";
12+
ROLLBACK;
13+
connect trx2_commit,localhost,root,,;
14+
insert into t1 values (99, "second best in binlog");
15+
connection default;
16+
SET DEBUG_SYNC= "now WAIT_FOR trx1_at_log";
17+
SET DEBUG_SYNC= "now SIGNAL trx1_go";
18+
connection trx2_commit;
19+
select * from t1;
20+
a b
21+
99 second best in binlog
22+
# Prove the logging order is Trx1, Trx2
23+
include/show_binlog_events.inc
24+
Log_name Pos Event_type Server_id End_log_pos Info
25+
master-bin.000001 # Gtid # # BEGIN GTID #-#-#
26+
master-bin.000001 # Annotate_rows # # insert into t_x values (1)
27+
master-bin.000001 # Table_map # # table_id: # (test.t_x)
28+
master-bin.000001 # Write_rows_v1 # # table_id: # flags: STMT_END_F
29+
master-bin.000001 # Query # # COMMIT
30+
master-bin.000001 # Gtid # # BEGIN GTID #-#-#
31+
master-bin.000001 # Annotate_rows # # insert into t1 values (99, "gotta log first")
32+
master-bin.000001 # Table_map # # table_id: # (test.t1)
33+
master-bin.000001 # Write_rows_v1 # # table_id: # flags: STMT_END_F
34+
master-bin.000001 # Query # # ROLLBACK
35+
master-bin.000001 # Gtid # # BEGIN GTID #-#-#
36+
master-bin.000001 # Query # # use `test`; insert into t1 values (99, "second best in binlog")
37+
master-bin.000001 # Xid # # COMMIT /* XID */
38+
drop table t_x, t1;
39+
disconnect trx1_rollback;
40+
disconnect trx2_commit;
41+
# end of the tests
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
--source include/have_debug_sync.inc
2+
--source include/have_innodb.inc
3+
--source include/have_binlog_format_mixed.inc
4+
5+
create table t1 (a int primary key, b text) engine=innodb;
6+
7+
connect(trx1_rollback,localhost,root,,);
8+
CREATE TABLE t_x (a int) engine=MEMORY;
9+
10+
--let $master_file= query_get_value(SHOW MASTER STATUS, File, 1)
11+
--let $binlog_start=query_get_value(SHOW MASTER STATUS, Position, 1)
12+
SET binlog_format=row;
13+
CREATE TEMPORARY TABLE tt_tmp ( id INT ) ENGINE = Memory;
14+
BEGIN;
15+
insert into t_x values (1);
16+
drop temporary table tt_tmp;
17+
insert into t1 values (99, "gotta log first");
18+
19+
SET DEBUG_SYNC= 'reset';
20+
SET DEBUG_SYNC= "before_group_commit_queue SIGNAL trx1_at_log WAIT_FOR trx1_go";
21+
22+
--send ROLLBACK
23+
24+
connect(trx2_commit,localhost,root,,);
25+
--send insert into t1 values (99, "second best in binlog")
26+
27+
connection default;
28+
29+
# Make sure ROLLBACK is in the binlogging phase ..
30+
SET DEBUG_SYNC= "now WAIT_FOR trx1_at_log";
31+
# .. and the contender trx2 in the locking phase ..
32+
let $wait_condition=
33+
select count(*) = 1 from information_schema.innodb_trx
34+
where trx_state = "LOCK WAIT" and trx_query like "%insert into t1 values%";
35+
source include/wait_condition.inc;
36+
# .. when both provided unfreeze the trx:s ..
37+
SET DEBUG_SYNC= "now SIGNAL trx1_go";
38+
39+
connection trx2_commit;
40+
reap;
41+
select * from t1;
42+
43+
# .. to observe proper binlogging.
44+
--echo # Prove the logging order is Trx1, Trx2
45+
--source include/show_binlog_events.inc
46+
47+
drop table t_x, t1;
48+
disconnect trx1_rollback;
49+
disconnect trx2_commit;
50+
--echo # end of the tests

sql/handler.cc

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2308,11 +2308,18 @@ int ha_rollback_trans(THD *thd, bool all)
23082308
if (is_real_trans) /* not a statement commit */
23092309
thd->stmt_map.close_transient_cursors();
23102310

2311+
int err;
2312+
if (has_binlog_hton(ha_info) &&
2313+
(err= binlog_hton->rollback(binlog_hton, thd, all)))
2314+
{
2315+
my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
2316+
error= 1;
2317+
}
23112318
for (; ha_info; ha_info= ha_info_next)
23122319
{
2313-
int err;
23142320
handlerton *ht= ha_info->ht();
2315-
if ((err= ht->rollback(ht, thd, all)))
2321+
2322+
if (ht != binlog_hton && (err= ht->rollback(ht, thd, all)))
23162323
{
23172324
// cannot happen
23182325
my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);

sql/log.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8235,6 +8235,7 @@ MYSQL_BIN_LOG::queue_for_group_commit(group_commit_entry *orig_entry)
82358235
bool
82368236
MYSQL_BIN_LOG::write_transaction_to_binlog_events(group_commit_entry *entry)
82378237
{
8238+
DEBUG_SYNC(entry->thd, "before_group_commit_queue");
82388239
int is_leader= queue_for_group_commit(entry);
82398240
#ifdef WITH_WSREP
82408241
/* commit order was released in queue_for_group_commit() call,

0 commit comments

Comments
 (0)