mirror of
https://github.com/MariaDB/server.git
synced 2025-07-29 05:21:33 +03:00
Implementation of simple deadlock detection for metadata locks.
This change is supposed to reduce number of ER_LOCK_DEADLOCK errors which occur when multi-statement transaction encounters conflicting metadata lock in cases when waiting is possible. The idea is not to fail ER_LOCK_DEADLOCK error immediately when we encounter conflicting metadata lock. Instead we release all metadata locks acquired by current statement and start to wait until conflicting lock go away. To avoid deadlocks we use simple empiric which aborts waiting with ER_LOCK_DEADLOCK error if it turns out that somebody is waiting for metadata locks owned by this transaction. This patch also fixes bug #46273 "MySQL 5.4.4 new MDL: Bug#989 is not fully fixed in case of ALTER". The bug was that concurrent execution of UPDATE or MULTI-UPDATE statement as a part of multi-statement transaction that already has used table being updated and ALTER TABLE statement might have resulted of loss of isolation between this transaction and ALTER TABLE statement, which manifested itself as changes performed by ALTER TABLE becoming visible in transaction and wrong binary log order as a consequence. This problem occurred when UPDATE or MULTI-UPDATE's wait in mysql_lock_tables() call was aborted due to metadata lock upgrade performed by concurrent ALTER TABLE. After such abort all metadata locks held by transaction were released but transaction silently continued to be executed as if nothing has happened. We solve this problem by changing our code not to release all locks in such case. Instead we release only locks which were acquired by current statement and then try to reacquire them by restarting open/lock tables process. We piggyback on simple deadlock detector implementation since this change has to be done anyway for it.
This commit is contained in:
@ -732,7 +732,7 @@ connection default;
|
||||
--disable_warnings
|
||||
drop table if exists t1;
|
||||
--enable_warnings
|
||||
create table t1 (a int);
|
||||
create table t1 (a int, key a (a));
|
||||
insert into t1 values (1);
|
||||
handler t1 open;
|
||||
connection con1;
|
||||
@ -743,7 +743,6 @@ let $wait_condition=
|
||||
where state = "Waiting for table" and info = "alter table t1 engine=memory";
|
||||
--source include/wait_condition.inc
|
||||
connection default;
|
||||
--error ER_ILLEGAL_HA
|
||||
handler t1 read a next;
|
||||
handler t1 close;
|
||||
connection con1;
|
||||
@ -983,11 +982,12 @@ lock table t2 read;
|
||||
--echo # --> connection con2
|
||||
connection con2;
|
||||
--echo # Sending:
|
||||
--send drop table t2
|
||||
send rename table t2 to t3, t1 to t2, t3 to t1;
|
||||
--echo # --> connection con1
|
||||
connection con1;
|
||||
--echo # Waiting for 'drop table t2' to get blocked...
|
||||
let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t2';
|
||||
--echo # Waiting for 'rename table ...' to get blocked...
|
||||
let $wait_condition=select count(*)=1 from information_schema.processlist
|
||||
where state='Waiting for table' and info='rename table t2 to t3, t1 to t2, t3 to t1';
|
||||
--source include/wait_condition.inc
|
||||
--echo # --> connection default
|
||||
connection default;
|
||||
@ -997,25 +997,26 @@ handler t2 open;
|
||||
select * from t2;
|
||||
handler t1 open;
|
||||
commit;
|
||||
--error ER_LOCK_DEADLOCK
|
||||
handler t2 open;
|
||||
handler t1 close;
|
||||
--echo # --> connection con1
|
||||
connection con1;
|
||||
unlock tables;
|
||||
--echo # --> connection con2
|
||||
connection con2;
|
||||
--echo # Reaping 'drop table t2'...
|
||||
--echo # Reaping 'rename table ...'...
|
||||
--reap
|
||||
--echo # --> connection default
|
||||
connection default;
|
||||
handler t1 open;
|
||||
handler t1 read a prev;
|
||||
handler t1 close;
|
||||
drop table t2;
|
||||
--echo #
|
||||
--echo # Likewise, this doesn't require a multi-statement transaction.
|
||||
--echo # ER_LOCK_DEADLOCK is also produced when we have an open
|
||||
--echo # HANDLER and try to acquire locks for a single statement.
|
||||
--echo # Originally there was a deadlock error in this test.
|
||||
--echo # With implementation of deadlock detector
|
||||
--echo # we no longer deadlock, but block and wait on a lock.
|
||||
--echo # The HANDLER is auto-closed as soon as the connection
|
||||
--echo # sees a pending conflicting lock against it.
|
||||
--echo #
|
||||
create table t2 (a int, key a (a));
|
||||
handler t1 open;
|
||||
@ -1033,10 +1034,12 @@ let $wait_condition=select count(*)=1 from information_schema.processlist where
|
||||
--source include/wait_condition.inc
|
||||
--echo # --> connection default
|
||||
connection default;
|
||||
--error ER_LOCK_DEADLOCK
|
||||
select * from t2;
|
||||
--echo # Sending 'select * from t2'
|
||||
send select * from t2;
|
||||
--echo # --> connection con1
|
||||
connection con1;
|
||||
--echo # Waiting for 'select * from t2' to get blocked...
|
||||
let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='select * from t2';
|
||||
unlock tables;
|
||||
--echo # --> connection con2
|
||||
connection con2;
|
||||
@ -1044,6 +1047,9 @@ connection con2;
|
||||
--reap
|
||||
--echo # --> connection default
|
||||
connection default;
|
||||
--echo # Reaping 'select * from t2'
|
||||
--error ER_NO_SUCH_TABLE
|
||||
reap;
|
||||
handler t1 close;
|
||||
|
||||
--echo #
|
||||
|
Reference in New Issue
Block a user