summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mysql-test/suite/rpl/r/rpl_xa.result61
-rw-r--r--mysql-test/suite/rpl/r/rpl_xa_gtid_pos_auto_engine.result61
-rw-r--r--mysql-test/suite/rpl/t/rpl_xa.inc80
-rw-r--r--storage/innobase/include/lock0lock.h6
-rw-r--r--storage/innobase/lock/lock0lock.cc59
-rw-r--r--storage/innobase/trx/trx0trx.cc14
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);
+ }
}
}