diff options
-rw-r--r-- | mysql-test/suite/rpl/r/rpl_xa.result | 61 | ||||
-rw-r--r-- | mysql-test/suite/rpl/r/rpl_xa_gtid_pos_auto_engine.result | 61 | ||||
-rw-r--r-- | mysql-test/suite/rpl/t/rpl_xa.inc | 80 | ||||
-rw-r--r-- | storage/innobase/include/lock0lock.h | 6 | ||||
-rw-r--r-- | storage/innobase/lock/lock0lock.cc | 59 | ||||
-rw-r--r-- | storage/innobase/trx/trx0trx.cc | 14 |
6 files changed, 279 insertions, 2 deletions
diff --git a/mysql-test/suite/rpl/r/rpl_xa.result b/mysql-test/suite/rpl/r/rpl_xa.result index a90e6e0b996..061c7b360d0 100644 --- a/mysql-test/suite/rpl/r/rpl_xa.result +++ b/mysql-test/suite/rpl/r/rpl_xa.result @@ -219,4 +219,65 @@ include/sync_with_master_gtid.inc connection master; drop database test_ign; drop table t1, t2, t3, tm; +# +# MDEV-26682 slave lock timeout with XA and gap locks +# +create table t1 (a int primary key, b int unique) engine=innodb; +insert t1 values (1,1),(3,3),(5,5); +connection slave; +set session tx_isolation='repeatable-read'; +start transaction; +select * from t1; +a b +1 1 +3 3 +5 5 +connect m2, localhost, root; +delete from t1 where a=3; +xa start 'x1'; +update t1 set b=3 where a=5; +xa end 'x1'; +xa prepare 'x1'; +connect m3, localhost, root; +insert t1 values (2, 2); +-->slave +connection slave; +commit; +select * from t1; +a b +1 1 +2 2 +5 5 +connection m2; +xa rollback 'x1'; +disconnect m2; +disconnect m3; +connection master; +drop table t1; +create table t1 (id int auto_increment primary key, c1 int not null unique) +engine=innodb; +create table t2 (id int auto_increment primary key, c1 int not null, +foreign key(c1) references t1(c1), unique key(c1)) engine=innodb; +insert t1 values (869,1), (871,3), (873,4), (872,5), (870,6), (877,7); +insert t2 values (795,6), (800,7); +xa start '1'; +update t2 set id = 9, c1 = 5 where c1 in (null, null, null, null, null, 7, 3); +connect con1, localhost,root; +xa start '2'; +delete from t1 where c1 like '3%'; +xa end '2'; +xa prepare '2'; +connection master; +xa end '1'; +xa prepare '1'; +->slave +connection slave; +connection slave; +include/sync_with_master_gtid.inc +connection con1; +xa commit '2'; +disconnect con1; +connection master; +xa commit '1'; +drop table t2, t1; include/rpl_end.inc diff --git a/mysql-test/suite/rpl/r/rpl_xa_gtid_pos_auto_engine.result b/mysql-test/suite/rpl/r/rpl_xa_gtid_pos_auto_engine.result index ffd0426ab0d..35625cc7026 100644 --- a/mysql-test/suite/rpl/r/rpl_xa_gtid_pos_auto_engine.result +++ b/mysql-test/suite/rpl/r/rpl_xa_gtid_pos_auto_engine.result @@ -228,6 +228,67 @@ include/sync_with_master_gtid.inc connection master; drop database test_ign; drop table t1, t2, t3, tm; +# +# MDEV-26682 slave lock timeout with XA and gap locks +# +create table t1 (a int primary key, b int unique) engine=innodb; +insert t1 values (1,1),(3,3),(5,5); +connection slave; +set session tx_isolation='repeatable-read'; +start transaction; +select * from t1; +a b +1 1 +3 3 +5 5 +connect m2, localhost, root; +delete from t1 where a=3; +xa start 'x1'; +update t1 set b=3 where a=5; +xa end 'x1'; +xa prepare 'x1'; +connect m3, localhost, root; +insert t1 values (2, 2); +-->slave +connection slave; +commit; +select * from t1; +a b +1 1 +2 2 +5 5 +connection m2; +xa rollback 'x1'; +disconnect m2; +disconnect m3; +connection master; +drop table t1; +create table t1 (id int auto_increment primary key, c1 int not null unique) +engine=innodb; +create table t2 (id int auto_increment primary key, c1 int not null, +foreign key(c1) references t1(c1), unique key(c1)) engine=innodb; +insert t1 values (869,1), (871,3), (873,4), (872,5), (870,6), (877,7); +insert t2 values (795,6), (800,7); +xa start '1'; +update t2 set id = 9, c1 = 5 where c1 in (null, null, null, null, null, 7, 3); +connect con1, localhost,root; +xa start '2'; +delete from t1 where c1 like '3%'; +xa end '2'; +xa prepare '2'; +connection master; +xa end '1'; +xa prepare '1'; +->slave +connection slave; +connection slave; +include/sync_with_master_gtid.inc +connection con1; +xa commit '2'; +disconnect con1; +connection master; +xa commit '1'; +drop table t2, t1; connection slave; include/stop_slave.inc SET @@global.gtid_pos_auto_engines=""; diff --git a/mysql-test/suite/rpl/t/rpl_xa.inc b/mysql-test/suite/rpl/t/rpl_xa.inc index 38344da5e66..d22d2d2ef3d 100644 --- a/mysql-test/suite/rpl/t/rpl_xa.inc +++ b/mysql-test/suite/rpl/t/rpl_xa.inc @@ -1,6 +1,6 @@ # # This "body" file checks general properties of XA transaction replication -# as of MDEV-7974. +# as of MDEV-742. # Parameters: # --let rpl_xa_check= SELECT ... # @@ -353,3 +353,81 @@ source include/sync_with_master_gtid.inc; connection master; --eval drop database test_ign drop table t1, t2, t3, tm; + +--echo # +--echo # MDEV-26682 slave lock timeout with XA and gap locks +--echo # +create table t1 (a int primary key, b int unique) engine=innodb; +insert t1 values (1,1),(3,3),(5,5); +sync_slave_with_master; + +# set a strong isolation level to keep the read view below. +# alternatively a long-running select can do that too even in read-committed +set session tx_isolation='repeatable-read'; +start transaction; +# opens a read view to disable purge on the slave +select * from t1; + +connect m2, localhost, root; +# now, delete a value, purge it on the master, but not on the slave +delete from t1 where a=3; +xa start 'x1'; +# this sets a gap lock on <3>, when it exists (so, on the slave) +update t1 set b=3 where a=5; +xa end 'x1'; +xa prepare 'x1'; + +connect m3, localhost, root; +# and this tries to insert straight into the locked gap +insert t1 values (2, 2); + +echo -->slave; +sync_slave_with_master; +commit; +select * from t1; + +connection m2; +xa rollback 'x1'; +disconnect m2; +disconnect m3; + +connection master; + +drop table t1; + +create table t1 (id int auto_increment primary key, c1 int not null unique) +engine=innodb; + +create table t2 (id int auto_increment primary key, c1 int not null, +foreign key(c1) references t1(c1), unique key(c1)) engine=innodb; + +insert t1 values (869,1), (871,3), (873,4), (872,5), (870,6), (877,7); +insert t2 values (795,6), (800,7); + +xa start '1'; +update t2 set id = 9, c1 = 5 where c1 in (null, null, null, null, null, 7, 3); + +connect con1, localhost,root; +xa start '2'; +delete from t1 where c1 like '3%'; +xa end '2'; +xa prepare '2'; + +connection master; +xa end '1'; +xa prepare '1'; + +echo ->slave; + +sync_slave_with_master; + +connection slave; +source include/sync_with_master_gtid.inc; + +connection con1; +xa commit '2'; +disconnect con1; + +connection master; +xa commit '1'; +drop table t2, t1; diff --git a/storage/innobase/include/lock0lock.h b/storage/innobase/include/lock0lock.h index 3b63b06a9bb..225c246f4e7 100644 --- a/storage/innobase/include/lock0lock.h +++ b/storage/innobase/include/lock0lock.h @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2017, 2020, MariaDB Corporation. +Copyright (c) 2017, 2021, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -478,6 +478,10 @@ lock_rec_unlock( and release possible other transactions waiting because of these locks. */ void lock_release(trx_t* trx); +/** Release non-exclusive locks on XA PREPARE, +and release possible other transactions waiting because of these locks. */ +void lock_release_on_prepare(trx_t *trx); + /*************************************************************//** Get the lock hash table */ UNIV_INLINE diff --git a/storage/innobase/lock/lock0lock.cc b/storage/innobase/lock/lock0lock.cc index d7ec5736826..12764470bef 100644 --- a/storage/innobase/lock/lock0lock.cc +++ b/storage/innobase/lock/lock0lock.cc @@ -4219,6 +4219,65 @@ void lock_release(trx_t* trx) #endif } +/** Release non-exclusive locks on XA PREPARE, +and release possible other transactions waiting because of these locks. */ +void lock_release_on_prepare(trx_t *trx) +{ + ulint count= 0; + lock_mutex_enter(); + ut_ad(!trx_mutex_own(trx)); + + for (lock_t *lock= UT_LIST_GET_LAST(trx->lock.trx_locks); lock; ) + { + ut_ad(lock->trx == trx); + + if (lock_get_type_low(lock) == LOCK_REC) + { + ut_ad(!lock->index->table->is_temporary()); + if (lock_rec_get_gap(lock) || lock_get_mode(lock) != LOCK_X) + lock_rec_dequeue_from_page(lock); + else + { + ut_ad(trx->dict_operation || + lock->index->table->id >= DICT_HDR_FIRST_ID); +retain_lock: + lock= UT_LIST_GET_PREV(trx_locks, lock); + continue; + } + } + else + { + ut_ad(lock_get_type_low(lock) & LOCK_TABLE); + dict_table_t *table= lock->un_member.tab_lock.table; + ut_ad(!table->is_temporary()); + + switch (lock_get_mode(lock)) { + case LOCK_IS: + case LOCK_S: + lock_table_dequeue(lock); + break; + case LOCK_IX: + case LOCK_X: + ut_ad(table->id >= DICT_HDR_FIRST_ID || trx->dict_operation); + /* fall through */ + default: + goto retain_lock; + } + } + + if (++count == LOCK_RELEASE_INTERVAL) + { + lock_mutex_exit(); + count= 0; + lock_mutex_enter(); + } + + lock= UT_LIST_GET_LAST(trx->lock.trx_locks); + } + + lock_mutex_exit(); +} + /* True if a lock mode is S or X */ #define IS_LOCK_S_OR_X(lock) \ (lock_get_mode(lock) == LOCK_S \ diff --git a/storage/innobase/trx/trx0trx.cc b/storage/innobase/trx/trx0trx.cc index cf8fa17cf1a..16ea7c41d71 100644 --- a/storage/innobase/trx/trx0trx.cc +++ b/storage/innobase/trx/trx0trx.cc @@ -1971,6 +1971,20 @@ trx_prepare( We must not be holding any mutexes or latches here. */ trx_flush_log_if_needed(lsn, trx); + + if (!UT_LIST_GET_LEN(trx->lock.trx_locks) + || trx->isolation_level == TRX_ISO_SERIALIZABLE) { + /* Do not release any locks at the + SERIALIZABLE isolation level. */ + } else if (!trx->mysql_thd + || thd_sql_command(trx->mysql_thd) + != SQLCOM_XA_PREPARE) { + /* Do not release locks for XA COMMIT ONE PHASE + or for internal distributed transactions + (XID::get_my_xid() would be nonzero). */ + } else { + lock_release_on_prepare(trx); + } } } |