mirror of
https://github.com/MariaDB/server.git
synced 2025-07-29 05:21:33 +03:00
Fix for bug #51263 "Deadlock between transactional
SELECT and ALTER TABLE ... REBUILD PARTITION". ALTER TABLE on InnoDB table (including partitioned tables) acquired exclusive locks on rows of table being altered. In cases when there was concurrent transaction which did locking reads from this table this sometimes led to a deadlock which was not detected by MDL subsystem nor by InnoDB engine (and was reported only after exceeding innodb_lock_wait_timeout). This problem stemmed from the fact that ALTER TABLE acquired TL_WRITE_ALLOW_READ lock on table being altered. This lock was interpreted as a write lock and thus for table being altered handler::external_lock() method was called with F_WRLCK as an argument. As result InnoDB engine treated ALTER TABLE as an operation which is going to change data and acquired LOCK_X locks on rows being read from old version of table. In case when there was a transaction which already acquired SR metadata lock on table and some LOCK_S locks on its rows (e.g. by using it in subquery of DML statement) concurrent ALTER TABLE was blocked at the moment when it tried to acquire LOCK_X lock before reading one of these rows. The transaction's attempt to acquire SW metadata lock on table being altered led to deadlock, since it had to wait for ALTER TABLE to release SNW lock. This deadlock was not detected and got resolved only after timeout expiring because waiting were happening in two different subsystems. Similar deadlocks could have occured in other situations. This patch tries to solve the problem by changing ALTER TABLE implementation to use TL_READ_NO_INSERT lock instead of TL_WRITE_ALLOW_READ. After this step handler::external_lock() is called with F_RDLCK as an argument and InnoDB engine correctly interprets ALTER TABLE as operation which only reads data from original version of table. Thanks to this ALTER TABLE acquires only LOCK_S locks on rows it reads. This, in its turn, causes inter-subsystem deadlocks to go away, as all potential lock conflicts and thus deadlocks will be limited to metadata locking subsystem: - When ALTER TABLE reads rows from table being altered it can't encounter any locks which conflict with LOCK_S row locks. There should be no concurrent transactions holding LOCK_X row locks. Such a transaction should have been acquired SW metadata lock on table first which would have conflicted with ALTER's SNW lock. - Vice versa, when DML which runs concurrently with ALTER TABLE tries to lock row it should be requesting only LOCK_S lock which is compatible with locks acquired by ALTER, as otherwise such DML must own an SW metadata lock on table which would be incompatible with ALTER's SNW lock.
This commit is contained in:
@ -562,3 +562,68 @@ drop view v1, v2;
|
||||
drop procedure p1;
|
||||
drop procedure p2;
|
||||
drop table t1, t2, t3, t4, t5;
|
||||
#
|
||||
# Test for bug#51263 "Deadlock between transactional SELECT
|
||||
# and ALTER TABLE ... REBUILD PARTITION".
|
||||
#
|
||||
drop table if exists t1, t2;
|
||||
create table t1 (i int auto_increment not null primary key) engine=innodb;
|
||||
create table t2 (i int) engine=innodb;
|
||||
insert into t1 values (1), (2), (3), (4), (5);
|
||||
begin;
|
||||
# Acquire SR metadata lock on t1 and LOCK_S row-locks on its rows.
|
||||
insert into t2 select count(*) from t1;
|
||||
# Switching to connection 'con1'.
|
||||
# Sending:
|
||||
alter table t1 add column j int;
|
||||
# Switching to connection 'default'.
|
||||
# Wait until ALTER is blocked because it tries to upgrade SNW
|
||||
# metadata lock to X lock.
|
||||
# It should not be blocked during copying data to new version of
|
||||
# table as it acquires LOCK_S locks on rows of old version, which
|
||||
# are compatible with locks acquired by connection 'con1'.
|
||||
# The below statement will deadlock because it will try to acquire
|
||||
# SW lock on t1, which will conflict with ALTER's SNW lock. And
|
||||
# ALTER will be waiting for this connection to release its SR lock.
|
||||
# This deadlock should be detected by an MDL subsystem and this
|
||||
# statement should be aborted with an appropriate error.
|
||||
insert into t1 values (6);
|
||||
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
|
||||
# Unblock ALTER TABLE.
|
||||
commit;
|
||||
# Switching to connection 'con1'.
|
||||
# Reaping ALTER TABLE.
|
||||
# Switching to connection 'default'.
|
||||
#
|
||||
# Now test for scenario in which bug was reported originally.
|
||||
#
|
||||
drop tables t1, t2;
|
||||
create table t1 (i int auto_increment not null primary key) engine=innodb
|
||||
partition by hash (i) partitions 4;
|
||||
create table t2 (i int) engine=innodb;
|
||||
insert into t1 values (1), (2), (3), (4), (5);
|
||||
begin;
|
||||
# Acquire SR metadata lock on t1.
|
||||
select * from t1;
|
||||
i
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
# Switching to connection 'con1'.
|
||||
# Sending:
|
||||
alter table t1 rebuild partition p0;
|
||||
# Switching to connection 'default'.
|
||||
# Wait until ALTER is blocked because of active SR lock.
|
||||
# The below statement should succeed as transaction
|
||||
# has SR metadata lock on t1 and only going to read
|
||||
# rows from it.
|
||||
insert into t2 select count(*) from t1;
|
||||
# Unblock ALTER TABLE.
|
||||
commit;
|
||||
# Switching to connection 'con1'.
|
||||
# Reaping ALTER TABLE.
|
||||
# Switching to connection 'default'.
|
||||
# Clean-up.
|
||||
drop tables t1, t2;
|
||||
|
Reference in New Issue
Block a user