Patch that refactors global read lock implementation and fixes
bug #57006 "Deadlock between HANDLER and FLUSH TABLES WITH READ LOCK" and bug #54673 "It takes too long to get readlock for 'FLUSH TABLES WITH READ LOCK'". The first bug manifested itself as a deadlock which occurred when a connection, which had some table open through HANDLER statement, tried to update some data through DML statement while another connection tried to execute FLUSH TABLES WITH READ LOCK concurrently. What happened was that FTWRL in the second connection managed to perform first step of GRL acquisition and thus blocked all upcoming DML. After that it started to wait for table open through HANDLER statement to be flushed. When the first connection tried to execute DML it has started to wait for GRL/the second connection creating deadlock. The second bug manifested itself as starvation of FLUSH TABLES WITH READ LOCK statements in cases when there was a constant stream of concurrent DML statements (in two or more connections). This has happened because requests for protection against GRL which were acquired by DML statements were ignoring presence of pending GRL and thus the latter was starved. This patch solves both these problems by re-implementing GRL using metadata locks. Similar to the old implementation acquisition of GRL in new implementation is two-step. During the first step we block all concurrent DML and DDL statements by acquiring global S metadata lock (each DML and DDL statement acquires global IX lock for its duration). During the second step we block commits by acquiring global S lock in COMMIT namespace (commit code acquires global IX lock in this namespace). Note that unlike in old implementation acquisition of protection against GRL in DML and DDL is semi-automatic. We assume that any statement which should be blocked by GRL will either open and acquires write-lock on tables or acquires metadata locks on objects it is going to modify. For any such statement global IX metadata lock is automatically acquired for its duration. The first problem is solved because waits for GRL become visible to deadlock detector in metadata locking subsystem and thus deadlocks like one in the first bug become impossible. The second problem is solved because global S locks which are used for GRL implementation are given preference over IX locks which are acquired by concurrent DML (and we can switch to fair scheduling in future if needed). Important change: FTWRL/GRL no longer blocks DML and DDL on temporary tables. Before this patch behavior was not consistent in this respect: in some cases DML/DDL statements on temporary tables were blocked while in others they were not. Since the main use cases for FTWRL are various forms of backups and temporary tables are not preserved during backups we have opted for consistently allowing DML/DDL on temporary tables during FTWRL/GRL. Important change: This patch changes thread state names which are used when DML/DDL of FTWRL is waiting for global read lock. It is now either "Waiting for global read lock" or "Waiting for commit lock" depending on the stage on which FTWRL is. Incompatible change: To solve deadlock in events code which was exposed by this patch we have to replace LOCK_event_metadata mutex with metadata locks on events. As result we have to prohibit DDL on events under LOCK TABLES. This patch also adds extensive test coverage for interaction of DML/DDL and FTWRL. Performance of new and old global read lock implementations in sysbench tests were compared. There were no significant difference between new and old implementations.
This commit is contained in:
parent
aee8fce233
commit
378cdc58c1
158
mysql-test/include/check_ftwrl_compatible.inc
Normal file
158
mysql-test/include/check_ftwrl_compatible.inc
Normal file
@ -0,0 +1,158 @@
|
||||
#
|
||||
# SUMMARY
|
||||
# Check that a statement is compatible with FLUSH TABLES WITH READ LOCK.
|
||||
#
|
||||
# PARAMETERS
|
||||
# $con_aux1 Name of the 1st aux connection to be used by this script.
|
||||
# $con_aux2 Name of the 2nd aux connection to be used by this script.
|
||||
# $statement The statement to be checked.
|
||||
# $cleanup_stmt The statement to be run in order to revert effects of
|
||||
# the statement to be checked.
|
||||
# $skip_3rd_chk Skip the 3rd stage of checking. The purpose of the third
|
||||
# stage is to check that metadata locks taken by this
|
||||
# statement are compatible with metadata locks taken
|
||||
# by FTWRL.
|
||||
#
|
||||
# EXAMPLE
|
||||
# flush_read_lock.test
|
||||
#
|
||||
--disable_result_log
|
||||
--disable_query_log
|
||||
|
||||
# Reset DEBUG_SYNC facility for safety.
|
||||
set debug_sync= "RESET";
|
||||
|
||||
#
|
||||
# First, check that the statement can be run under FTWRL.
|
||||
#
|
||||
flush tables with read lock;
|
||||
--disable_abort_on_error
|
||||
--eval $statement
|
||||
--enable_abort_on_error
|
||||
let $err= $mysql_errno;
|
||||
if (!$err)
|
||||
{
|
||||
--echo Success: Was able to run '$statement' under FTWRL.
|
||||
unlock tables;
|
||||
if (`SELECT "$cleanup_stmt" <> ""`)
|
||||
{
|
||||
--eval $cleanup_stmt;
|
||||
}
|
||||
}
|
||||
if ($err)
|
||||
{
|
||||
--echo Error: Wasn't able to run '$statement' under FTWRL!
|
||||
unlock tables;
|
||||
}
|
||||
|
||||
#
|
||||
# Then check that this statement won't be blocked by FTWRL
|
||||
# that is active in another connection.
|
||||
#
|
||||
connection $con_aux1;
|
||||
flush tables with read lock;
|
||||
|
||||
connection default;
|
||||
--send_eval $statement;
|
||||
|
||||
connection $con_aux1;
|
||||
|
||||
--enable_result_log
|
||||
--enable_query_log
|
||||
let $wait_condition=
|
||||
select count(*) = 0 from information_schema.processlist
|
||||
where info = "$statement";
|
||||
--source include/wait_condition.inc
|
||||
--disable_result_log
|
||||
--disable_query_log
|
||||
|
||||
if ($success)
|
||||
{
|
||||
--echo Success: Was able to run '$statement' with FTWRL active in another connection.
|
||||
|
||||
connection default;
|
||||
# Apparently statement was successfully executed and so
|
||||
# was not blocked by FTWRL.
|
||||
# To be safe against wait_condition.inc succeeding due to
|
||||
# races let us first reap the statement being checked to
|
||||
# ensure that it has been successfully executed.
|
||||
--reap
|
||||
|
||||
connection $con_aux1;
|
||||
unlock tables;
|
||||
|
||||
connection default;
|
||||
}
|
||||
if (!$success)
|
||||
{
|
||||
--echo Error: Wasn't able to run '$statement' with FTWRL active in another connection!
|
||||
unlock tables;
|
||||
connection default;
|
||||
--reap
|
||||
}
|
||||
|
||||
if (`SELECT "$cleanup_stmt" <> ""`)
|
||||
{
|
||||
--eval $cleanup_stmt;
|
||||
}
|
||||
|
||||
if (`SELECT "$skip_3rd_check" = ""`)
|
||||
{
|
||||
#
|
||||
# Finally, let us check that FTWRL will succeed if this statement
|
||||
# is active but has already closed its tables.
|
||||
#
|
||||
connection default;
|
||||
set debug_sync='execute_command_after_close_tables SIGNAL parked WAIT_FOR go';
|
||||
--send_eval $statement;
|
||||
|
||||
connection $con_aux1;
|
||||
set debug_sync="now WAIT_FOR parked";
|
||||
--send flush tables with read lock
|
||||
|
||||
connection $con_aux2;
|
||||
--enable_result_log
|
||||
--enable_query_log
|
||||
let $wait_condition=
|
||||
select count(*) = 0 from information_schema.processlist
|
||||
where info = "flush tables with read lock";
|
||||
--source include/wait_condition.inc
|
||||
--disable_result_log
|
||||
--disable_query_log
|
||||
|
||||
if ($success)
|
||||
{
|
||||
--echo Success: Was able to run FTWRL while '$statement' was active in another connection.
|
||||
connection $con_aux1;
|
||||
# Apparently FTWRL was successfully executed and so was not blocked by
|
||||
# the statement being checked. To be safe against wait_condition.inc
|
||||
# succeeding due to races let us first reap the FTWRL to ensure that it
|
||||
# has been successfully executed.
|
||||
--reap
|
||||
unlock tables;
|
||||
set debug_sync="now SIGNAL go";
|
||||
connection default;
|
||||
--reap
|
||||
}
|
||||
if (!$success)
|
||||
{
|
||||
--echo Error: Wasn't able to run FTWRL while '$statement' was active in another connection!
|
||||
set debug_sync="now SIGNAL go";
|
||||
connection default;
|
||||
--reap
|
||||
connection $con_aux1;
|
||||
--reap
|
||||
unlock tables;
|
||||
connection default;
|
||||
}
|
||||
|
||||
set debug_sync= "RESET";
|
||||
if (`SELECT "$cleanup_stmt" <> ""`)
|
||||
{
|
||||
--eval $cleanup_stmt;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
--enable_result_log
|
||||
--enable_query_log
|
155
mysql-test/include/check_ftwrl_incompatible.inc
Normal file
155
mysql-test/include/check_ftwrl_incompatible.inc
Normal file
@ -0,0 +1,155 @@
|
||||
#
|
||||
# SUMMARY
|
||||
# Check that a statement is incompatible with FLUSH TABLES WITH READ LOCK.
|
||||
#
|
||||
# PARAMETERS
|
||||
# $con_aux1 Name of the 1st aux connection to be used by this script.
|
||||
# $con_aux2 Name of the 2nd aux connection to be used by this script.
|
||||
# $statement The statement to be checked.
|
||||
# $cleanup_stmt1 The 1st statement to be run in order to revert effects
|
||||
# of statement to be checked.
|
||||
# $cleanup_stmt2 The 2nd statement to be run in order to revert effects
|
||||
# of statement to be checked.
|
||||
# $skip_3rd_chk Skip the 3rd stage of checking. The purpose of the third
|
||||
# stage is to check that metadata locks taken by this
|
||||
# statement are incompatible with metadata locks taken
|
||||
# by FTWRL.
|
||||
#
|
||||
# EXAMPLE
|
||||
# flush_read_lock.test
|
||||
#
|
||||
--disable_result_log
|
||||
--disable_query_log
|
||||
|
||||
# Reset DEBUG_SYNC facility for safety.
|
||||
set debug_sync= "RESET";
|
||||
|
||||
#
|
||||
# First, check that the statement cannot be run under FTWRL.
|
||||
#
|
||||
flush tables with read lock;
|
||||
--disable_abort_on_error
|
||||
--eval $statement
|
||||
--enable_abort_on_error
|
||||
let $err= $mysql_errno;
|
||||
if ($err)
|
||||
{
|
||||
--echo Success: Was not able to run '$statement' under FTWRL.
|
||||
unlock tables;
|
||||
}
|
||||
if (!$err)
|
||||
{
|
||||
--echo Error: Was able to run '$statement' under FTWRL!
|
||||
unlock tables;
|
||||
if (`SELECT "$cleanup_stmt1" <> ""`)
|
||||
{
|
||||
--eval $cleanup_stmt1;
|
||||
}
|
||||
if (`SELECT "$cleanup_stmt2" <> ""`)
|
||||
{
|
||||
--eval $cleanup_stmt2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Then check that this statement is blocked by FTWRL
|
||||
# that is active in another connection.
|
||||
#
|
||||
connection $con_aux1;
|
||||
flush tables with read lock;
|
||||
|
||||
connection default;
|
||||
--send_eval $statement;
|
||||
|
||||
connection $con_aux1;
|
||||
|
||||
--enable_result_log
|
||||
--enable_query_log
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where (state = "Waiting for global read lock" or
|
||||
state = "Waiting for commit lock") and
|
||||
info = "$statement";
|
||||
--source include/wait_condition.inc
|
||||
--disable_result_log
|
||||
--disable_query_log
|
||||
|
||||
if ($success)
|
||||
{
|
||||
--echo Success: '$statement' is blocked by FTWRL active in another connection.
|
||||
}
|
||||
if (!$success)
|
||||
{
|
||||
--echo Error: '$statement' wasn't blocked by FTWRL active in another connection!
|
||||
}
|
||||
unlock tables;
|
||||
|
||||
connection default;
|
||||
--reap
|
||||
|
||||
if (`SELECT "$cleanup_stmt1" <> ""`)
|
||||
{
|
||||
--eval $cleanup_stmt1;
|
||||
}
|
||||
if (`SELECT "$cleanup_stmt2" <> ""`)
|
||||
{
|
||||
--eval $cleanup_stmt2;
|
||||
}
|
||||
|
||||
if (`SELECT "$skip_3rd_check" = ""`)
|
||||
{
|
||||
#
|
||||
# Finally, let us check that FTWRL will not succeed if this
|
||||
# statement is active but has already closed its tables.
|
||||
#
|
||||
connection default;
|
||||
--eval set debug_sync='execute_command_after_close_tables SIGNAL parked WAIT_FOR go';
|
||||
--send_eval $statement;
|
||||
|
||||
connection $con_aux1;
|
||||
set debug_sync="now WAIT_FOR parked";
|
||||
--send flush tables with read lock
|
||||
|
||||
connection $con_aux2;
|
||||
--enable_result_log
|
||||
--enable_query_log
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where (state = "Waiting for global read lock" or
|
||||
state = "Waiting for commit lock") and
|
||||
info = "flush tables with read lock";
|
||||
--source include/wait_condition.inc
|
||||
--disable_result_log
|
||||
--disable_query_log
|
||||
|
||||
if ($success)
|
||||
{
|
||||
--echo Success: FTWRL is blocked when '$statement' is active in another connection.
|
||||
}
|
||||
if (!$success)
|
||||
{
|
||||
--echo Error: FTWRL isn't blocked when '$statement' is active in another connection!
|
||||
}
|
||||
set debug_sync="now SIGNAL go";
|
||||
connection default;
|
||||
--reap
|
||||
connection $con_aux1;
|
||||
--reap
|
||||
unlock tables;
|
||||
connection default;
|
||||
|
||||
set debug_sync= "RESET";
|
||||
|
||||
if (`SELECT "$cleanup_stmt1" <> ""`)
|
||||
{
|
||||
--eval $cleanup_stmt1;
|
||||
}
|
||||
if (`SELECT "$cleanup_stmt2" <> ""`)
|
||||
{
|
||||
--eval $cleanup_stmt2;
|
||||
}
|
||||
}
|
||||
|
||||
--enable_result_log
|
||||
--enable_query_log
|
@ -1545,8 +1545,6 @@ lock table not_exists_write read;
|
||||
--echo # We still have the read lock.
|
||||
--error ER_CANT_UPDATE_WITH_READLOCK
|
||||
drop table t1;
|
||||
handler t1 read next;
|
||||
handler t1 close;
|
||||
handler t1 open;
|
||||
select a from t2;
|
||||
handler t1 read next;
|
||||
|
@ -101,7 +101,7 @@ if (`SELECT '$wait_for_all' = '1'`)
|
||||
|
||||
if (!$found)
|
||||
{
|
||||
echo # Timeout in include/wait_show_condition.inc for $wait_condition;
|
||||
echo # Timeout in include/wait_show_condition.inc for $condition;
|
||||
echo # show_statement : $show_statement;
|
||||
echo # field : $field;
|
||||
echo # condition : $condition;
|
||||
|
@ -418,6 +418,18 @@ COMMIT;
|
||||
UNLOCK TABLES;
|
||||
# Connection con1
|
||||
# Reaping: INSERT DELAYED INTO t1 VALUES (5)
|
||||
# Connection default
|
||||
# Test 5: LOCK TABLES + INSERT DELAYED in one connection.
|
||||
# This test has triggered some asserts in metadata locking
|
||||
# subsystem at some point in time..
|
||||
LOCK TABLE t1 WRITE;
|
||||
INSERT DELAYED INTO t2 VALUES (7);
|
||||
UNLOCK TABLES;
|
||||
SET AUTOCOMMIT= 0;
|
||||
LOCK TABLE t1 WRITE;
|
||||
INSERT DELAYED INTO t2 VALUES (8);
|
||||
UNLOCK TABLES;
|
||||
SET AUTOCOMMIT= 1;
|
||||
# Connection con2
|
||||
# Connection con1
|
||||
# Connection default
|
||||
|
@ -133,15 +133,15 @@ select event_name from information_schema.events;
|
||||
event_name
|
||||
e1
|
||||
create event e2 on schedule every 10 hour do select 1;
|
||||
ERROR HY000: Table 'event' was not locked with LOCK TABLES
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
alter event e2 disable;
|
||||
ERROR HY000: Table 'event' was not locked with LOCK TABLES
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
alter event e2 rename to e3;
|
||||
ERROR HY000: Table 'event' was not locked with LOCK TABLES
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
drop event e2;
|
||||
ERROR HY000: Table 'event' was not locked with LOCK TABLES
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
drop event e1;
|
||||
ERROR HY000: Table 'event' was not locked with LOCK TABLES
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
unlock tables;
|
||||
lock table t1 write;
|
||||
show create event e1;
|
||||
@ -151,15 +151,15 @@ select event_name from information_schema.events;
|
||||
event_name
|
||||
e1
|
||||
create event e2 on schedule every 10 hour do select 1;
|
||||
ERROR HY000: Table 'event' was not locked with LOCK TABLES
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
alter event e2 disable;
|
||||
ERROR HY000: Table 'event' was not locked with LOCK TABLES
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
alter event e2 rename to e3;
|
||||
ERROR HY000: Table 'event' was not locked with LOCK TABLES
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
drop event e2;
|
||||
ERROR HY000: Table 'event' was not locked with LOCK TABLES
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
drop event e1;
|
||||
ERROR HY000: Table 'event' was not locked with LOCK TABLES
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
unlock tables;
|
||||
lock table t1 read, mysql.event read;
|
||||
show create event e1;
|
||||
@ -169,15 +169,15 @@ select event_name from information_schema.events;
|
||||
event_name
|
||||
e1
|
||||
create event e2 on schedule every 10 hour do select 1;
|
||||
ERROR HY000: Table 'event' was locked with a READ lock and can't be updated
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
alter event e2 disable;
|
||||
ERROR HY000: Table 'event' was locked with a READ lock and can't be updated
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
alter event e2 rename to e3;
|
||||
ERROR HY000: Table 'event' was locked with a READ lock and can't be updated
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
drop event e2;
|
||||
ERROR HY000: Table 'event' was locked with a READ lock and can't be updated
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
drop event e1;
|
||||
ERROR HY000: Table 'event' was locked with a READ lock and can't be updated
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
unlock tables;
|
||||
lock table t1 write, mysql.event read;
|
||||
show create event e1;
|
||||
@ -187,15 +187,15 @@ select event_name from information_schema.events;
|
||||
event_name
|
||||
e1
|
||||
create event e2 on schedule every 10 hour do select 1;
|
||||
ERROR HY000: Table 'event' was locked with a READ lock and can't be updated
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
alter event e2 disable;
|
||||
ERROR HY000: Table 'event' was locked with a READ lock and can't be updated
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
alter event e2 rename to e3;
|
||||
ERROR HY000: Table 'event' was locked with a READ lock and can't be updated
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
drop event e2;
|
||||
ERROR HY000: Table 'event' was locked with a READ lock and can't be updated
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
drop event e1;
|
||||
ERROR HY000: Table 'event' was locked with a READ lock and can't be updated
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
unlock tables;
|
||||
lock table t1 read, mysql.event write;
|
||||
ERROR HY000: You can't combine write-locking of system tables with other tables or lock types
|
||||
@ -209,11 +209,17 @@ select event_name from information_schema.events;
|
||||
event_name
|
||||
e1
|
||||
create event e2 on schedule every 10 hour do select 1;
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
alter event e2 disable;
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
alter event e2 rename to e3;
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
drop event e3;
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
drop event e1;
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
unlock tables;
|
||||
drop event e1;
|
||||
Make sure we have left no events
|
||||
select event_name from information_schema.events;
|
||||
event_name
|
||||
|
@ -423,3 +423,31 @@ i
|
||||
4
|
||||
unlock tables;
|
||||
drop tables tm, t1, t2;
|
||||
#
|
||||
# Test for bug #57006 "Deadlock between HANDLER and
|
||||
# FLUSH TABLES WITH READ LOCK".
|
||||
#
|
||||
drop table if exists t1, t2;
|
||||
create table t1 (i int);
|
||||
create table t2 (i int);
|
||||
handler t1 open;
|
||||
# Switching to connection 'con1'.
|
||||
# Sending:
|
||||
flush tables with read lock;
|
||||
# Switching to connection 'con2'.
|
||||
# Wait until FTWRL starts waiting for 't1' to be closed.
|
||||
# Switching to connection 'default'.
|
||||
# The below statement should not cause deadlock.
|
||||
# Sending:
|
||||
insert into t2 values (1);
|
||||
# Switching to connection 'con2'.
|
||||
# Wait until INSERT starts to wait for FTWRL to go away.
|
||||
# Switching to connection 'con1'.
|
||||
# FTWRL should be able to continue now.
|
||||
# Reap FTWRL.
|
||||
unlock tables;
|
||||
# Switching to connection 'default'.
|
||||
# Reap INSERT.
|
||||
handler t1 close;
|
||||
# Cleanup.
|
||||
drop tables t1, t2;
|
||||
|
1683
mysql-test/r/flush_read_lock.result
Normal file
1683
mysql-test/r/flush_read_lock.result
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,38 @@
|
||||
SET @old_concurrent_insert= @@global.concurrent_insert;
|
||||
SET @@global.concurrent_insert= 0;
|
||||
DROP TABLE IF EXISTS t1;
|
||||
CREATE TABLE t1 (kill_id INT);
|
||||
SET DEBUG_SYNC= 'RESET';
|
||||
CREATE TABLE t1 (kill_id INT) engine = InnoDB;
|
||||
INSERT INTO t1 VALUES(connection_id());
|
||||
# Switching to connection 'default'.
|
||||
# Start transaction.
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES(connection_id());
|
||||
# Ensure that COMMIT will pause once it acquires protection
|
||||
# against its global read lock.
|
||||
SET DEBUG_SYNC='ha_commit_trans_after_acquire_commit_lock SIGNAL acquired WAIT_FOR go';
|
||||
# Sending:
|
||||
COMMIT;
|
||||
# Switching to 'con1'.
|
||||
# Wait till COMMIT acquires protection against global read
|
||||
# lock and pauses.
|
||||
SET DEBUG_SYNC='now WAIT_FOR acquired';
|
||||
# Sending:
|
||||
FLUSH TABLES WITH READ LOCK;
|
||||
SELECT ((@id := kill_id) - kill_id) FROM t1;
|
||||
# Switching to 'con2'.
|
||||
SELECT ((@id := kill_id) - kill_id) FROM t1 LIMIT 1;
|
||||
((@id := kill_id) - kill_id)
|
||||
0
|
||||
# Wait till FLUSH TABLES WITH READ LOCK blocks due
|
||||
# to active COMMIT
|
||||
# Kill connection 'con1'.
|
||||
KILL CONNECTION @id;
|
||||
# Switching to 'con1'.
|
||||
# Try to reap FLUSH TABLES WITH READ LOCK,
|
||||
# it fail due to killed statement and connection.
|
||||
Got one of the listed errors
|
||||
# Switching to 'con2'.
|
||||
# Resume COMMIT.
|
||||
SET DEBUG_SYNC='now SIGNAL go';
|
||||
# Switching to 'default'.
|
||||
# Reaping COMMIT.
|
||||
DROP TABLE t1;
|
||||
SET @@global.concurrent_insert= @old_concurrent_insert;
|
||||
SET DEBUG_SYNC= 'RESET';
|
||||
|
@ -1485,10 +1485,6 @@ ERROR 42S02: Table 'test.not_exists_write' doesn't exist
|
||||
# We still have the read lock.
|
||||
drop table t1;
|
||||
ERROR HY000: Can't execute the query because you have a conflicting read lock
|
||||
handler t1 read next;
|
||||
a b
|
||||
1 1
|
||||
handler t1 close;
|
||||
handler t1 open;
|
||||
select a from t2;
|
||||
a
|
||||
|
@ -1481,10 +1481,6 @@ ERROR 42S02: Table 'test.not_exists_write' doesn't exist
|
||||
# We still have the read lock.
|
||||
drop table t1;
|
||||
ERROR HY000: Can't execute the query because you have a conflicting read lock
|
||||
handler t1 read next;
|
||||
a b
|
||||
1 1
|
||||
handler t1 close;
|
||||
handler t1 open;
|
||||
select a from t2;
|
||||
a
|
||||
|
@ -2471,7 +2471,7 @@ CREATE PROCEDURE p1() SELECT 1;
|
||||
SET DEBUG_SYNC= 'now WAIT_FOR table_opened';
|
||||
# Check that FLUSH must wait to get the GRL
|
||||
# and let CREATE PROCEDURE continue
|
||||
SET DEBUG_SYNC= 'wait_lock_global_read_lock SIGNAL grlwait';
|
||||
SET DEBUG_SYNC= 'mdl_acquire_lock_wait SIGNAL grlwait';
|
||||
FLUSH TABLES WITH READ LOCK;
|
||||
# Connection 1
|
||||
# Connection 2
|
||||
@ -2486,10 +2486,17 @@ DROP PROCEDURE p1;
|
||||
SET DEBUG_SYNC= 'now WAIT_FOR table_opened';
|
||||
# Check that FLUSH must wait to get the GRL
|
||||
# and let DROP PROCEDURE continue
|
||||
SET DEBUG_SYNC= 'wait_lock_global_read_lock SIGNAL grlwait';
|
||||
SET DEBUG_SYNC= 'mdl_acquire_lock_wait SIGNAL grlwait';
|
||||
FLUSH TABLES WITH READ LOCK;
|
||||
# Connection 1
|
||||
# Once FLUSH TABLES WITH READ LOCK starts waiting
|
||||
# DROP PROCEDURE will be waked up and will drop
|
||||
# procedure. Global read lock will be granted after
|
||||
# this statement ends.
|
||||
#
|
||||
# Reaping DROP PROCEDURE.
|
||||
# Connection 2
|
||||
# Reaping FTWRL.
|
||||
UNLOCK TABLES;
|
||||
# Connection 1
|
||||
SET DEBUG_SYNC= 'RESET';
|
||||
|
@ -37,7 +37,6 @@ where name like 'Wait/Synch/Cond/sql/%'
|
||||
order by name limit 10;
|
||||
NAME ENABLED TIMED
|
||||
wait/synch/cond/sql/COND_flush_thread_cache YES YES
|
||||
wait/synch/cond/sql/COND_global_read_lock YES YES
|
||||
wait/synch/cond/sql/COND_manager YES YES
|
||||
wait/synch/cond/sql/COND_queue_state YES YES
|
||||
wait/synch/cond/sql/COND_rpl_status YES YES
|
||||
@ -46,6 +45,7 @@ wait/synch/cond/sql/COND_thread_cache YES YES
|
||||
wait/synch/cond/sql/COND_thread_count YES YES
|
||||
wait/synch/cond/sql/Delayed_insert::cond YES YES
|
||||
wait/synch/cond/sql/Delayed_insert::cond_client YES YES
|
||||
wait/synch/cond/sql/Event_scheduler::COND_state YES YES
|
||||
select * from performance_schema.SETUP_INSTRUMENTS
|
||||
where name='Wait';
|
||||
select * from performance_schema.SETUP_INSTRUMENTS
|
||||
|
@ -100,3 +100,4 @@ INNER JOIN performance_schema.THREADS p USING (THREAD_ID)
|
||||
WHERE p.PROCESSLIST_ID = 1
|
||||
GROUP BY h.EVENT_NAME
|
||||
HAVING TOTAL_WAIT > 0;
|
||||
UPDATE performance_schema.SETUP_INSTRUMENTS SET enabled = 'YES';
|
||||
|
@ -110,4 +110,5 @@ WHERE (EVENT_NAME = 'wait/synch/rwlock/sql/LOCK_grant'));
|
||||
SELECT IF((COALESCE(@after_count, 0) - COALESCE(@before_count, 0)) = 0, 'Success', 'Failure') test_fm2_rw_timed;
|
||||
test_fm2_rw_timed
|
||||
Success
|
||||
UPDATE performance_schema.SETUP_INSTRUMENTS SET enabled = 'YES';
|
||||
DROP TABLE t1;
|
||||
|
@ -1,4 +1,5 @@
|
||||
use performance_schema;
|
||||
update performance_schema.SETUP_INSTRUMENTS set enabled='YES';
|
||||
grant SELECT, UPDATE, LOCK TABLES on performance_schema.* to pfsuser@localhost;
|
||||
flush privileges;
|
||||
connect (con1, localhost, pfsuser, , test);
|
||||
@ -21,9 +22,9 @@ select event_name,
|
||||
left(source, locate(":", source)) as short_source,
|
||||
timer_end, timer_wait, operation
|
||||
from performance_schema.EVENTS_WAITS_CURRENT
|
||||
where event_name like "wait/synch/cond/sql/COND_global_read_lock";
|
||||
where event_name like "wait/synch/cond/sql/MDL_context::COND_wait_status";
|
||||
event_name short_source timer_end timer_wait operation
|
||||
wait/synch/cond/sql/COND_global_read_lock lock.cc: NULL NULL wait
|
||||
wait/synch/cond/sql/MDL_context::COND_wait_status mdl.cc: NULL NULL timed_wait
|
||||
unlock tables;
|
||||
update performance_schema.SETUP_INSTRUMENTS set enabled='NO';
|
||||
update performance_schema.SETUP_INSTRUMENTS set enabled='YES';
|
||||
|
@ -88,10 +88,6 @@ where name like "wait/synch/mutex/sql/LOCK_manager";
|
||||
count(name)
|
||||
1
|
||||
select count(name) from MUTEX_INSTANCES
|
||||
where name like "wait/synch/mutex/sql/LOCK_global_read_lock";
|
||||
count(name)
|
||||
1
|
||||
select count(name) from MUTEX_INSTANCES
|
||||
where name like "wait/synch/mutex/sql/LOCK_global_system_variables";
|
||||
count(name)
|
||||
1
|
||||
@ -120,10 +116,6 @@ where name like "wait/synch/mutex/sql/Query_cache::structure_guard_mutex";
|
||||
count(name)
|
||||
1
|
||||
select count(name) from MUTEX_INSTANCES
|
||||
where name like "wait/synch/mutex/sql/LOCK_event_metadata";
|
||||
count(name)
|
||||
1
|
||||
select count(name) from MUTEX_INSTANCES
|
||||
where name like "wait/synch/mutex/sql/LOCK_event_queue";
|
||||
count(name)
|
||||
1
|
||||
@ -184,10 +176,6 @@ where name like "wait/synch/cond/sql/COND_manager";
|
||||
count(name)
|
||||
1
|
||||
select count(name) from COND_INSTANCES
|
||||
where name like "wait/synch/cond/sql/COND_global_read_lock";
|
||||
count(name)
|
||||
1
|
||||
select count(name) from COND_INSTANCES
|
||||
where name like "wait/synch/cond/sql/COND_thread_cache";
|
||||
count(name)
|
||||
1
|
||||
|
@ -190,3 +190,6 @@ HAVING TOTAL_WAIT > 0;
|
||||
## HAVING BYTES > 0
|
||||
## ORDER BY i.user, h.operation;
|
||||
## --enable_result_log
|
||||
|
||||
# Clean-up.
|
||||
UPDATE performance_schema.SETUP_INSTRUMENTS SET enabled = 'YES';
|
||||
|
@ -128,4 +128,6 @@ SET @after_count = (SELECT SUM(TIMER_WAIT)
|
||||
|
||||
SELECT IF((COALESCE(@after_count, 0) - COALESCE(@before_count, 0)) = 0, 'Success', 'Failure') test_fm2_rw_timed;
|
||||
|
||||
# Clean-up.
|
||||
UPDATE performance_schema.SETUP_INSTRUMENTS SET enabled = 'YES';
|
||||
DROP TABLE t1;
|
||||
|
@ -22,6 +22,10 @@
|
||||
|
||||
use performance_schema;
|
||||
|
||||
# Make test robust against errors in other tests.
|
||||
# Ensure that instrumentation is turned on when we create new connection.
|
||||
update performance_schema.SETUP_INSTRUMENTS set enabled='YES';
|
||||
|
||||
grant SELECT, UPDATE, LOCK TABLES on performance_schema.* to pfsuser@localhost;
|
||||
flush privileges;
|
||||
|
||||
@ -60,7 +64,7 @@ lock tables performance_schema.SETUP_INSTRUMENTS write;
|
||||
--echo connection default;
|
||||
connection default;
|
||||
|
||||
let $wait_condition= select 1 from performance_schema.EVENTS_WAITS_CURRENT where event_name like "wait/synch/cond/sql/COND_global_read_lock";
|
||||
let $wait_condition= select 1 from performance_schema.EVENTS_WAITS_CURRENT where event_name like "wait/synch/cond/sql/MDL_context::COND_wait_status";
|
||||
|
||||
--source include/wait_condition.inc
|
||||
|
||||
@ -69,7 +73,7 @@ select event_name,
|
||||
left(source, locate(":", source)) as short_source,
|
||||
timer_end, timer_wait, operation
|
||||
from performance_schema.EVENTS_WAITS_CURRENT
|
||||
where event_name like "wait/synch/cond/sql/COND_global_read_lock";
|
||||
where event_name like "wait/synch/cond/sql/MDL_context::COND_wait_status";
|
||||
|
||||
unlock tables;
|
||||
|
||||
|
@ -100,9 +100,6 @@ select count(name) from MUTEX_INSTANCES
|
||||
select count(name) from MUTEX_INSTANCES
|
||||
where name like "wait/synch/mutex/sql/LOCK_manager";
|
||||
|
||||
select count(name) from MUTEX_INSTANCES
|
||||
where name like "wait/synch/mutex/sql/LOCK_global_read_lock";
|
||||
|
||||
select count(name) from MUTEX_INSTANCES
|
||||
where name like "wait/synch/mutex/sql/LOCK_global_system_variables";
|
||||
|
||||
@ -132,9 +129,6 @@ select count(name) from MUTEX_INSTANCES
|
||||
# select count(name) from MUTEX_INSTANCES
|
||||
# where name like "wait/synch/mutex/sql/Event_scheduler::LOCK_scheduler_state";
|
||||
|
||||
select count(name) from MUTEX_INSTANCES
|
||||
where name like "wait/synch/mutex/sql/LOCK_event_metadata";
|
||||
|
||||
select count(name) from MUTEX_INSTANCES
|
||||
where name like "wait/synch/mutex/sql/LOCK_event_queue";
|
||||
|
||||
@ -188,9 +182,6 @@ select count(name) from COND_INSTANCES
|
||||
select count(name) from COND_INSTANCES
|
||||
where name like "wait/synch/cond/sql/COND_manager";
|
||||
|
||||
select count(name) from COND_INSTANCES
|
||||
where name like "wait/synch/cond/sql/COND_global_read_lock";
|
||||
|
||||
select count(name) from COND_INSTANCES
|
||||
where name like "wait/synch/cond/sql/COND_thread_cache";
|
||||
|
||||
|
@ -123,16 +123,16 @@ DROP PROCEDURE p2;
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
INSERT INTO t2 VALUES ("DROP PROCEDURE p2 with table locked");
|
||||
CREATE EVENT e1 ON SCHEDULE EVERY 10 HOUR DO SELECT 1;
|
||||
ERROR HY000: Table 'event' was not locked with LOCK TABLES
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
INSERT INTO t2 VALUES ("CREATE EVENT e1 with table locked");
|
||||
UNLOCK TABLE;
|
||||
CREATE EVENT e2 ON SCHEDULE EVERY 10 HOUR DO SELECT 1;
|
||||
LOCK TABLE t1 WRITE;
|
||||
ALTER EVENT e2 ON SCHEDULE EVERY 20 HOUR DO SELECT 1;
|
||||
ERROR HY000: Table 'event' was not locked with LOCK TABLES
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
INSERT INTO t2 VALUES ("ALTER EVENT e2 with table locked");
|
||||
DROP EVENT e2;
|
||||
ERROR HY000: Table 'event' was not locked with LOCK TABLES
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
INSERT INTO t2 VALUES ("DROP EVENT e2 with table locked");
|
||||
CREATE DATABASE mysqltest1;
|
||||
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
|
||||
|
@ -539,6 +539,21 @@ connection con1;
|
||||
--echo # Reaping: INSERT DELAYED INTO t1 VALUES (5)
|
||||
--reap
|
||||
|
||||
--echo # Connection default
|
||||
connection default;
|
||||
|
||||
--echo # Test 5: LOCK TABLES + INSERT DELAYED in one connection.
|
||||
--echo # This test has triggered some asserts in metadata locking
|
||||
--echo # subsystem at some point in time..
|
||||
LOCK TABLE t1 WRITE;
|
||||
INSERT DELAYED INTO t2 VALUES (7);
|
||||
UNLOCK TABLES;
|
||||
SET AUTOCOMMIT= 0;
|
||||
LOCK TABLE t1 WRITE;
|
||||
INSERT DELAYED INTO t2 VALUES (8);
|
||||
UNLOCK TABLES;
|
||||
SET AUTOCOMMIT= 1;
|
||||
|
||||
--echo # Connection con2
|
||||
connection con2;
|
||||
disconnect con2;
|
||||
|
@ -212,15 +212,15 @@ lock table t1 read;
|
||||
--replace_regex /STARTS '[^']+'/STARTS '#'/
|
||||
show create event e1;
|
||||
select event_name from information_schema.events;
|
||||
--error ER_TABLE_NOT_LOCKED
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
create event e2 on schedule every 10 hour do select 1;
|
||||
--error ER_TABLE_NOT_LOCKED
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
alter event e2 disable;
|
||||
--error ER_TABLE_NOT_LOCKED
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
alter event e2 rename to e3;
|
||||
--error ER_TABLE_NOT_LOCKED
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
drop event e2;
|
||||
--error ER_TABLE_NOT_LOCKED
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
drop event e1;
|
||||
unlock tables;
|
||||
#
|
||||
@ -229,15 +229,15 @@ lock table t1 write;
|
||||
--replace_regex /STARTS '[^']+'/STARTS '#'/
|
||||
show create event e1;
|
||||
select event_name from information_schema.events;
|
||||
--error ER_TABLE_NOT_LOCKED
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
create event e2 on schedule every 10 hour do select 1;
|
||||
--error ER_TABLE_NOT_LOCKED
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
alter event e2 disable;
|
||||
--error ER_TABLE_NOT_LOCKED
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
alter event e2 rename to e3;
|
||||
--error ER_TABLE_NOT_LOCKED
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
drop event e2;
|
||||
--error ER_TABLE_NOT_LOCKED
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
drop event e1;
|
||||
unlock tables;
|
||||
#
|
||||
@ -246,15 +246,15 @@ lock table t1 read, mysql.event read;
|
||||
--replace_regex /STARTS '[^']+'/STARTS '#'/
|
||||
show create event e1;
|
||||
select event_name from information_schema.events;
|
||||
--error ER_TABLE_NOT_LOCKED_FOR_WRITE
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
create event e2 on schedule every 10 hour do select 1;
|
||||
--error ER_TABLE_NOT_LOCKED_FOR_WRITE
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
alter event e2 disable;
|
||||
--error ER_TABLE_NOT_LOCKED_FOR_WRITE
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
alter event e2 rename to e3;
|
||||
--error ER_TABLE_NOT_LOCKED_FOR_WRITE
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
drop event e2;
|
||||
--error ER_TABLE_NOT_LOCKED_FOR_WRITE
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
drop event e1;
|
||||
unlock tables;
|
||||
#
|
||||
@ -263,15 +263,15 @@ lock table t1 write, mysql.event read;
|
||||
--replace_regex /STARTS '[^']+'/STARTS '#'/
|
||||
show create event e1;
|
||||
select event_name from information_schema.events;
|
||||
--error ER_TABLE_NOT_LOCKED_FOR_WRITE
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
create event e2 on schedule every 10 hour do select 1;
|
||||
--error ER_TABLE_NOT_LOCKED_FOR_WRITE
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
alter event e2 disable;
|
||||
--error ER_TABLE_NOT_LOCKED_FOR_WRITE
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
alter event e2 rename to e3;
|
||||
--error ER_TABLE_NOT_LOCKED_FOR_WRITE
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
drop event e2;
|
||||
--error ER_TABLE_NOT_LOCKED_FOR_WRITE
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
drop event e1;
|
||||
unlock tables;
|
||||
#
|
||||
@ -285,12 +285,18 @@ lock table mysql.event write;
|
||||
--replace_regex /STARTS '[^']+'/STARTS '#'/
|
||||
show create event e1;
|
||||
select event_name from information_schema.events;
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
create event e2 on schedule every 10 hour do select 1;
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
alter event e2 disable;
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
alter event e2 rename to e3;
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
drop event e3;
|
||||
--error ER_LOCK_OR_ACTIVE_TRANSACTION
|
||||
drop event e1;
|
||||
unlock tables;
|
||||
drop event e1;
|
||||
--echo Make sure we have left no events
|
||||
select event_name from information_schema.events;
|
||||
--echo
|
||||
|
@ -577,3 +577,70 @@ select * from t1;
|
||||
select * from t2;
|
||||
unlock tables;
|
||||
drop tables tm, t1, t2;
|
||||
|
||||
|
||||
--echo #
|
||||
--echo # Test for bug #57006 "Deadlock between HANDLER and
|
||||
--echo # FLUSH TABLES WITH READ LOCK".
|
||||
--echo #
|
||||
--disable_warnings
|
||||
drop table if exists t1, t2;
|
||||
--enable_warnings
|
||||
connect (con1,localhost,root,,);
|
||||
connect (con2,localhost,root,,);
|
||||
connection default;
|
||||
create table t1 (i int);
|
||||
create table t2 (i int);
|
||||
handler t1 open;
|
||||
|
||||
--echo # Switching to connection 'con1'.
|
||||
connection con1;
|
||||
--echo # Sending:
|
||||
--send flush tables with read lock
|
||||
|
||||
--echo # Switching to connection 'con2'.
|
||||
connection con2;
|
||||
--echo # Wait until FTWRL starts waiting for 't1' to be closed.
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where state = "Waiting for table flush"
|
||||
and info = "flush tables with read lock";
|
||||
--source include/wait_condition.inc
|
||||
|
||||
--echo # Switching to connection 'default'.
|
||||
connection default;
|
||||
--echo # The below statement should not cause deadlock.
|
||||
--echo # Sending:
|
||||
--send insert into t2 values (1)
|
||||
|
||||
--echo # Switching to connection 'con2'.
|
||||
connection con2;
|
||||
--echo # Wait until INSERT starts to wait for FTWRL to go away.
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where state = "Waiting for global read lock"
|
||||
and info = "insert into t2 values (1)";
|
||||
--source include/wait_condition.inc
|
||||
|
||||
--echo # Switching to connection 'con1'.
|
||||
connection con1;
|
||||
--echo # FTWRL should be able to continue now.
|
||||
--echo # Reap FTWRL.
|
||||
--reap
|
||||
unlock tables;
|
||||
|
||||
--echo # Switching to connection 'default'.
|
||||
connection default;
|
||||
--echo # Reap INSERT.
|
||||
--reap
|
||||
handler t1 close;
|
||||
|
||||
--echo # Cleanup.
|
||||
connection con1;
|
||||
disconnect con1;
|
||||
--source include/wait_until_disconnected.inc
|
||||
connection con2;
|
||||
disconnect con2;
|
||||
--source include/wait_until_disconnected.inc
|
||||
connection default;
|
||||
drop tables t1, t2;
|
||||
|
@ -39,7 +39,7 @@ connection con2;
|
||||
--echo # Wait until COMMIT gets blocked.
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where state = "Waiting for release of readlock" and info = "COMMIT";
|
||||
where state = "Waiting for commit lock" and info = "COMMIT";
|
||||
--source include/wait_condition.inc
|
||||
--echo # Verify that 'con1' was blocked and data did not move.
|
||||
SELECT * FROM t1;
|
||||
|
@ -53,7 +53,7 @@ begin;
|
||||
connection con1;
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where state = "Waiting for release of readlock" and
|
||||
where state = "Waiting for global read lock" and
|
||||
info = "insert into t1 values (1)";
|
||||
--source include/wait_condition.inc
|
||||
unlock tables;
|
||||
|
2187
mysql-test/t/flush_read_lock.test
Normal file
2187
mysql-test/t/flush_read_lock.test
Normal file
File diff suppressed because it is too large
Load Diff
@ -1 +0,0 @@
|
||||
--loose-debug=+d,make_global_read_lock_block_commit_loop
|
@ -2,24 +2,19 @@
|
||||
# for running commits to finish (in the past it could not)
|
||||
# This will not be a meaningful test on non-debug servers so will be
|
||||
# skipped.
|
||||
# If running mysql-test-run --debug, the --debug added by
|
||||
# mysql-test-run to the mysqld command line will override the one of
|
||||
# -master.opt. But this test is designed to still pass then (though it
|
||||
# won't test anything interesting).
|
||||
|
||||
# This also won't work with the embedded server test
|
||||
--source include/not_embedded.inc
|
||||
|
||||
--source include/have_debug.inc
|
||||
|
||||
# This test needs transactional engine as otherwise COMMIT
|
||||
# won't block FLUSH TABLES WITH GLOBAL READ LOCK.
|
||||
--source include/have_innodb.inc
|
||||
|
||||
# Save the initial number of concurrent sessions
|
||||
--source include/count_sessions.inc
|
||||
|
||||
# Disable concurrent inserts to avoid test failures when reading the
|
||||
# connection id which was inserted into a table by another thread.
|
||||
SET @old_concurrent_insert= @@global.concurrent_insert;
|
||||
SET @@global.concurrent_insert= 0;
|
||||
|
||||
connect (con1,localhost,root,,);
|
||||
connect (con2,localhost,root,,);
|
||||
connection con1;
|
||||
@ -27,47 +22,64 @@ connection con1;
|
||||
--disable_warnings
|
||||
DROP TABLE IF EXISTS t1;
|
||||
--enable_warnings
|
||||
CREATE TABLE t1 (kill_id INT);
|
||||
SET DEBUG_SYNC= 'RESET';
|
||||
CREATE TABLE t1 (kill_id INT) engine = InnoDB;
|
||||
INSERT INTO t1 VALUES(connection_id());
|
||||
|
||||
# Thanks to the parameter we passed to --debug, this FLUSH will
|
||||
# block on a debug build running with our --debug=make_global... It
|
||||
# will block until killed. In other cases (non-debug build or other
|
||||
# --debug) it will succeed immediately
|
||||
--echo # Switching to connection 'default'.
|
||||
connection default;
|
||||
--echo # Start transaction.
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES(connection_id());
|
||||
--echo # Ensure that COMMIT will pause once it acquires protection
|
||||
--echo # against its global read lock.
|
||||
SET DEBUG_SYNC='ha_commit_trans_after_acquire_commit_lock SIGNAL acquired WAIT_FOR go';
|
||||
|
||||
--echo # Sending:
|
||||
--send COMMIT
|
||||
|
||||
--echo # Switching to 'con1'.
|
||||
connection con1;
|
||||
--echo # Wait till COMMIT acquires protection against global read
|
||||
--echo # lock and pauses.
|
||||
SET DEBUG_SYNC='now WAIT_FOR acquired';
|
||||
--echo # Sending:
|
||||
send FLUSH TABLES WITH READ LOCK;
|
||||
|
||||
# kill con1
|
||||
--echo # Switching to 'con2'.
|
||||
connection con2;
|
||||
SELECT ((@id := kill_id) - kill_id) FROM t1;
|
||||
SELECT ((@id := kill_id) - kill_id) FROM t1 LIMIT 1;
|
||||
|
||||
# Wait for the debug sync point, test won't run on non-debug
|
||||
# builds anyway.
|
||||
--echo # Wait till FLUSH TABLES WITH READ LOCK blocks due
|
||||
--echo # to active COMMIT
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where state = "Waiting for all running commits to finish"
|
||||
where state = "Waiting for commit lock"
|
||||
and info = "flush tables with read lock";
|
||||
--source include/wait_condition.inc
|
||||
|
||||
--echo # Kill connection 'con1'.
|
||||
KILL CONNECTION @id;
|
||||
|
||||
--echo # Switching to 'con1'.
|
||||
connection con1;
|
||||
# On debug builds it will be error 1053 (killed); on non-debug, or
|
||||
# debug build running without our --debug=make_global..., will be
|
||||
# error 0 (no error). The only important thing to test is that on
|
||||
# debug builds with our --debug=make_global... we don't hang forever.
|
||||
--error 0,1317,2013
|
||||
--echo # Try to reap FLUSH TABLES WITH READ LOCK,
|
||||
--echo # it fail due to killed statement and connection.
|
||||
--error 1317,2013
|
||||
reap;
|
||||
|
||||
--echo # Switching to 'con2'.
|
||||
connection con2;
|
||||
DROP TABLE t1;
|
||||
--echo # Resume COMMIT.
|
||||
SET DEBUG_SYNC='now SIGNAL go';
|
||||
|
||||
--echo # Switching to 'default'.
|
||||
connection default;
|
||||
--echo # Reaping COMMIT.
|
||||
--reap
|
||||
disconnect con2;
|
||||
|
||||
# Restore global concurrent_insert value
|
||||
SET @@global.concurrent_insert= @old_concurrent_insert;
|
||||
DROP TABLE t1;
|
||||
SET DEBUG_SYNC= 'RESET';
|
||||
|
||||
# Wait till all disconnects are completed
|
||||
--source include/wait_until_count_sessions.inc
|
||||
|
@ -228,7 +228,7 @@ connection writer;
|
||||
# Sleep a bit till the flush of connection locker is in work and hangs
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where state = "Waiting for global metadata lock" and
|
||||
where state = "Waiting for global read lock" and
|
||||
info = "FLUSH TABLES WITH READ LOCK";
|
||||
--source include/wait_condition.inc
|
||||
# This must not block.
|
||||
@ -260,7 +260,7 @@ connection writer;
|
||||
# Sleep a bit till the flush of connection locker is in work and hangs
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where state = "Waiting for global metadata lock" and
|
||||
where state = "Waiting for global read lock" and
|
||||
info = "FLUSH TABLES WITH READ LOCK";
|
||||
--source include/wait_condition.inc
|
||||
--error ER_TABLE_NOT_LOCKED
|
||||
@ -296,10 +296,11 @@ DROP DATABASE mysqltest_1;
|
||||
# With bug in place: try to acquire LOCK_mysql_create_table...
|
||||
# When fixed: Reject dropping db because of the read lock.
|
||||
connection con1;
|
||||
# Wait a bit so that the session con2 is in state "Waiting for release of readlock"
|
||||
# Wait a bit so that the session con2 is in state
|
||||
# "Waiting for global read lock"
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where state = "Waiting for release of readlock"
|
||||
where state = "Waiting for global read lock"
|
||||
and info = "DROP DATABASE mysqltest_1";
|
||||
--source include/wait_condition.inc
|
||||
--error ER_CANT_UPDATE_WITH_READLOCK
|
||||
@ -376,7 +377,7 @@ connection con5;
|
||||
--echo # con5
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where state = "Waiting for global metadata lock" and
|
||||
where state = "Waiting for global read lock" and
|
||||
info = "flush tables with read lock";
|
||||
--source include/wait_condition.inc
|
||||
--echo # global read lock is taken
|
||||
@ -384,10 +385,11 @@ connection con3;
|
||||
--echo # con3
|
||||
send select * from t2 for update;
|
||||
connection con5;
|
||||
let $show_statement= SHOW PROCESSLIST;
|
||||
let $field= State;
|
||||
let $condition= = 'Waiting for release of readlock';
|
||||
--source include/wait_show_condition.inc
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where state = "Waiting for global read lock" and
|
||||
info = "select * from t2 for update";
|
||||
--source include/wait_condition.inc
|
||||
--echo # waiting for release of read lock
|
||||
connection con4;
|
||||
--echo # con4
|
||||
@ -433,10 +435,11 @@ connection con1;
|
||||
send update t2 set a = 1;
|
||||
connection default;
|
||||
--echo # default
|
||||
let $show_statement= SHOW PROCESSLIST;
|
||||
let $field= State;
|
||||
let $condition= = 'Waiting for release of readlock';
|
||||
--source include/wait_show_condition.inc
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where state = "Waiting for global read lock" and
|
||||
info = "update t2 set a = 1";
|
||||
--source include/wait_condition.inc
|
||||
--echo # statement is waiting for release of read lock
|
||||
connection con2;
|
||||
--echo # con2
|
||||
@ -460,10 +463,11 @@ connection con1;
|
||||
send lock tables t2 write;
|
||||
connection default;
|
||||
--echo # default
|
||||
let $show_statement= SHOW PROCESSLIST;
|
||||
let $field= State;
|
||||
let $condition= = 'Waiting for release of readlock';
|
||||
--source include/wait_show_condition.inc
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where state = "Waiting for global read lock" and
|
||||
info = "lock tables t2 write";
|
||||
--source include/wait_condition.inc
|
||||
--echo # statement is waiting for release of read lock
|
||||
connection con2;
|
||||
--echo # con2
|
||||
@ -571,7 +575,8 @@ connection default;
|
||||
--echo connection: default
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where state = "Waiting for global metadata lock";
|
||||
where state = "Waiting for global read lock" and
|
||||
info = "flush tables with read lock";
|
||||
--source include/wait_condition.inc
|
||||
alter table t1 add column j int;
|
||||
connect (insert,localhost,root,,test,,);
|
||||
@ -579,14 +584,16 @@ connection insert;
|
||||
--echo connection: insert
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where state = "Waiting for global metadata lock";
|
||||
where state = "Waiting for global read lock" and
|
||||
info = "flush tables with read lock";
|
||||
--source include/wait_condition.inc
|
||||
--send insert into t1 values (1,2);
|
||||
--echo connection: default
|
||||
connection default;
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where state = "Waiting for release of readlock";
|
||||
where state = "Waiting for global read lock" and
|
||||
info = "insert into t1 values (1,2)";
|
||||
--source include/wait_condition.inc
|
||||
unlock tables;
|
||||
connection flush;
|
||||
@ -594,7 +601,8 @@ connection flush;
|
||||
--reap
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where state = "Waiting for release of readlock";
|
||||
where state = "Waiting for global read lock" and
|
||||
info = "insert into t1 values (1,2)";
|
||||
--source include/wait_condition.inc
|
||||
select * from t1;
|
||||
unlock tables;
|
||||
@ -629,12 +637,12 @@ connection default;
|
||||
--echo connection: default
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where state = "Waiting for global metadata lock";
|
||||
where state = "Waiting for global read lock";
|
||||
--source include/wait_condition.inc
|
||||
flush tables;
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where state = "Waiting for global metadata lock";
|
||||
where state = "Waiting for global read lock";
|
||||
--source include/wait_condition.inc
|
||||
unlock tables;
|
||||
connection flush;
|
||||
@ -698,12 +706,12 @@ connection default;
|
||||
--echo connection: default
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where state = "Waiting for global metadata lock";
|
||||
where state = "Waiting for global read lock";
|
||||
--source include/wait_condition.inc
|
||||
flush tables;
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where state = "Waiting for global metadata lock";
|
||||
where state = "Waiting for global read lock";
|
||||
--source include/wait_condition.inc
|
||||
drop table t1;
|
||||
connection flush;
|
||||
|
@ -3661,7 +3661,7 @@ connection con2;
|
||||
SET DEBUG_SYNC= 'now WAIT_FOR table_opened';
|
||||
--echo # Check that FLUSH must wait to get the GRL
|
||||
--echo # and let CREATE PROCEDURE continue
|
||||
SET DEBUG_SYNC= 'wait_lock_global_read_lock SIGNAL grlwait';
|
||||
SET DEBUG_SYNC= 'mdl_acquire_lock_wait SIGNAL grlwait';
|
||||
--send FLUSH TABLES WITH READ LOCK
|
||||
|
||||
--echo # Connection 1
|
||||
@ -3689,15 +3689,22 @@ connection con2;
|
||||
SET DEBUG_SYNC= 'now WAIT_FOR table_opened';
|
||||
--echo # Check that FLUSH must wait to get the GRL
|
||||
--echo # and let DROP PROCEDURE continue
|
||||
SET DEBUG_SYNC= 'wait_lock_global_read_lock SIGNAL grlwait';
|
||||
SET DEBUG_SYNC= 'mdl_acquire_lock_wait SIGNAL grlwait';
|
||||
--send FLUSH TABLES WITH READ LOCK
|
||||
|
||||
--echo # Connection 1
|
||||
connection default;
|
||||
--echo # Once FLUSH TABLES WITH READ LOCK starts waiting
|
||||
--echo # DROP PROCEDURE will be waked up and will drop
|
||||
--echo # procedure. Global read lock will be granted after
|
||||
--echo # this statement ends.
|
||||
--echo #
|
||||
--echo # Reaping DROP PROCEDURE.
|
||||
--reap
|
||||
|
||||
--echo # Connection 2
|
||||
connection con2;
|
||||
--echo # Reaping FTWRL.
|
||||
--reap
|
||||
UNLOCK TABLES;
|
||||
|
||||
@ -4485,7 +4492,7 @@ connection con2;
|
||||
--echo # Connection default
|
||||
connection default;
|
||||
let $wait_condition=SELECT COUNT(*)=1 FROM information_schema.processlist
|
||||
WHERE state='Waiting for release of readlock'
|
||||
WHERE state='Waiting for global read lock'
|
||||
AND info='CREATE TABLE db1.t2(a INT)';
|
||||
--source include/wait_condition.inc
|
||||
UNLOCK TABLES;
|
||||
@ -4507,7 +4514,7 @@ connection con2;
|
||||
--echo # Connection default
|
||||
connection default;
|
||||
let $wait_condition=SELECT COUNT(*)=1 FROM information_schema.processlist
|
||||
WHERE state='Waiting for release of readlock'
|
||||
WHERE state='Waiting for global read lock'
|
||||
AND info='ALTER DATABASE db1 DEFAULT CHARACTER SET utf8';
|
||||
--source include/wait_condition.inc
|
||||
UNLOCK TABLES;
|
||||
|
@ -896,7 +896,7 @@ connection default;
|
||||
--echo connection: default
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where state = "Waiting for global metadata lock";
|
||||
where state = "Waiting for global read lock";
|
||||
--source include/wait_condition.inc
|
||||
create trigger t1_bi before insert on t1 for each row begin end;
|
||||
unlock tables;
|
||||
|
@ -290,7 +290,6 @@ Event_basic::load_time_zone(THD *thd, const LEX_STRING tz_name)
|
||||
*/
|
||||
|
||||
Event_queue_element::Event_queue_element():
|
||||
status_changed(FALSE), last_executed_changed(FALSE),
|
||||
on_completion(Event_parse_data::ON_COMPLETION_DROP),
|
||||
status(Event_parse_data::ENABLED), expression(0), dropped(FALSE),
|
||||
execution_count(0)
|
||||
@ -539,7 +538,6 @@ Event_queue_element::load_from_row(THD *thd, TABLE *table)
|
||||
TIME_NO_ZERO_DATE);
|
||||
last_executed= my_tz_OFFSET0->TIME_to_gmt_sec(&time,¬_used);
|
||||
}
|
||||
last_executed_changed= FALSE;
|
||||
|
||||
if ((ptr= get_field(&mem_root, table->field[ET_FIELD_STATUS])) == NullS)
|
||||
DBUG_RETURN(TRUE);
|
||||
@ -935,7 +933,6 @@ Event_queue_element::compute_next_execution_time()
|
||||
DBUG_PRINT("info",("One-time event will be dropped: %d.", dropped));
|
||||
|
||||
status= Event_parse_data::DISABLED;
|
||||
status_changed= TRUE;
|
||||
}
|
||||
goto ret;
|
||||
}
|
||||
@ -955,7 +952,6 @@ Event_queue_element::compute_next_execution_time()
|
||||
dropped= TRUE;
|
||||
DBUG_PRINT("info", ("Dropped: %d", dropped));
|
||||
status= Event_parse_data::DISABLED;
|
||||
status_changed= TRUE;
|
||||
|
||||
goto ret;
|
||||
}
|
||||
@ -1018,7 +1014,6 @@ Event_queue_element::compute_next_execution_time()
|
||||
if (on_completion == Event_parse_data::ON_COMPLETION_DROP)
|
||||
dropped= TRUE;
|
||||
status= Event_parse_data::DISABLED;
|
||||
status_changed= TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1108,7 +1103,6 @@ Event_queue_element::compute_next_execution_time()
|
||||
execute_at= 0;
|
||||
execute_at_null= TRUE;
|
||||
status= Event_parse_data::DISABLED;
|
||||
status_changed= TRUE;
|
||||
if (on_completion == Event_parse_data::ON_COMPLETION_DROP)
|
||||
dropped= TRUE;
|
||||
}
|
||||
@ -1144,50 +1138,11 @@ void
|
||||
Event_queue_element::mark_last_executed(THD *thd)
|
||||
{
|
||||
last_executed= (my_time_t) thd->query_start();
|
||||
last_executed_changed= TRUE;
|
||||
|
||||
execution_count++;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Saves status and last_executed_at to the disk if changed.
|
||||
|
||||
SYNOPSIS
|
||||
Event_queue_element::update_timing_fields()
|
||||
thd - thread context
|
||||
|
||||
RETURN VALUE
|
||||
FALSE OK
|
||||
TRUE Error while opening mysql.event for writing or during
|
||||
write on disk
|
||||
*/
|
||||
|
||||
bool
|
||||
Event_queue_element::update_timing_fields(THD *thd)
|
||||
{
|
||||
Event_db_repository *db_repository= Events::get_db_repository();
|
||||
int ret;
|
||||
|
||||
DBUG_ENTER("Event_queue_element::update_timing_fields");
|
||||
|
||||
DBUG_PRINT("enter", ("name: %*s", (int) name.length, name.str));
|
||||
|
||||
/* No need to update if nothing has changed */
|
||||
if (!(status_changed || last_executed_changed))
|
||||
DBUG_RETURN(0);
|
||||
|
||||
ret= db_repository->update_timing_fields_for_event(thd,
|
||||
dbname, name,
|
||||
last_executed_changed,
|
||||
last_executed,
|
||||
status_changed,
|
||||
(ulonglong) status);
|
||||
last_executed_changed= status_changed= FALSE;
|
||||
DBUG_RETURN(ret);
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
void
|
||||
append_datetime(String *buf, Time_zone *time_zone, my_time_t secs,
|
||||
|
@ -82,10 +82,6 @@ protected:
|
||||
|
||||
class Event_queue_element : public Event_basic
|
||||
{
|
||||
protected:
|
||||
bool status_changed;
|
||||
bool last_executed_changed;
|
||||
|
||||
public:
|
||||
int on_completion;
|
||||
int status;
|
||||
@ -117,9 +113,6 @@ public:
|
||||
|
||||
void
|
||||
mark_last_executed(THD *thd);
|
||||
|
||||
bool
|
||||
update_timing_fields(THD *thd);
|
||||
};
|
||||
|
||||
|
||||
|
@ -622,6 +622,12 @@ Event_db_repository::create_event(THD *thd, Event_parse_data *parse_data,
|
||||
TABLE *table= NULL;
|
||||
sp_head *sp= thd->lex->sphead;
|
||||
ulong saved_mode= thd->variables.sql_mode;
|
||||
/*
|
||||
Take a savepoint to release only the lock on mysql.event
|
||||
table at the end but keep the global read lock and
|
||||
possible other locks taken by the caller.
|
||||
*/
|
||||
MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
|
||||
DBUG_ENTER("Event_db_repository::create_event");
|
||||
|
||||
@ -699,8 +705,8 @@ Event_db_repository::create_event(THD *thd, Event_parse_data *parse_data,
|
||||
ret= 0;
|
||||
|
||||
end:
|
||||
if (table)
|
||||
close_mysql_tables(thd);
|
||||
close_thread_tables(thd);
|
||||
thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
|
||||
|
||||
thd->variables.sql_mode= saved_mode;
|
||||
DBUG_RETURN(test(ret));
|
||||
@ -734,6 +740,12 @@ Event_db_repository::update_event(THD *thd, Event_parse_data *parse_data,
|
||||
TABLE *table= NULL;
|
||||
sp_head *sp= thd->lex->sphead;
|
||||
ulong saved_mode= thd->variables.sql_mode;
|
||||
/*
|
||||
Take a savepoint to release only the lock on mysql.event
|
||||
table at the end but keep the global read lock and
|
||||
possible other locks taken by the caller.
|
||||
*/
|
||||
MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
int ret= 1;
|
||||
|
||||
DBUG_ENTER("Event_db_repository::update_event");
|
||||
@ -811,8 +823,8 @@ Event_db_repository::update_event(THD *thd, Event_parse_data *parse_data,
|
||||
ret= 0;
|
||||
|
||||
end:
|
||||
if (table)
|
||||
close_mysql_tables(thd);
|
||||
close_thread_tables(thd);
|
||||
thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
|
||||
|
||||
thd->variables.sql_mode= saved_mode;
|
||||
DBUG_RETURN(test(ret));
|
||||
@ -838,6 +850,12 @@ Event_db_repository::drop_event(THD *thd, LEX_STRING db, LEX_STRING name,
|
||||
bool drop_if_exists)
|
||||
{
|
||||
TABLE *table= NULL;
|
||||
/*
|
||||
Take a savepoint to release only the lock on mysql.event
|
||||
table at the end but keep the global read lock and
|
||||
possible other locks taken by the caller.
|
||||
*/
|
||||
MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
int ret= 1;
|
||||
|
||||
DBUG_ENTER("Event_db_repository::drop_event");
|
||||
@ -866,8 +884,8 @@ Event_db_repository::drop_event(THD *thd, LEX_STRING db, LEX_STRING name,
|
||||
ret= 0;
|
||||
|
||||
end:
|
||||
if (table)
|
||||
close_mysql_tables(thd);
|
||||
close_thread_tables(thd);
|
||||
thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
|
||||
|
||||
DBUG_RETURN(test(ret));
|
||||
}
|
||||
@ -940,7 +958,7 @@ Event_db_repository::drop_schema_events(THD *thd, LEX_STRING schema)
|
||||
TABLE *table= NULL;
|
||||
READ_RECORD read_record_info;
|
||||
enum enum_events_table_field field= ET_FIELD_DB;
|
||||
MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
DBUG_ENTER("Event_db_repository::drop_schema_events");
|
||||
DBUG_PRINT("enter", ("field=%d schema=%s", field, schema.str));
|
||||
|
||||
@ -1042,15 +1060,14 @@ Event_db_repository::
|
||||
update_timing_fields_for_event(THD *thd,
|
||||
LEX_STRING event_db_name,
|
||||
LEX_STRING event_name,
|
||||
bool update_last_executed,
|
||||
my_time_t last_executed,
|
||||
bool update_status,
|
||||
ulonglong status)
|
||||
{
|
||||
TABLE *table= NULL;
|
||||
Field **fields;
|
||||
int ret= 1;
|
||||
bool save_binlog_row_based;
|
||||
MYSQL_TIME time;
|
||||
|
||||
DBUG_ENTER("Event_db_repository::update_timing_fields_for_event");
|
||||
|
||||
@ -1075,20 +1092,12 @@ update_timing_fields_for_event(THD *thd,
|
||||
/* Don't update create on row update. */
|
||||
table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET;
|
||||
|
||||
if (update_last_executed)
|
||||
{
|
||||
MYSQL_TIME time;
|
||||
my_tz_OFFSET0->gmt_sec_to_TIME(&time, last_executed);
|
||||
my_tz_OFFSET0->gmt_sec_to_TIME(&time, last_executed);
|
||||
fields[ET_FIELD_LAST_EXECUTED]->set_notnull();
|
||||
fields[ET_FIELD_LAST_EXECUTED]->store_time(&time, MYSQL_TIMESTAMP_DATETIME);
|
||||
|
||||
fields[ET_FIELD_LAST_EXECUTED]->set_notnull();
|
||||
fields[ET_FIELD_LAST_EXECUTED]->store_time(&time,
|
||||
MYSQL_TIMESTAMP_DATETIME);
|
||||
}
|
||||
if (update_status)
|
||||
{
|
||||
fields[ET_FIELD_STATUS]->set_notnull();
|
||||
fields[ET_FIELD_STATUS]->store(status, TRUE);
|
||||
}
|
||||
fields[ET_FIELD_STATUS]->set_notnull();
|
||||
fields[ET_FIELD_STATUS]->store(status, TRUE);
|
||||
|
||||
if ((ret= table->file->ha_update_row(table->record[1], table->record[0])))
|
||||
{
|
||||
|
@ -101,9 +101,7 @@ public:
|
||||
update_timing_fields_for_event(THD *thd,
|
||||
LEX_STRING event_db_name,
|
||||
LEX_STRING event_name,
|
||||
bool update_last_executed,
|
||||
my_time_t last_executed,
|
||||
bool update_status,
|
||||
ulonglong status);
|
||||
public:
|
||||
static bool
|
||||
|
@ -17,6 +17,8 @@
|
||||
#include "unireg.h"
|
||||
#include "event_queue.h"
|
||||
#include "event_data_objects.h"
|
||||
#include "event_db_repository.h"
|
||||
#include "events.h"
|
||||
#include "sql_audit.h"
|
||||
#include "tztime.h" // my_tz_find, my_tz_OFFSET0, struct Time_zone
|
||||
#include "log.h" // sql_print_error
|
||||
@ -444,7 +446,6 @@ Event_queue::recalculate_activation_times(THD *thd)
|
||||
for (i= 0; i < queue.elements; i++)
|
||||
{
|
||||
((Event_queue_element*)queue_element(&queue, i))->compute_next_execution_time();
|
||||
((Event_queue_element*)queue_element(&queue, i))->update_timing_fields(thd);
|
||||
}
|
||||
queue_fix(&queue);
|
||||
/*
|
||||
@ -567,6 +568,8 @@ Event_queue::get_top_for_execution_if_time(THD *thd,
|
||||
{
|
||||
bool ret= FALSE;
|
||||
*event_name= NULL;
|
||||
my_time_t last_executed;
|
||||
int status;
|
||||
DBUG_ENTER("Event_queue::get_top_for_execution_if_time");
|
||||
|
||||
LOCK_QUEUE_DATA();
|
||||
@ -632,8 +635,14 @@ Event_queue::get_top_for_execution_if_time(THD *thd,
|
||||
|
||||
top->execution_count++;
|
||||
(*event_name)->dropped= top->dropped;
|
||||
/*
|
||||
Save new values of last_executed timestamp and event status on stack
|
||||
in order to be able to update event description in system table once
|
||||
QUEUE_DATA lock is released.
|
||||
*/
|
||||
last_executed= top->last_executed;
|
||||
status= top->status;
|
||||
|
||||
top->update_timing_fields(thd);
|
||||
if (top->status == Event_parse_data::DISABLED)
|
||||
{
|
||||
DBUG_PRINT("info", ("removing from the queue"));
|
||||
@ -656,9 +665,16 @@ end:
|
||||
ret, (long) *event_name));
|
||||
|
||||
if (*event_name)
|
||||
{
|
||||
DBUG_PRINT("info", ("db: %s name: %s",
|
||||
(*event_name)->dbname.str, (*event_name)->name.str));
|
||||
|
||||
Event_db_repository *db_repository= Events::get_db_repository();
|
||||
(void) db_repository->update_timing_fields_for_event(thd,
|
||||
(*event_name)->dbname, (*event_name)->name,
|
||||
last_executed, (ulonglong) status);
|
||||
}
|
||||
|
||||
DBUG_RETURN(ret);
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "event_scheduler.h"
|
||||
#include "sp_head.h" // for Stored_program_creation_ctx
|
||||
#include "set_var.h"
|
||||
#include "lock.h" // lock_object_name
|
||||
|
||||
/**
|
||||
@addtogroup Event_Scheduler
|
||||
@ -77,7 +78,6 @@ Event_queue *Events::event_queue;
|
||||
Event_scheduler *Events::scheduler;
|
||||
Event_db_repository *Events::db_repository;
|
||||
ulong Events::opt_event_scheduler= Events::EVENTS_OFF;
|
||||
mysql_mutex_t Events::LOCK_event_metadata;
|
||||
bool Events::check_system_tables_error= FALSE;
|
||||
|
||||
|
||||
@ -340,7 +340,9 @@ Events::create_event(THD *thd, Event_parse_data *parse_data,
|
||||
if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row()))
|
||||
thd->clear_current_stmt_binlog_format_row();
|
||||
|
||||
mysql_mutex_lock(&LOCK_event_metadata);
|
||||
if (lock_object_name(thd, MDL_key::EVENT,
|
||||
parse_data->dbname.str, parse_data->name.str))
|
||||
DBUG_RETURN(TRUE);
|
||||
|
||||
/* On error conditions my_error() is called so no need to handle here */
|
||||
if (!(ret= db_repository->create_event(thd, parse_data, if_not_exists)))
|
||||
@ -388,7 +390,6 @@ Events::create_event(THD *thd, Event_parse_data *parse_data,
|
||||
}
|
||||
}
|
||||
}
|
||||
mysql_mutex_unlock(&LOCK_event_metadata);
|
||||
/* Restore the state of binlog format */
|
||||
DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
|
||||
if (save_binlog_row_based)
|
||||
@ -472,7 +473,9 @@ Events::update_event(THD *thd, Event_parse_data *parse_data,
|
||||
if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row()))
|
||||
thd->clear_current_stmt_binlog_format_row();
|
||||
|
||||
mysql_mutex_lock(&LOCK_event_metadata);
|
||||
if (lock_object_name(thd, MDL_key::EVENT,
|
||||
parse_data->dbname.str, parse_data->name.str))
|
||||
DBUG_RETURN(TRUE);
|
||||
|
||||
/* On error conditions my_error() is called so no need to handle here */
|
||||
if (!(ret= db_repository->update_event(thd, parse_data,
|
||||
@ -502,7 +505,6 @@ Events::update_event(THD *thd, Event_parse_data *parse_data,
|
||||
ret= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
|
||||
}
|
||||
}
|
||||
mysql_mutex_unlock(&LOCK_event_metadata);
|
||||
/* Restore the state of binlog format */
|
||||
DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
|
||||
if (save_binlog_row_based)
|
||||
@ -556,7 +558,9 @@ Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists)
|
||||
if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row()))
|
||||
thd->clear_current_stmt_binlog_format_row();
|
||||
|
||||
mysql_mutex_lock(&LOCK_event_metadata);
|
||||
if (lock_object_name(thd, MDL_key::EVENT,
|
||||
dbname.str, name.str))
|
||||
DBUG_RETURN(TRUE);
|
||||
/* On error conditions my_error() is called so no need to handle here */
|
||||
if (!(ret= db_repository->drop_event(thd, dbname, name, if_exists)))
|
||||
{
|
||||
@ -566,7 +570,6 @@ Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists)
|
||||
DBUG_ASSERT(thd->query() && thd->query_length());
|
||||
ret= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
|
||||
}
|
||||
mysql_mutex_unlock(&LOCK_event_metadata);
|
||||
/* Restore the state of binlog format */
|
||||
DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
|
||||
if (save_binlog_row_based)
|
||||
@ -595,15 +598,12 @@ Events::drop_schema_events(THD *thd, char *db)
|
||||
DBUG_PRINT("enter", ("dropping events from %s", db));
|
||||
|
||||
/*
|
||||
sic: no check if the scheduler is disabled or system tables
|
||||
Sic: no check if the scheduler is disabled or system tables
|
||||
are damaged, as intended.
|
||||
*/
|
||||
|
||||
mysql_mutex_lock(&LOCK_event_metadata);
|
||||
if (event_queue)
|
||||
event_queue->drop_schema_events(thd, db_lex);
|
||||
db_repository->drop_schema_events(thd, db_lex);
|
||||
mysql_mutex_unlock(&LOCK_event_metadata);
|
||||
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
@ -915,12 +915,11 @@ Events::deinit()
|
||||
}
|
||||
|
||||
#ifdef HAVE_PSI_INTERFACE
|
||||
PSI_mutex_key key_LOCK_event_metadata, key_LOCK_event_queue,
|
||||
PSI_mutex_key key_LOCK_event_queue,
|
||||
key_event_scheduler_LOCK_scheduler_state;
|
||||
|
||||
static PSI_mutex_info all_events_mutexes[]=
|
||||
{
|
||||
{ &key_LOCK_event_metadata, "LOCK_event_metadata", PSI_FLAG_GLOBAL},
|
||||
{ &key_LOCK_event_queue, "LOCK_event_queue", PSI_FLAG_GLOBAL},
|
||||
{ &key_event_scheduler_LOCK_scheduler_state, "Event_scheduler::LOCK_scheduler_state", PSI_FLAG_GLOBAL}
|
||||
};
|
||||
@ -974,23 +973,6 @@ Events::init_mutexes()
|
||||
#ifdef HAVE_PSI_INTERFACE
|
||||
init_events_psi_keys();
|
||||
#endif
|
||||
|
||||
mysql_mutex_init(key_LOCK_event_metadata,
|
||||
&LOCK_event_metadata, MY_MUTEX_INIT_FAST);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Destroys Events mutexes
|
||||
|
||||
SYNOPSIS
|
||||
Events::destroy_mutexes()
|
||||
*/
|
||||
|
||||
void
|
||||
Events::destroy_mutexes()
|
||||
{
|
||||
mysql_mutex_destroy(&LOCK_event_metadata);
|
||||
}
|
||||
|
||||
|
||||
|
@ -26,8 +26,7 @@
|
||||
*/
|
||||
|
||||
#ifdef HAVE_PSI_INTERFACE
|
||||
extern PSI_mutex_key key_LOCK_event_metadata,
|
||||
key_event_scheduler_LOCK_scheduler_state;
|
||||
extern PSI_mutex_key key_event_scheduler_LOCK_scheduler_state;
|
||||
extern PSI_cond_key key_event_scheduler_COND_state;
|
||||
extern PSI_thread_key key_thread_event_scheduler, key_thread_event_worker;
|
||||
#endif /* HAVE_PSI_INTERFACE */
|
||||
@ -79,7 +78,6 @@ public:
|
||||
enum enum_opt_event_scheduler { EVENTS_OFF, EVENTS_ON, EVENTS_DISABLED };
|
||||
/* Protected using LOCK_global_system_variables only. */
|
||||
static ulong opt_event_scheduler;
|
||||
static mysql_mutex_t LOCK_event_metadata;
|
||||
static bool check_if_system_tables_error();
|
||||
static bool start();
|
||||
static bool stop();
|
||||
|
@ -7382,38 +7382,35 @@ int ndbcluster_find_files(handlerton *hton, THD *thd,
|
||||
}
|
||||
|
||||
/*
|
||||
Delete old files.
|
||||
|
||||
ndbcluster_find_files() may be called from I_S code and ndbcluster_binlog
|
||||
thread in situations when some tables are already open. This means that
|
||||
code below will try to obtain exclusive metadata lock on some table
|
||||
while holding shared meta-data lock on other tables. This might lead to
|
||||
a deadlock, and therefore is disallowed by assertions of the metadata
|
||||
locking subsystem. This is violation of metadata
|
||||
locking protocol which has to be closed ASAP.
|
||||
while holding shared meta-data lock on other tables. This might lead to a
|
||||
deadlock but such a deadlock should be detected by MDL deadlock detector.
|
||||
|
||||
XXX: the scenario described above is not covered with any test.
|
||||
*/
|
||||
if (!global_read_lock)
|
||||
List_iterator_fast<char> it3(delete_list);
|
||||
while ((file_name_str= it3++))
|
||||
{
|
||||
// Delete old files
|
||||
List_iterator_fast<char> it3(delete_list);
|
||||
while ((file_name_str= it3++))
|
||||
{
|
||||
DBUG_PRINT("info", ("Remove table %s/%s", db, file_name_str));
|
||||
// Delete the table and all related files
|
||||
TABLE_LIST table_list;
|
||||
table_list.init_one_table(db, strlen(db), file_name_str,
|
||||
strlen(file_name_str), file_name_str,
|
||||
TL_WRITE);
|
||||
table_list.mdl_request.set_type(MDL_EXCLUSIVE);
|
||||
(void)mysql_rm_table_part2(thd, &table_list,
|
||||
FALSE, /* if_exists */
|
||||
FALSE, /* drop_temporary */
|
||||
FALSE, /* drop_view */
|
||||
TRUE /* dont_log_query*/);
|
||||
trans_commit_implicit(thd); /* Safety, should be unnecessary. */
|
||||
thd->mdl_context.release_transactional_locks();
|
||||
/* Clear error message that is returned when table is deleted */
|
||||
thd->clear_error();
|
||||
}
|
||||
DBUG_PRINT("info", ("Remove table %s/%s", db, file_name_str));
|
||||
/* Delete the table and all related files. */
|
||||
TABLE_LIST table_list;
|
||||
table_list.init_one_table(db, strlen(db), file_name_str,
|
||||
strlen(file_name_str), file_name_str,
|
||||
TL_WRITE);
|
||||
table_list.mdl_request.set_type(MDL_EXCLUSIVE);
|
||||
(void)mysql_rm_table_part2(thd, &table_list,
|
||||
FALSE, /* if_exists */
|
||||
FALSE, /* drop_temporary */
|
||||
FALSE, /* drop_view */
|
||||
TRUE /* dont_log_query*/);
|
||||
trans_commit_implicit(thd); /* Safety, should be unnecessary. */
|
||||
thd->mdl_context.release_transactional_locks();
|
||||
/* Clear error message that is returned when table is deleted */
|
||||
thd->clear_error();
|
||||
}
|
||||
|
||||
/* Lock mutex before creating .FRM files. */
|
||||
|
@ -29,8 +29,6 @@
|
||||
#include "sql_cache.h" // query_cache, query_cache_*
|
||||
#include "key.h" // key_copy, key_unpack, key_cmp_if_same, key_cmp
|
||||
#include "sql_table.h" // build_table_filename
|
||||
#include "lock.h" // wait_if_global_read_lock,
|
||||
// start_waiting_global_read_lock
|
||||
#include "sql_parse.h" // check_stack_overrun
|
||||
#include "sql_acl.h" // SUPER_ACL
|
||||
#include "sql_base.h" // free_io_cache
|
||||
@ -41,6 +39,7 @@
|
||||
#include "transaction.h"
|
||||
#include <errno.h>
|
||||
#include "probes_mysql.h"
|
||||
#include "debug_sync.h" // DEBUG_SYNC
|
||||
|
||||
#ifdef WITH_PARTITION_STORAGE_ENGINE
|
||||
#include "ha_partition.h"
|
||||
@ -1155,6 +1154,7 @@ int ha_commit_trans(THD *thd, bool all)
|
||||
{
|
||||
uint rw_ha_count;
|
||||
bool rw_trans;
|
||||
MDL_request mdl_request;
|
||||
|
||||
DBUG_EXECUTE_IF("crash_commit_before", DBUG_SUICIDE(););
|
||||
|
||||
@ -1166,11 +1166,27 @@ int ha_commit_trans(THD *thd, bool all)
|
||||
/* rw_trans is TRUE when we in a transaction changing data */
|
||||
rw_trans= is_real_trans && (rw_ha_count > 0);
|
||||
|
||||
if (rw_trans &&
|
||||
thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, FALSE))
|
||||
if (rw_trans)
|
||||
{
|
||||
ha_rollback_trans(thd, all);
|
||||
DBUG_RETURN(1);
|
||||
/*
|
||||
Acquire a metadata lock which will ensure that COMMIT is blocked
|
||||
by an active FLUSH TABLES WITH READ LOCK (and vice versa:
|
||||
COMMIT in progress blocks FTWRL).
|
||||
|
||||
We allow the owner of FTWRL to COMMIT; we assume that it knows
|
||||
what it does.
|
||||
*/
|
||||
mdl_request.init(MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE,
|
||||
MDL_EXPLICIT);
|
||||
|
||||
if (thd->mdl_context.acquire_lock(&mdl_request,
|
||||
thd->variables.lock_wait_timeout))
|
||||
{
|
||||
ha_rollback_trans(thd, all);
|
||||
DBUG_RETURN(1);
|
||||
}
|
||||
|
||||
DEBUG_SYNC(thd, "ha_commit_trans_after_acquire_commit_lock");
|
||||
}
|
||||
|
||||
if (rw_trans &&
|
||||
@ -1225,8 +1241,16 @@ int ha_commit_trans(THD *thd, bool all)
|
||||
DBUG_EXECUTE_IF("crash_commit_after", DBUG_SUICIDE(););
|
||||
RUN_HOOK(transaction, after_commit, (thd, FALSE));
|
||||
end:
|
||||
if (rw_trans)
|
||||
thd->global_read_lock.start_waiting_global_read_lock(thd);
|
||||
if (rw_trans && mdl_request.ticket)
|
||||
{
|
||||
/*
|
||||
We do not always immediately release transactional locks
|
||||
after ha_commit_trans() (see uses of ha_enable_transaction()),
|
||||
thus we release the commit blocker lock as soon as it's
|
||||
not needed.
|
||||
*/
|
||||
thd->mdl_context.release_lock(mdl_request.ticket);
|
||||
}
|
||||
}
|
||||
/* Free resources and perform other cleanup even for 'empty' transactions. */
|
||||
else if (is_real_trans)
|
||||
|
360
sql/lock.cc
360
sql/lock.cc
@ -777,8 +777,11 @@ bool lock_schema_name(THD *thd, const char *db)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE);
|
||||
mdl_request.init(MDL_key::SCHEMA, db, "", MDL_EXCLUSIVE);
|
||||
if (thd->global_read_lock.can_acquire_protection())
|
||||
return TRUE;
|
||||
global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
|
||||
MDL_STATEMENT);
|
||||
mdl_request.init(MDL_key::SCHEMA, db, "", MDL_EXCLUSIVE, MDL_TRANSACTION);
|
||||
|
||||
mdl_requests.push_front(&mdl_request);
|
||||
mdl_requests.push_front(&global_request);
|
||||
@ -793,13 +796,13 @@ bool lock_schema_name(THD *thd, const char *db)
|
||||
|
||||
|
||||
/**
|
||||
Obtain an exclusive metadata lock on the stored routine name.
|
||||
Obtain an exclusive metadata lock on an object name.
|
||||
|
||||
@param thd Thread handle.
|
||||
@param is_function Stored routine type (only functions or procedures
|
||||
are name-locked.
|
||||
@param db The schema the routine belongs to.
|
||||
@param name Routine name.
|
||||
@param mdl_type Object type (currently functions, procedures
|
||||
and events can be name-locked).
|
||||
@param db The schema the object belongs to.
|
||||
@param name Object name in the schema.
|
||||
|
||||
This function assumes that no metadata locks were acquired
|
||||
before calling it. Additionally, it cannot be called while
|
||||
@ -815,12 +818,9 @@ bool lock_schema_name(THD *thd, const char *db)
|
||||
or this connection was killed.
|
||||
*/
|
||||
|
||||
bool lock_routine_name(THD *thd, bool is_function,
|
||||
bool lock_object_name(THD *thd, MDL_key::enum_mdl_namespace mdl_type,
|
||||
const char *db, const char *name)
|
||||
{
|
||||
MDL_key::enum_mdl_namespace mdl_type= (is_function ?
|
||||
MDL_key::FUNCTION :
|
||||
MDL_key::PROCEDURE);
|
||||
MDL_request_list mdl_requests;
|
||||
MDL_request global_request;
|
||||
MDL_request schema_request;
|
||||
@ -836,9 +836,13 @@ bool lock_routine_name(THD *thd, bool is_function,
|
||||
DBUG_ASSERT(name);
|
||||
DEBUG_SYNC(thd, "before_wait_locked_pname");
|
||||
|
||||
global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE);
|
||||
schema_request.init(MDL_key::SCHEMA, db, "", MDL_INTENTION_EXCLUSIVE);
|
||||
mdl_request.init(mdl_type, db, name, MDL_EXCLUSIVE);
|
||||
if (thd->global_read_lock.can_acquire_protection())
|
||||
return TRUE;
|
||||
global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
|
||||
MDL_STATEMENT);
|
||||
schema_request.init(MDL_key::SCHEMA, db, "", MDL_INTENTION_EXCLUSIVE,
|
||||
MDL_TRANSACTION);
|
||||
mdl_request.init(mdl_type, db, name, MDL_EXCLUSIVE, MDL_TRANSACTION);
|
||||
|
||||
mdl_requests.push_front(&mdl_request);
|
||||
mdl_requests.push_front(&schema_request);
|
||||
@ -888,45 +892,24 @@ static void print_lock_error(int error, const char *table)
|
||||
/****************************************************************************
|
||||
Handling of global read locks
|
||||
|
||||
Global read lock is implemented using metadata lock infrastructure.
|
||||
|
||||
Taking the global read lock is TWO steps (2nd step is optional; without
|
||||
it, COMMIT of existing transactions will be allowed):
|
||||
lock_global_read_lock() THEN make_global_read_lock_block_commit().
|
||||
|
||||
The global locks are handled through the global variables:
|
||||
global_read_lock
|
||||
count of threads which have the global read lock (i.e. have completed at
|
||||
least the first step above)
|
||||
global_read_lock_blocks_commit
|
||||
count of threads which have the global read lock and block
|
||||
commits (i.e. are in or have completed the second step above)
|
||||
waiting_for_read_lock
|
||||
count of threads which want to take a global read lock but cannot
|
||||
protect_against_global_read_lock
|
||||
count of threads which have set protection against global read lock.
|
||||
|
||||
access to them is protected with a mutex LOCK_global_read_lock
|
||||
|
||||
(XXX: one should never take LOCK_open if LOCK_global_read_lock is
|
||||
taken, otherwise a deadlock may occur. Other mutexes could be a
|
||||
problem too - grep the code for global_read_lock if you want to use
|
||||
any other mutex here) Also one must not hold LOCK_open when calling
|
||||
wait_if_global_read_lock(). When the thread with the global read lock
|
||||
tries to close its tables, it needs to take LOCK_open in
|
||||
close_thread_table().
|
||||
|
||||
How blocking of threads by global read lock is achieved: that's
|
||||
advisory. Any piece of code which should be blocked by global read lock must
|
||||
be designed like this:
|
||||
- call to wait_if_global_read_lock(). When this returns 0, no global read
|
||||
lock is owned; if argument abort_on_refresh was 0, none can be obtained.
|
||||
- job
|
||||
- if abort_on_refresh was 0, call to start_waiting_global_read_lock() to
|
||||
allow other threads to get the global read lock. I.e. removal of the
|
||||
protection.
|
||||
(Note: it's a bit like an implementation of rwlock).
|
||||
|
||||
[ I am sorry to mention some SQL syntaxes below I know I shouldn't but found
|
||||
no better descriptive way ]
|
||||
semi-automatic. We assume that any statement which should be blocked
|
||||
by global read lock will either open and acquires write-lock on tables
|
||||
or acquires metadata locks on objects it is going to modify. For any
|
||||
such statement global IX metadata lock is automatically acquired for
|
||||
its duration (in case of LOCK TABLES until end of LOCK TABLES mode).
|
||||
And lock_global_read_lock() simply acquires global S metadata lock
|
||||
and thus prohibits execution of statements which modify data (unless
|
||||
they modify only temporary tables). If deadlock happens it is detected
|
||||
by MDL subsystem and resolved in the standard fashion (by backing-off
|
||||
metadata locks acquired so far and restarting open tables process
|
||||
if possible).
|
||||
|
||||
Why does FLUSH TABLES WITH READ LOCK need to block COMMIT: because it's used
|
||||
to read a non-moving SHOW MASTER STATUS, and a COMMIT writes to the binary
|
||||
@ -960,11 +943,6 @@ static void print_lock_error(int error, const char *table)
|
||||
|
||||
****************************************************************************/
|
||||
|
||||
volatile uint global_read_lock=0;
|
||||
volatile uint global_read_lock_blocks_commit=0;
|
||||
static volatile uint protect_against_global_read_lock=0;
|
||||
static volatile uint waiting_for_read_lock=0;
|
||||
|
||||
/**
|
||||
Take global read lock, wait if there is protection against lock.
|
||||
|
||||
@ -985,84 +963,17 @@ bool Global_read_lock::lock_global_read_lock(THD *thd)
|
||||
if (!m_state)
|
||||
{
|
||||
MDL_request mdl_request;
|
||||
const char *old_message;
|
||||
const char *new_message= "Waiting to get readlock";
|
||||
(void) mysql_mutex_lock(&LOCK_global_read_lock);
|
||||
|
||||
old_message= thd->enter_cond(&COND_global_read_lock,
|
||||
&LOCK_global_read_lock, new_message);
|
||||
DBUG_PRINT("info",
|
||||
("waiting_for: %d protect_against: %d",
|
||||
waiting_for_read_lock, protect_against_global_read_lock));
|
||||
|
||||
waiting_for_read_lock++;
|
||||
|
||||
#if defined(ENABLED_DEBUG_SYNC)
|
||||
/*
|
||||
The below sync point fires if we have to wait for
|
||||
protect_against_global_read_lock.
|
||||
|
||||
WARNING: Beware to use WAIT_FOR with this sync point. We hold
|
||||
LOCK_global_read_lock here.
|
||||
|
||||
The sync point is after enter_cond() so that proc_info is
|
||||
available immediately after the sync point sends a SIGNAL. This
|
||||
can make tests more reliable.
|
||||
|
||||
The sync point is before the loop so that it is executed only once.
|
||||
*/
|
||||
if (protect_against_global_read_lock && !thd->killed)
|
||||
{
|
||||
DEBUG_SYNC(thd, "wait_lock_global_read_lock");
|
||||
}
|
||||
#endif /* defined(ENABLED_DEBUG_SYNC) */
|
||||
|
||||
while (protect_against_global_read_lock && !thd->killed)
|
||||
mysql_cond_wait(&COND_global_read_lock, &LOCK_global_read_lock);
|
||||
waiting_for_read_lock--;
|
||||
if (thd->killed)
|
||||
{
|
||||
thd->exit_cond(old_message);
|
||||
DBUG_RETURN(1);
|
||||
}
|
||||
m_state= GRL_ACQUIRED;
|
||||
global_read_lock++;
|
||||
thd->exit_cond(old_message); // this unlocks LOCK_global_read_lock
|
||||
/*
|
||||
When we perform FLUSH TABLES or ALTER TABLE under LOCK TABLES,
|
||||
tables being reopened are protected only by meta-data locks at
|
||||
some point. To avoid sneaking in with our global read lock at
|
||||
this moment we have to take global shared meta data lock.
|
||||
|
||||
TODO: We should change this code to acquire global shared metadata
|
||||
lock before acquiring global read lock. But in order to do
|
||||
this we have to get rid of all those places in which
|
||||
wait_if_global_read_lock() is called before acquiring
|
||||
metadata locks first. Also long-term we should get rid of
|
||||
redundancy between metadata locks, global read lock and DDL
|
||||
blocker (see WL#4399 and WL#4400).
|
||||
*/
|
||||
|
||||
DBUG_ASSERT(! thd->mdl_context.is_lock_owner(MDL_key::GLOBAL, "", "",
|
||||
MDL_SHARED));
|
||||
mdl_request.init(MDL_key::GLOBAL, "", "", MDL_SHARED);
|
||||
mdl_request.init(MDL_key::GLOBAL, "", "", MDL_SHARED, MDL_EXPLICIT);
|
||||
|
||||
if (thd->mdl_context.acquire_lock(&mdl_request,
|
||||
thd->variables.lock_wait_timeout))
|
||||
{
|
||||
/* Our thread was killed -- return back to initial state. */
|
||||
mysql_mutex_lock(&LOCK_global_read_lock);
|
||||
if (!(--global_read_lock))
|
||||
{
|
||||
DBUG_PRINT("signal", ("Broadcasting COND_global_read_lock"));
|
||||
mysql_cond_broadcast(&COND_global_read_lock);
|
||||
}
|
||||
mysql_mutex_unlock(&LOCK_global_read_lock);
|
||||
m_state= GRL_NONE;
|
||||
DBUG_RETURN(1);
|
||||
}
|
||||
thd->mdl_context.move_ticket_after_trans_sentinel(mdl_request.ticket);
|
||||
|
||||
m_mdl_global_shared_lock= mdl_request.ticket;
|
||||
m_state= GRL_ACQUIRED;
|
||||
}
|
||||
/*
|
||||
We DON'T set global_read_lock_blocks_commit now, it will be set after
|
||||
@ -1088,166 +999,22 @@ bool Global_read_lock::lock_global_read_lock(THD *thd)
|
||||
|
||||
void Global_read_lock::unlock_global_read_lock(THD *thd)
|
||||
{
|
||||
uint tmp;
|
||||
DBUG_ENTER("unlock_global_read_lock");
|
||||
DBUG_PRINT("info",
|
||||
("global_read_lock: %u global_read_lock_blocks_commit: %u",
|
||||
global_read_lock, global_read_lock_blocks_commit));
|
||||
|
||||
DBUG_ASSERT(m_mdl_global_shared_lock && m_state);
|
||||
|
||||
if (m_mdl_blocks_commits_lock)
|
||||
{
|
||||
thd->mdl_context.release_lock(m_mdl_blocks_commits_lock);
|
||||
m_mdl_blocks_commits_lock= NULL;
|
||||
}
|
||||
thd->mdl_context.release_lock(m_mdl_global_shared_lock);
|
||||
m_mdl_global_shared_lock= NULL;
|
||||
|
||||
mysql_mutex_lock(&LOCK_global_read_lock);
|
||||
tmp= --global_read_lock;
|
||||
if (m_state == GRL_ACQUIRED_AND_BLOCKS_COMMIT)
|
||||
--global_read_lock_blocks_commit;
|
||||
mysql_mutex_unlock(&LOCK_global_read_lock);
|
||||
/* Send the signal outside the mutex to avoid a context switch */
|
||||
if (!tmp)
|
||||
{
|
||||
DBUG_PRINT("signal", ("Broadcasting COND_global_read_lock"));
|
||||
mysql_cond_broadcast(&COND_global_read_lock);
|
||||
}
|
||||
m_state= GRL_NONE;
|
||||
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
/**
|
||||
Wait if the global read lock is set, and optionally seek protection against
|
||||
global read lock.
|
||||
|
||||
See also "Handling of global read locks" above.
|
||||
|
||||
@param thd Reference to thread.
|
||||
@param abort_on_refresh If True, abort waiting if a refresh occurs,
|
||||
do NOT seek protection against GRL.
|
||||
If False, wait until the GRL is released and seek
|
||||
protection against GRL.
|
||||
@param is_not_commit If False, called from a commit operation,
|
||||
wait only if commit blocking is also enabled.
|
||||
|
||||
@retval False Success, protection against global read lock is set
|
||||
(if !abort_on_refresh)
|
||||
@retval True Failure, wait was aborted or thread was killed.
|
||||
*/
|
||||
|
||||
#define must_wait (global_read_lock && \
|
||||
(is_not_commit || \
|
||||
global_read_lock_blocks_commit))
|
||||
|
||||
bool Global_read_lock::
|
||||
wait_if_global_read_lock(THD *thd, bool abort_on_refresh,
|
||||
bool is_not_commit)
|
||||
{
|
||||
const char *UNINIT_VAR(old_message);
|
||||
bool result= 0, need_exit_cond;
|
||||
DBUG_ENTER("wait_if_global_read_lock");
|
||||
|
||||
/*
|
||||
If we already have protection against global read lock,
|
||||
just increment the counter.
|
||||
*/
|
||||
if (unlikely(m_protection_count > 0))
|
||||
{
|
||||
if (!abort_on_refresh)
|
||||
m_protection_count++;
|
||||
DBUG_RETURN(FALSE);
|
||||
}
|
||||
/*
|
||||
Assert that we do not own LOCK_open. If we would own it, other
|
||||
threads could not close their tables. This would make a pretty
|
||||
deadlock.
|
||||
*/
|
||||
mysql_mutex_assert_not_owner(&LOCK_open);
|
||||
|
||||
mysql_mutex_lock(&LOCK_global_read_lock);
|
||||
if ((need_exit_cond= must_wait))
|
||||
{
|
||||
if (m_state) // This thread had the read locks
|
||||
{
|
||||
if (is_not_commit)
|
||||
my_message(ER_CANT_UPDATE_WITH_READLOCK,
|
||||
ER(ER_CANT_UPDATE_WITH_READLOCK), MYF(0));
|
||||
mysql_mutex_unlock(&LOCK_global_read_lock);
|
||||
/*
|
||||
We allow FLUSHer to COMMIT; we assume FLUSHer knows what it does.
|
||||
This allowance is needed to not break existing versions of innobackup
|
||||
which do a BEGIN; INSERT; FLUSH TABLES WITH READ LOCK; COMMIT.
|
||||
*/
|
||||
DBUG_RETURN(is_not_commit);
|
||||
}
|
||||
old_message=thd->enter_cond(&COND_global_read_lock, &LOCK_global_read_lock,
|
||||
"Waiting for release of readlock");
|
||||
while (must_wait && ! thd->killed &&
|
||||
(!abort_on_refresh || !thd->open_tables ||
|
||||
thd->open_tables->s->version == refresh_version))
|
||||
{
|
||||
DBUG_PRINT("signal", ("Waiting for COND_global_read_lock"));
|
||||
mysql_cond_wait(&COND_global_read_lock, &LOCK_global_read_lock);
|
||||
DBUG_PRINT("signal", ("Got COND_global_read_lock"));
|
||||
}
|
||||
if (thd->killed)
|
||||
result=1;
|
||||
}
|
||||
if (!abort_on_refresh && !result)
|
||||
{
|
||||
m_protection_count++;
|
||||
protect_against_global_read_lock++;
|
||||
DBUG_PRINT("sql_lock", ("protect_against_global_read_lock incr: %u",
|
||||
protect_against_global_read_lock));
|
||||
}
|
||||
/*
|
||||
The following is only true in case of a global read locks (which is rare)
|
||||
and if old_message is set
|
||||
*/
|
||||
if (unlikely(need_exit_cond))
|
||||
thd->exit_cond(old_message); // this unlocks LOCK_global_read_lock
|
||||
else
|
||||
mysql_mutex_unlock(&LOCK_global_read_lock);
|
||||
DBUG_RETURN(result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Release protection against global read lock and restart
|
||||
global read lock waiters.
|
||||
|
||||
Should only be called if we have protection against global read lock.
|
||||
|
||||
See also "Handling of global read locks" above.
|
||||
|
||||
@param thd Reference to thread.
|
||||
*/
|
||||
|
||||
void Global_read_lock::start_waiting_global_read_lock(THD *thd)
|
||||
{
|
||||
bool tmp;
|
||||
DBUG_ENTER("start_waiting_global_read_lock");
|
||||
/*
|
||||
Ignore request if we do not have protection against global read lock.
|
||||
(Note that this is a violation of the interface contract, hence the assert).
|
||||
*/
|
||||
DBUG_ASSERT(m_protection_count > 0);
|
||||
if (unlikely(m_protection_count == 0))
|
||||
DBUG_VOID_RETURN;
|
||||
/* Decrement local read lock protection counter, return if we still have it */
|
||||
if (unlikely(--m_protection_count > 0))
|
||||
DBUG_VOID_RETURN;
|
||||
if (unlikely(m_state))
|
||||
DBUG_VOID_RETURN;
|
||||
mysql_mutex_lock(&LOCK_global_read_lock);
|
||||
DBUG_ASSERT(protect_against_global_read_lock);
|
||||
tmp= (!--protect_against_global_read_lock &&
|
||||
(waiting_for_read_lock || global_read_lock_blocks_commit));
|
||||
mysql_mutex_unlock(&LOCK_global_read_lock);
|
||||
if (tmp)
|
||||
mysql_cond_broadcast(&COND_global_read_lock);
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Make global read lock also block commits.
|
||||
@ -1266,8 +1033,7 @@ void Global_read_lock::start_waiting_global_read_lock(THD *thd)
|
||||
|
||||
bool Global_read_lock::make_global_read_lock_block_commit(THD *thd)
|
||||
{
|
||||
bool error;
|
||||
const char *old_message;
|
||||
MDL_request mdl_request;
|
||||
DBUG_ENTER("make_global_read_lock_block_commit");
|
||||
/*
|
||||
If we didn't succeed lock_global_read_lock(), or if we already suceeded
|
||||
@ -1275,42 +1041,32 @@ bool Global_read_lock::make_global_read_lock_block_commit(THD *thd)
|
||||
*/
|
||||
if (m_state != GRL_ACQUIRED)
|
||||
DBUG_RETURN(0);
|
||||
mysql_mutex_lock(&LOCK_global_read_lock);
|
||||
/* increment this BEFORE waiting on cond (otherwise race cond) */
|
||||
global_read_lock_blocks_commit++;
|
||||
/* For testing we set up some blocking, to see if we can be killed */
|
||||
DBUG_EXECUTE_IF("make_global_read_lock_block_commit_loop",
|
||||
protect_against_global_read_lock++;);
|
||||
old_message= thd->enter_cond(&COND_global_read_lock, &LOCK_global_read_lock,
|
||||
"Waiting for all running commits to finish");
|
||||
while (protect_against_global_read_lock && !thd->killed)
|
||||
mysql_cond_wait(&COND_global_read_lock, &LOCK_global_read_lock);
|
||||
DBUG_EXECUTE_IF("make_global_read_lock_block_commit_loop",
|
||||
protect_against_global_read_lock--;);
|
||||
if ((error= test(thd->killed)))
|
||||
global_read_lock_blocks_commit--; // undo what we did
|
||||
else
|
||||
m_state= GRL_ACQUIRED_AND_BLOCKS_COMMIT;
|
||||
thd->exit_cond(old_message); // this unlocks LOCK_global_read_lock
|
||||
DBUG_RETURN(error);
|
||||
|
||||
mdl_request.init(MDL_key::COMMIT, "", "", MDL_SHARED, MDL_EXPLICIT);
|
||||
|
||||
if (thd->mdl_context.acquire_lock(&mdl_request,
|
||||
thd->variables.lock_wait_timeout))
|
||||
DBUG_RETURN(TRUE);
|
||||
|
||||
m_mdl_blocks_commits_lock= mdl_request.ticket;
|
||||
m_state= GRL_ACQUIRED_AND_BLOCKS_COMMIT;
|
||||
|
||||
DBUG_RETURN(FALSE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Broadcast COND_global_read_lock.
|
||||
Set explicit duration for metadata locks which are used to implement GRL.
|
||||
|
||||
TODO/FIXME: Dmitry thinks that we broadcast on COND_global_read_lock
|
||||
when old instance of table is closed to avoid races
|
||||
between incrementing refresh_version and
|
||||
wait_if_global_read_lock(thd, TRUE, FALSE) call.
|
||||
Once global read lock implementation starts using MDL
|
||||
infrastructure this will became unnecessary and should
|
||||
be removed.
|
||||
@param thd Reference to thread.
|
||||
*/
|
||||
|
||||
void broadcast_refresh(void)
|
||||
void Global_read_lock::set_explicit_lock_duration(THD *thd)
|
||||
{
|
||||
mysql_cond_broadcast(&COND_global_read_lock);
|
||||
if (m_mdl_global_shared_lock)
|
||||
thd->mdl_context.set_lock_duration(m_mdl_global_shared_lock, MDL_EXPLICIT);
|
||||
if (m_mdl_blocks_commits_lock)
|
||||
thd->mdl_context.set_lock_duration(m_mdl_blocks_commits_lock, MDL_EXPLICIT);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,6 +2,7 @@
|
||||
#define LOCK_INCLUDED
|
||||
|
||||
#include "thr_lock.h" /* thr_lock_type */
|
||||
#include "mdl.h"
|
||||
|
||||
// Forward declarations
|
||||
struct TABLE;
|
||||
@ -18,11 +19,10 @@ void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table);
|
||||
void mysql_lock_abort(THD *thd, TABLE *table, bool upgrade_lock);
|
||||
bool mysql_lock_abort_for_thread(THD *thd, TABLE *table);
|
||||
MYSQL_LOCK *mysql_lock_merge(MYSQL_LOCK *a,MYSQL_LOCK *b);
|
||||
void broadcast_refresh(void);
|
||||
/* Lock based on name */
|
||||
bool lock_schema_name(THD *thd, const char *db);
|
||||
/* Lock based on stored routine name */
|
||||
bool lock_routine_name(THD *thd, bool is_function, const char *db,
|
||||
const char *name);
|
||||
bool lock_object_name(THD *thd, MDL_key::enum_mdl_namespace mdl_type,
|
||||
const char *db, const char *name);
|
||||
|
||||
#endif /* LOCK_INCLUDED */
|
||||
|
@ -4961,6 +4961,8 @@ error:
|
||||
*/
|
||||
if (! thd->in_multi_stmt_transaction_mode())
|
||||
thd->mdl_context.release_transactional_locks();
|
||||
else
|
||||
thd->mdl_context.release_statement_locks();
|
||||
|
||||
DBUG_EXECUTE_IF("LOAD_DATA_INFILE_has_fatal_error",
|
||||
thd->is_slave_error= 0; thd->is_fatal_error= 1;);
|
||||
|
569
sql/mdl.cc
569
sql/mdl.cc
@ -68,8 +68,6 @@ static void init_mdl_psi_keys(void)
|
||||
}
|
||||
#endif /* HAVE_PSI_INTERFACE */
|
||||
|
||||
void notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket);
|
||||
|
||||
|
||||
/**
|
||||
Thread state names to be used in case when we have to wait on resource
|
||||
@ -78,12 +76,14 @@ void notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket);
|
||||
|
||||
const char *MDL_key::m_namespace_to_wait_state_name[NAMESPACE_END]=
|
||||
{
|
||||
"Waiting for global metadata lock",
|
||||
"Waiting for global read lock",
|
||||
"Waiting for schema metadata lock",
|
||||
"Waiting for table metadata lock",
|
||||
"Waiting for stored function metadata lock",
|
||||
"Waiting for stored procedure metadata lock",
|
||||
NULL
|
||||
"Waiting for trigger metadata lock",
|
||||
"Waiting for event metadata lock",
|
||||
"Waiting for commit lock"
|
||||
};
|
||||
|
||||
static bool mdl_initialized= 0;
|
||||
@ -100,7 +100,6 @@ class MDL_map
|
||||
public:
|
||||
void init();
|
||||
void destroy();
|
||||
MDL_lock *find(const MDL_key *key);
|
||||
MDL_lock *find_or_insert(const MDL_key *key);
|
||||
void remove(MDL_lock *lock);
|
||||
private:
|
||||
@ -110,6 +109,10 @@ private:
|
||||
HASH m_locks;
|
||||
/* Protects access to m_locks hash. */
|
||||
mysql_mutex_t m_mutex;
|
||||
/** Pre-allocated MDL_lock object for GLOBAL namespace. */
|
||||
MDL_lock *m_global_lock;
|
||||
/** Pre-allocated MDL_lock object for COMMIT namespace. */
|
||||
MDL_lock *m_commit_lock;
|
||||
};
|
||||
|
||||
|
||||
@ -354,18 +357,6 @@ public:
|
||||
|
||||
inline static MDL_lock *create(const MDL_key *key);
|
||||
|
||||
void notify_shared_locks(MDL_context *ctx)
|
||||
{
|
||||
Ticket_iterator it(m_granted);
|
||||
MDL_ticket *conflicting_ticket;
|
||||
|
||||
while ((conflicting_ticket= it++))
|
||||
{
|
||||
if (conflicting_ticket->get_ctx() != ctx)
|
||||
notify_shared_lock(ctx->get_thd(), conflicting_ticket);
|
||||
}
|
||||
}
|
||||
|
||||
void reschedule_waiters();
|
||||
|
||||
void remove_ticket(Ticket_list MDL_lock::*queue, MDL_ticket *ticket);
|
||||
@ -373,6 +364,9 @@ public:
|
||||
bool visit_subgraph(MDL_ticket *waiting_ticket,
|
||||
MDL_wait_for_graph_visitor *gvisitor);
|
||||
|
||||
virtual bool needs_notification(const MDL_ticket *ticket) const = 0;
|
||||
virtual void notify_conflicting_locks(MDL_context *ctx) = 0;
|
||||
|
||||
/** List of granted tickets for this lock. */
|
||||
Ticket_list m_granted;
|
||||
/** Tickets for contexts waiting to acquire a lock. */
|
||||
@ -442,6 +436,11 @@ public:
|
||||
{
|
||||
return m_waiting_incompatible;
|
||||
}
|
||||
virtual bool needs_notification(const MDL_ticket *ticket) const
|
||||
{
|
||||
return (ticket->get_type() == MDL_SHARED);
|
||||
}
|
||||
virtual void notify_conflicting_locks(MDL_context *ctx);
|
||||
|
||||
private:
|
||||
static const bitmap_t m_granted_incompatible[MDL_TYPE_END];
|
||||
@ -469,6 +468,11 @@ public:
|
||||
{
|
||||
return m_waiting_incompatible;
|
||||
}
|
||||
virtual bool needs_notification(const MDL_ticket *ticket) const
|
||||
{
|
||||
return ticket->is_upgradable_or_exclusive();
|
||||
}
|
||||
virtual void notify_conflicting_locks(MDL_context *ctx);
|
||||
|
||||
private:
|
||||
static const bitmap_t m_granted_incompatible[MDL_TYPE_END];
|
||||
@ -536,9 +540,14 @@ void mdl_destroy()
|
||||
|
||||
void MDL_map::init()
|
||||
{
|
||||
MDL_key global_lock_key(MDL_key::GLOBAL, "", "");
|
||||
MDL_key commit_lock_key(MDL_key::COMMIT, "", "");
|
||||
|
||||
mysql_mutex_init(key_MDL_map_mutex, &m_mutex, NULL);
|
||||
my_hash_init(&m_locks, &my_charset_bin, 16 /* FIXME */, 0, 0,
|
||||
mdl_locks_key, 0, 0);
|
||||
m_global_lock= MDL_lock::create(&global_lock_key);
|
||||
m_commit_lock= MDL_lock::create(&commit_lock_key);
|
||||
}
|
||||
|
||||
|
||||
@ -552,6 +561,8 @@ void MDL_map::destroy()
|
||||
DBUG_ASSERT(!m_locks.records);
|
||||
mysql_mutex_destroy(&m_mutex);
|
||||
my_hash_free(&m_locks);
|
||||
MDL_lock::destroy(m_global_lock);
|
||||
MDL_lock::destroy(m_commit_lock);
|
||||
}
|
||||
|
||||
|
||||
@ -569,6 +580,29 @@ MDL_lock* MDL_map::find_or_insert(const MDL_key *mdl_key)
|
||||
MDL_lock *lock;
|
||||
my_hash_value_type hash_value;
|
||||
|
||||
if (mdl_key->mdl_namespace() == MDL_key::GLOBAL ||
|
||||
mdl_key->mdl_namespace() == MDL_key::COMMIT)
|
||||
{
|
||||
/*
|
||||
Avoid locking m_mutex when lock for GLOBAL or COMMIT namespace is
|
||||
requested. Return pointer to pre-allocated MDL_lock instance instead.
|
||||
Such an optimization allows to save one mutex lock/unlock for any
|
||||
statement changing data.
|
||||
|
||||
It works since these namespaces contain only one element so keys
|
||||
for them look like '<namespace-id>\0\0'.
|
||||
*/
|
||||
DBUG_ASSERT(mdl_key->length() == 3);
|
||||
|
||||
lock= (mdl_key->mdl_namespace() == MDL_key::GLOBAL) ? m_global_lock :
|
||||
m_commit_lock;
|
||||
|
||||
mysql_prlock_wrlock(&lock->m_rwlock);
|
||||
|
||||
return lock;
|
||||
}
|
||||
|
||||
|
||||
hash_value= my_calc_hash(&m_locks, mdl_key->ptr(), mdl_key->length());
|
||||
|
||||
retry:
|
||||
@ -594,39 +628,6 @@ retry:
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Find MDL_lock object corresponding to the key.
|
||||
|
||||
@retval non-NULL - MDL_lock instance for the key with locked
|
||||
MDL_lock::m_rwlock.
|
||||
@retval NULL - There was no MDL_lock for the key.
|
||||
*/
|
||||
|
||||
MDL_lock* MDL_map::find(const MDL_key *mdl_key)
|
||||
{
|
||||
MDL_lock *lock;
|
||||
my_hash_value_type hash_value;
|
||||
|
||||
hash_value= my_calc_hash(&m_locks, mdl_key->ptr(), mdl_key->length());
|
||||
|
||||
retry:
|
||||
mysql_mutex_lock(&m_mutex);
|
||||
if (!(lock= (MDL_lock*) my_hash_search_using_hash_value(&m_locks,
|
||||
hash_value,
|
||||
mdl_key->ptr(),
|
||||
mdl_key->length())))
|
||||
{
|
||||
mysql_mutex_unlock(&m_mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (move_from_hash_to_lock_mutex(lock))
|
||||
goto retry;
|
||||
|
||||
return lock;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Release mdl_locks.m_mutex mutex and lock MDL_lock::m_rwlock for lock
|
||||
object from the hash. Handle situation when object was released
|
||||
@ -684,6 +685,17 @@ void MDL_map::remove(MDL_lock *lock)
|
||||
{
|
||||
uint ref_usage, ref_release;
|
||||
|
||||
if (lock->key.mdl_namespace() == MDL_key::GLOBAL ||
|
||||
lock->key.mdl_namespace() == MDL_key::COMMIT)
|
||||
{
|
||||
/*
|
||||
Never destroy pre-allocated MDL_lock objects for GLOBAL and
|
||||
COMMIT namespaces.
|
||||
*/
|
||||
mysql_prlock_unlock(&lock->m_rwlock);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Destroy the MDL_lock object, but ensure that anyone that is
|
||||
holding a reference to the object is not remaining, if so he
|
||||
@ -719,8 +731,7 @@ void MDL_map::remove(MDL_lock *lock)
|
||||
*/
|
||||
|
||||
MDL_context::MDL_context()
|
||||
:m_trans_sentinel(NULL),
|
||||
m_thd(NULL),
|
||||
: m_thd(NULL),
|
||||
m_needs_thr_lock_abort(FALSE),
|
||||
m_waiting_for(NULL)
|
||||
{
|
||||
@ -742,7 +753,9 @@ MDL_context::MDL_context()
|
||||
|
||||
void MDL_context::destroy()
|
||||
{
|
||||
DBUG_ASSERT(m_tickets.is_empty());
|
||||
DBUG_ASSERT(m_tickets[MDL_STATEMENT].is_empty() &&
|
||||
m_tickets[MDL_TRANSACTION].is_empty() &&
|
||||
m_tickets[MDL_EXPLICIT].is_empty());
|
||||
|
||||
mysql_prlock_destroy(&m_LOCK_waiting_for);
|
||||
}
|
||||
@ -771,10 +784,12 @@ void MDL_context::destroy()
|
||||
void MDL_request::init(MDL_key::enum_mdl_namespace mdl_namespace,
|
||||
const char *db_arg,
|
||||
const char *name_arg,
|
||||
enum enum_mdl_type mdl_type_arg)
|
||||
enum_mdl_type mdl_type_arg,
|
||||
enum_mdl_duration mdl_duration_arg)
|
||||
{
|
||||
key.mdl_key_init(mdl_namespace, db_arg, name_arg);
|
||||
type= mdl_type_arg;
|
||||
duration= mdl_duration_arg;
|
||||
ticket= NULL;
|
||||
}
|
||||
|
||||
@ -789,48 +804,16 @@ void MDL_request::init(MDL_key::enum_mdl_namespace mdl_namespace,
|
||||
*/
|
||||
|
||||
void MDL_request::init(const MDL_key *key_arg,
|
||||
enum enum_mdl_type mdl_type_arg)
|
||||
enum_mdl_type mdl_type_arg,
|
||||
enum_mdl_duration mdl_duration_arg)
|
||||
{
|
||||
key.mdl_key_init(key_arg);
|
||||
type= mdl_type_arg;
|
||||
duration= mdl_duration_arg;
|
||||
ticket= NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Allocate and initialize one lock request.
|
||||
|
||||
Same as mdl_init_lock(), but allocates the lock and the key buffer
|
||||
on a memory root. Necessary to lock ad-hoc tables, e.g.
|
||||
mysql.* tables of grant and data dictionary subsystems.
|
||||
|
||||
@param mdl_namespace Id of namespace of object to be locked
|
||||
@param db Name of database to which object belongs
|
||||
@param name Name of of object
|
||||
@param root MEM_ROOT on which object should be allocated
|
||||
|
||||
@note The allocated lock request will have MDL_SHARED type.
|
||||
|
||||
@retval 0 Error if out of memory
|
||||
@retval non-0 Pointer to an object representing a lock request
|
||||
*/
|
||||
|
||||
MDL_request *
|
||||
MDL_request::create(MDL_key::enum_mdl_namespace mdl_namespace, const char *db,
|
||||
const char *name, enum_mdl_type mdl_type,
|
||||
MEM_ROOT *root)
|
||||
{
|
||||
MDL_request *mdl_request;
|
||||
|
||||
if (!(mdl_request= (MDL_request*) alloc_root(root, sizeof(MDL_request))))
|
||||
return NULL;
|
||||
|
||||
mdl_request->init(mdl_namespace, db, name, mdl_type);
|
||||
|
||||
return mdl_request;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Auxiliary functions needed for creation/destruction of MDL_lock objects.
|
||||
|
||||
@ -846,6 +829,7 @@ inline MDL_lock *MDL_lock::create(const MDL_key *mdl_key)
|
||||
{
|
||||
case MDL_key::GLOBAL:
|
||||
case MDL_key::SCHEMA:
|
||||
case MDL_key::COMMIT:
|
||||
return new MDL_scoped_lock(mdl_key);
|
||||
default:
|
||||
return new MDL_object_lock(mdl_key);
|
||||
@ -867,9 +851,17 @@ void MDL_lock::destroy(MDL_lock *lock)
|
||||
on memory allocation by reusing released objects.
|
||||
*/
|
||||
|
||||
MDL_ticket *MDL_ticket::create(MDL_context *ctx_arg, enum_mdl_type type_arg)
|
||||
MDL_ticket *MDL_ticket::create(MDL_context *ctx_arg, enum_mdl_type type_arg
|
||||
#ifndef DBUG_OFF
|
||||
, enum_mdl_duration duration_arg
|
||||
#endif
|
||||
)
|
||||
{
|
||||
return new MDL_ticket(ctx_arg, type_arg);
|
||||
return new MDL_ticket(ctx_arg, type_arg
|
||||
#ifndef DBUG_OFF
|
||||
, duration_arg
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1438,13 +1430,11 @@ bool MDL_ticket::is_incompatible_when_waiting(enum_mdl_type type) const
|
||||
/**
|
||||
Check whether the context already holds a compatible lock ticket
|
||||
on an object.
|
||||
Start searching the transactional locks. If not
|
||||
found in the list of transactional locks, look at LOCK TABLES
|
||||
and HANDLER locks.
|
||||
Start searching from list of locks for the same duration as lock
|
||||
being requested. If not look at lists for other durations.
|
||||
|
||||
@param mdl_request Lock request object for lock to be acquired
|
||||
@param[out] is_transactional FALSE if we pass beyond m_trans_sentinel
|
||||
while searching for ticket, otherwise TRUE.
|
||||
@param[out] result_duration Duration of lock which was found.
|
||||
|
||||
@note Tickets which correspond to lock types "stronger" than one
|
||||
being requested are also considered compatible.
|
||||
@ -1454,24 +1444,28 @@ bool MDL_ticket::is_incompatible_when_waiting(enum_mdl_type type) const
|
||||
|
||||
MDL_ticket *
|
||||
MDL_context::find_ticket(MDL_request *mdl_request,
|
||||
bool *is_transactional)
|
||||
enum_mdl_duration *result_duration)
|
||||
{
|
||||
MDL_ticket *ticket;
|
||||
Ticket_iterator it(m_tickets);
|
||||
int i;
|
||||
|
||||
*is_transactional= TRUE;
|
||||
|
||||
while ((ticket= it++))
|
||||
for (i= 0; i < MDL_DURATION_END; i++)
|
||||
{
|
||||
if (ticket == m_trans_sentinel)
|
||||
*is_transactional= FALSE;
|
||||
enum_mdl_duration duration= (enum_mdl_duration)((mdl_request->duration+i) %
|
||||
MDL_DURATION_END);
|
||||
Ticket_iterator it(m_tickets[duration]);
|
||||
|
||||
if (mdl_request->key.is_equal(&ticket->m_lock->key) &&
|
||||
ticket->has_stronger_or_equal_type(mdl_request->type))
|
||||
break;
|
||||
while ((ticket= it++))
|
||||
{
|
||||
if (mdl_request->key.is_equal(&ticket->m_lock->key) &&
|
||||
ticket->has_stronger_or_equal_type(mdl_request->type))
|
||||
{
|
||||
*result_duration= duration;
|
||||
return ticket;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ticket;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
@ -1549,7 +1543,7 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request,
|
||||
MDL_lock *lock;
|
||||
MDL_key *key= &mdl_request->key;
|
||||
MDL_ticket *ticket;
|
||||
bool is_transactional;
|
||||
enum_mdl_duration found_duration;
|
||||
|
||||
DBUG_ASSERT(mdl_request->type != MDL_EXCLUSIVE ||
|
||||
is_lock_owner(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE));
|
||||
@ -1563,7 +1557,7 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request,
|
||||
Check whether the context already holds a shared lock on the object,
|
||||
and if so, grant the request.
|
||||
*/
|
||||
if ((ticket= find_ticket(mdl_request, &is_transactional)))
|
||||
if ((ticket= find_ticket(mdl_request, &found_duration)))
|
||||
{
|
||||
DBUG_ASSERT(ticket->m_lock);
|
||||
DBUG_ASSERT(ticket->has_stronger_or_equal_type(mdl_request->type));
|
||||
@ -1585,7 +1579,9 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request,
|
||||
a different alias.
|
||||
*/
|
||||
mdl_request->ticket= ticket;
|
||||
if (!is_transactional && clone_ticket(mdl_request))
|
||||
if ((found_duration != mdl_request->duration ||
|
||||
mdl_request->duration == MDL_EXPLICIT) &&
|
||||
clone_ticket(mdl_request))
|
||||
{
|
||||
/* Clone failed. */
|
||||
mdl_request->ticket= NULL;
|
||||
@ -1594,7 +1590,11 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!(ticket= MDL_ticket::create(this, mdl_request->type)))
|
||||
if (!(ticket= MDL_ticket::create(this, mdl_request->type
|
||||
#ifndef DBUG_OFF
|
||||
, mdl_request->duration
|
||||
#endif
|
||||
)))
|
||||
return TRUE;
|
||||
|
||||
/* The below call implicitly locks MDL_lock::m_rwlock on success. */
|
||||
@ -1612,7 +1612,7 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request,
|
||||
|
||||
mysql_prlock_unlock(&lock->m_rwlock);
|
||||
|
||||
m_tickets.push_front(ticket);
|
||||
m_tickets[mdl_request->duration].push_front(ticket);
|
||||
|
||||
mdl_request->ticket= ticket;
|
||||
}
|
||||
@ -1647,7 +1647,11 @@ MDL_context::clone_ticket(MDL_request *mdl_request)
|
||||
we effectively downgrade the cloned lock to the level of
|
||||
the request.
|
||||
*/
|
||||
if (!(ticket= MDL_ticket::create(this, mdl_request->type)))
|
||||
if (!(ticket= MDL_ticket::create(this, mdl_request->type
|
||||
#ifndef DBUG_OFF
|
||||
, mdl_request->duration
|
||||
#endif
|
||||
)))
|
||||
return TRUE;
|
||||
|
||||
/* clone() is not supposed to be used to get a stronger lock. */
|
||||
@ -1660,36 +1664,74 @@ MDL_context::clone_ticket(MDL_request *mdl_request)
|
||||
ticket->m_lock->m_granted.add_ticket(ticket);
|
||||
mysql_prlock_unlock(&ticket->m_lock->m_rwlock);
|
||||
|
||||
m_tickets.push_front(ticket);
|
||||
m_tickets[mdl_request->duration].push_front(ticket);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Notify a thread holding a shared metadata lock which
|
||||
conflicts with a pending exclusive lock.
|
||||
Notify threads holding a shared metadata locks on object which
|
||||
conflict with a pending X, SNW or SNRW lock.
|
||||
|
||||
@param thd Current thread context
|
||||
@param conflicting_ticket Conflicting metadata lock
|
||||
@param ctx MDL_context for current thread.
|
||||
*/
|
||||
|
||||
void notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket)
|
||||
void MDL_object_lock::notify_conflicting_locks(MDL_context *ctx)
|
||||
{
|
||||
/* Only try to abort locks on which we back off. */
|
||||
if (conflicting_ticket->get_type() < MDL_SHARED_NO_WRITE)
|
||||
{
|
||||
MDL_context *conflicting_ctx= conflicting_ticket->get_ctx();
|
||||
THD *conflicting_thd= conflicting_ctx->get_thd();
|
||||
DBUG_ASSERT(thd != conflicting_thd); /* Self-deadlock */
|
||||
Ticket_iterator it(m_granted);
|
||||
MDL_ticket *conflicting_ticket;
|
||||
|
||||
/*
|
||||
If thread which holds conflicting lock is waiting on table-level
|
||||
lock or some other non-MDL resource we might need to wake it up
|
||||
by calling code outside of MDL.
|
||||
*/
|
||||
mysql_notify_thread_having_shared_lock(thd, conflicting_thd,
|
||||
conflicting_ctx->get_needs_thr_lock_abort());
|
||||
while ((conflicting_ticket= it++))
|
||||
{
|
||||
/* Only try to abort locks on which we back off. */
|
||||
if (conflicting_ticket->get_ctx() != ctx &&
|
||||
conflicting_ticket->get_type() < MDL_SHARED_NO_WRITE)
|
||||
|
||||
{
|
||||
MDL_context *conflicting_ctx= conflicting_ticket->get_ctx();
|
||||
|
||||
/*
|
||||
If thread which holds conflicting lock is waiting on table-level
|
||||
lock or some other non-MDL resource we might need to wake it up
|
||||
by calling code outside of MDL.
|
||||
*/
|
||||
mysql_notify_thread_having_shared_lock(ctx->get_thd(),
|
||||
conflicting_ctx->get_thd(),
|
||||
conflicting_ctx->get_needs_thr_lock_abort());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Notify threads holding scoped IX locks which conflict with a pending S lock.
|
||||
|
||||
@param ctx MDL_context for current thread.
|
||||
*/
|
||||
|
||||
void MDL_scoped_lock::notify_conflicting_locks(MDL_context *ctx)
|
||||
{
|
||||
Ticket_iterator it(m_granted);
|
||||
MDL_ticket *conflicting_ticket;
|
||||
|
||||
while ((conflicting_ticket= it++))
|
||||
{
|
||||
if (conflicting_ticket->get_ctx() != ctx &&
|
||||
conflicting_ticket->get_type() == MDL_INTENTION_EXCLUSIVE)
|
||||
|
||||
{
|
||||
MDL_context *conflicting_ctx= conflicting_ticket->get_ctx();
|
||||
|
||||
/*
|
||||
Thread which holds global IX lock can be a handler thread for
|
||||
insert delayed. We need to kill such threads in order to get
|
||||
global shared lock. We do this my calling code outside of MDL.
|
||||
*/
|
||||
mysql_notify_thread_having_shared_lock(ctx->get_thd(),
|
||||
conflicting_ctx->get_thd(),
|
||||
conflicting_ctx->get_needs_thr_lock_abort());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1747,8 +1789,8 @@ MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout)
|
||||
*/
|
||||
m_wait.reset_status();
|
||||
|
||||
if (ticket->is_upgradable_or_exclusive())
|
||||
lock->notify_shared_locks(this);
|
||||
if (lock->needs_notification(ticket))
|
||||
lock->notify_conflicting_locks(this);
|
||||
|
||||
mysql_prlock_unlock(&lock->m_rwlock);
|
||||
|
||||
@ -1759,7 +1801,7 @@ MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout)
|
||||
|
||||
find_deadlock();
|
||||
|
||||
if (ticket->is_upgradable_or_exclusive())
|
||||
if (lock->needs_notification(ticket))
|
||||
{
|
||||
struct timespec abs_shortwait;
|
||||
set_timespec(abs_shortwait, 1);
|
||||
@ -1775,7 +1817,7 @@ MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout)
|
||||
break;
|
||||
|
||||
mysql_prlock_wrlock(&lock->m_rwlock);
|
||||
lock->notify_shared_locks(this);
|
||||
lock->notify_conflicting_locks(this);
|
||||
mysql_prlock_unlock(&lock->m_rwlock);
|
||||
set_timespec(abs_shortwait, 1);
|
||||
}
|
||||
@ -1818,7 +1860,7 @@ MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout)
|
||||
*/
|
||||
DBUG_ASSERT(wait_status == MDL_wait::GRANTED);
|
||||
|
||||
m_tickets.push_front(ticket);
|
||||
m_tickets[mdl_request->duration].push_front(ticket);
|
||||
|
||||
mdl_request->ticket= ticket;
|
||||
|
||||
@ -1859,7 +1901,7 @@ bool MDL_context::acquire_locks(MDL_request_list *mdl_requests,
|
||||
{
|
||||
MDL_request_list::Iterator it(*mdl_requests);
|
||||
MDL_request **sort_buf, **p_req;
|
||||
MDL_ticket *mdl_svp= mdl_savepoint();
|
||||
MDL_savepoint mdl_svp= mdl_savepoint();
|
||||
ssize_t req_count= static_cast<ssize_t>(mdl_requests->elements());
|
||||
|
||||
if (req_count == 0)
|
||||
@ -1928,7 +1970,7 @@ MDL_context::upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket,
|
||||
ulong lock_wait_timeout)
|
||||
{
|
||||
MDL_request mdl_xlock_request;
|
||||
MDL_ticket *mdl_svp= mdl_savepoint();
|
||||
MDL_savepoint mdl_svp= mdl_savepoint();
|
||||
bool is_new_ticket;
|
||||
|
||||
DBUG_ENTER("MDL_ticket::upgrade_shared_lock_to_exclusive");
|
||||
@ -1945,7 +1987,8 @@ MDL_context::upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket,
|
||||
DBUG_ASSERT(mdl_ticket->m_type == MDL_SHARED_NO_WRITE ||
|
||||
mdl_ticket->m_type == MDL_SHARED_NO_READ_WRITE);
|
||||
|
||||
mdl_xlock_request.init(&mdl_ticket->m_lock->key, MDL_EXCLUSIVE);
|
||||
mdl_xlock_request.init(&mdl_ticket->m_lock->key, MDL_EXCLUSIVE,
|
||||
MDL_TRANSACTION);
|
||||
|
||||
if (acquire_lock(&mdl_xlock_request, lock_wait_timeout))
|
||||
DBUG_RETURN(TRUE);
|
||||
@ -1969,7 +2012,7 @@ MDL_context::upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket,
|
||||
|
||||
if (is_new_ticket)
|
||||
{
|
||||
m_tickets.remove(mdl_xlock_request.ticket);
|
||||
m_tickets[MDL_TRANSACTION].remove(mdl_xlock_request.ticket);
|
||||
MDL_ticket::destroy(mdl_xlock_request.ticket);
|
||||
}
|
||||
|
||||
@ -2230,10 +2273,12 @@ void MDL_context::find_deadlock()
|
||||
/**
|
||||
Release lock.
|
||||
|
||||
@param ticket Ticket for lock to be released.
|
||||
@param duration Lock duration.
|
||||
@param ticket Ticket for lock to be released.
|
||||
|
||||
*/
|
||||
|
||||
void MDL_context::release_lock(MDL_ticket *ticket)
|
||||
void MDL_context::release_lock(enum_mdl_duration duration, MDL_ticket *ticket)
|
||||
{
|
||||
MDL_lock *lock= ticket->m_lock;
|
||||
DBUG_ENTER("MDL_context::release_lock");
|
||||
@ -2243,63 +2288,66 @@ void MDL_context::release_lock(MDL_ticket *ticket)
|
||||
DBUG_ASSERT(this == ticket->get_ctx());
|
||||
mysql_mutex_assert_not_owner(&LOCK_open);
|
||||
|
||||
if (ticket == m_trans_sentinel)
|
||||
m_trans_sentinel= ++Ticket_list::Iterator(m_tickets, ticket);
|
||||
|
||||
lock->remove_ticket(&MDL_lock::m_granted, ticket);
|
||||
|
||||
m_tickets.remove(ticket);
|
||||
m_tickets[duration].remove(ticket);
|
||||
MDL_ticket::destroy(ticket);
|
||||
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Release lock with explicit duration.
|
||||
|
||||
@param ticket Ticket for lock to be released.
|
||||
|
||||
*/
|
||||
|
||||
void MDL_context::release_lock(MDL_ticket *ticket)
|
||||
{
|
||||
DBUG_ASSERT(ticket->m_duration == MDL_EXPLICIT);
|
||||
|
||||
release_lock(MDL_EXPLICIT, ticket);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Release all locks associated with the context. If the sentinel
|
||||
is not NULL, do not release locks stored in the list after and
|
||||
including the sentinel.
|
||||
|
||||
Transactional locks are added to the beginning of the list, i.e.
|
||||
stored in reverse temporal order. This allows to employ this
|
||||
function to:
|
||||
Statement and transactional locks are added to the beginning of
|
||||
the corresponding lists, i.e. stored in reverse temporal order.
|
||||
This allows to employ this function to:
|
||||
- back off in case of a lock conflict.
|
||||
- release all locks in the end of a transaction
|
||||
- release all locks in the end of a statment or transaction
|
||||
- rollback to a savepoint.
|
||||
|
||||
The sentinel semantics is used to support LOCK TABLES
|
||||
mode and HANDLER statements: locks taken by these statements
|
||||
survive COMMIT, ROLLBACK, ROLLBACK TO SAVEPOINT.
|
||||
*/
|
||||
|
||||
void MDL_context::release_locks_stored_before(MDL_ticket *sentinel)
|
||||
void MDL_context::release_locks_stored_before(enum_mdl_duration duration,
|
||||
MDL_ticket *sentinel)
|
||||
{
|
||||
MDL_ticket *ticket;
|
||||
Ticket_iterator it(m_tickets);
|
||||
Ticket_iterator it(m_tickets[duration]);
|
||||
DBUG_ENTER("MDL_context::release_locks_stored_before");
|
||||
|
||||
if (m_tickets.is_empty())
|
||||
if (m_tickets[duration].is_empty())
|
||||
DBUG_VOID_RETURN;
|
||||
|
||||
while ((ticket= it++) && ticket != sentinel)
|
||||
{
|
||||
DBUG_PRINT("info", ("found lock to release ticket=%p", ticket));
|
||||
release_lock(ticket);
|
||||
release_lock(duration, ticket);
|
||||
}
|
||||
/*
|
||||
If all locks were released, then the sentinel was not present
|
||||
in the list. It must never happen because the sentinel was
|
||||
bogus, i.e. pointed to a ticket that no longer exists.
|
||||
*/
|
||||
DBUG_ASSERT(! m_tickets.is_empty() || sentinel == NULL);
|
||||
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Release all locks in the context which correspond to the same name/
|
||||
object as this lock request.
|
||||
Release all explicit locks in the context which correspond to the
|
||||
same name/object as this lock request.
|
||||
|
||||
@param ticket One of the locks for the name/object for which all
|
||||
locks should be released.
|
||||
@ -2312,17 +2360,13 @@ void MDL_context::release_all_locks_for_name(MDL_ticket *name)
|
||||
|
||||
/* Remove matching lock tickets from the context. */
|
||||
MDL_ticket *ticket;
|
||||
Ticket_iterator it_ticket(m_tickets);
|
||||
Ticket_iterator it_ticket(m_tickets[MDL_EXPLICIT]);
|
||||
|
||||
while ((ticket= it_ticket++))
|
||||
{
|
||||
DBUG_ASSERT(ticket->m_lock);
|
||||
/*
|
||||
We rarely have more than one ticket in this loop,
|
||||
let's not bother saving on pthread_cond_broadcast().
|
||||
*/
|
||||
if (ticket->m_lock == lock)
|
||||
release_lock(ticket);
|
||||
release_lock(MDL_EXPLICIT, ticket);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2377,9 +2421,10 @@ MDL_context::is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace,
|
||||
enum_mdl_type mdl_type)
|
||||
{
|
||||
MDL_request mdl_request;
|
||||
bool is_transactional_unused;
|
||||
mdl_request.init(mdl_namespace, db, name, mdl_type);
|
||||
MDL_ticket *ticket= find_ticket(&mdl_request, &is_transactional_unused);
|
||||
enum_mdl_duration not_unused;
|
||||
/* We don't care about exact duration of lock here. */
|
||||
mdl_request.init(mdl_namespace, db, name, mdl_type, MDL_TRANSACTION);
|
||||
MDL_ticket *ticket= find_ticket(&mdl_request, ¬_unused);
|
||||
|
||||
DBUG_ASSERT(ticket == NULL || ticket->m_lock);
|
||||
|
||||
@ -2408,18 +2453,15 @@ bool MDL_ticket::has_pending_conflicting_lock() const
|
||||
@note It's safe to iterate and unlock any locks after taken after this
|
||||
savepoint because other statements that take other special locks
|
||||
cause a implicit commit (ie LOCK TABLES).
|
||||
|
||||
@param mdl_savepont The last acquired MDL lock when the
|
||||
savepoint was set.
|
||||
*/
|
||||
|
||||
void MDL_context::rollback_to_savepoint(MDL_ticket *mdl_savepoint)
|
||||
void MDL_context::rollback_to_savepoint(const MDL_savepoint &mdl_savepoint)
|
||||
{
|
||||
DBUG_ENTER("MDL_context::rollback_to_savepoint");
|
||||
|
||||
/* If savepoint is NULL, it is from the start of the transaction. */
|
||||
release_locks_stored_before(mdl_savepoint ?
|
||||
mdl_savepoint : m_trans_sentinel);
|
||||
release_locks_stored_before(MDL_STATEMENT, mdl_savepoint.m_stmt_ticket);
|
||||
release_locks_stored_before(MDL_TRANSACTION, mdl_savepoint.m_trans_ticket);
|
||||
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
@ -2437,65 +2479,150 @@ void MDL_context::rollback_to_savepoint(MDL_ticket *mdl_savepoint)
|
||||
void MDL_context::release_transactional_locks()
|
||||
{
|
||||
DBUG_ENTER("MDL_context::release_transactional_locks");
|
||||
release_locks_stored_before(m_trans_sentinel);
|
||||
release_locks_stored_before(MDL_STATEMENT, NULL);
|
||||
release_locks_stored_before(MDL_TRANSACTION, NULL);
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
|
||||
void MDL_context::release_statement_locks()
|
||||
{
|
||||
DBUG_ENTER("MDL_context::release_transactional_locks");
|
||||
release_locks_stored_before(MDL_STATEMENT, NULL);
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Does this savepoint have this lock?
|
||||
|
||||
@retval TRUE The ticket is older than the savepoint and
|
||||
is not LT, HA or GLR ticket. Thus it belongs
|
||||
to the savepoint.
|
||||
@retval FALSE The ticket is newer than the savepoint
|
||||
or is an LT, HA or GLR ticket.
|
||||
|
||||
@retval TRUE The ticket is older than the savepoint or
|
||||
is an LT, HA or GLR ticket. Thus it belongs
|
||||
to the savepoint or has explicit duration.
|
||||
@retval FALSE The ticket is newer than the savepoint.
|
||||
and is not an LT, HA or GLR ticket.
|
||||
*/
|
||||
|
||||
bool MDL_context::has_lock(MDL_ticket *mdl_savepoint,
|
||||
bool MDL_context::has_lock(const MDL_savepoint &mdl_savepoint,
|
||||
MDL_ticket *mdl_ticket)
|
||||
{
|
||||
MDL_ticket *ticket;
|
||||
/* Start from the beginning, most likely mdl_ticket's been just acquired. */
|
||||
MDL_context::Ticket_iterator it(m_tickets);
|
||||
bool found_savepoint= FALSE;
|
||||
MDL_context::Ticket_iterator s_it(m_tickets[MDL_STATEMENT]);
|
||||
MDL_context::Ticket_iterator t_it(m_tickets[MDL_TRANSACTION]);
|
||||
|
||||
while ((ticket= it++) && ticket != m_trans_sentinel)
|
||||
while ((ticket= s_it++) && ticket != mdl_savepoint.m_stmt_ticket)
|
||||
{
|
||||
/*
|
||||
First met the savepoint. The ticket must be
|
||||
somewhere after it.
|
||||
*/
|
||||
if (ticket == mdl_savepoint)
|
||||
found_savepoint= TRUE;
|
||||
/*
|
||||
Met the ticket. If we haven't yet met the savepoint,
|
||||
the ticket is newer than the savepoint.
|
||||
*/
|
||||
if (ticket == mdl_ticket)
|
||||
return found_savepoint;
|
||||
return FALSE;
|
||||
}
|
||||
/* Reached m_trans_sentinel. The ticket must be LT, HA or GRL ticket. */
|
||||
return FALSE;
|
||||
|
||||
while ((ticket= t_it++) && ticket != mdl_savepoint.m_trans_ticket)
|
||||
{
|
||||
if (ticket == mdl_ticket)
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Rearrange the ticket to reside in the part of the list that's
|
||||
beyond m_trans_sentinel. This effectively changes the ticket
|
||||
life cycle, from automatic to manual: i.e. the ticket is no
|
||||
longer released by MDL_context::release_transactional_locks() or
|
||||
MDL_context::rollback_to_savepoint(), it must be released manually.
|
||||
Change lock duration for transactional lock.
|
||||
|
||||
@param ticket Ticket representing lock.
|
||||
@param duration Lock duration to be set.
|
||||
|
||||
@note This method only supports changing duration of
|
||||
transactional lock to some other duration.
|
||||
*/
|
||||
|
||||
void MDL_context::move_ticket_after_trans_sentinel(MDL_ticket *mdl_ticket)
|
||||
void MDL_context::set_lock_duration(MDL_ticket *mdl_ticket,
|
||||
enum_mdl_duration duration)
|
||||
{
|
||||
m_tickets.remove(mdl_ticket);
|
||||
if (m_trans_sentinel == NULL)
|
||||
{
|
||||
m_trans_sentinel= mdl_ticket;
|
||||
m_tickets.push_back(mdl_ticket);
|
||||
}
|
||||
else
|
||||
m_tickets.insert_after(m_trans_sentinel, mdl_ticket);
|
||||
DBUG_ASSERT(mdl_ticket->m_duration == MDL_TRANSACTION &&
|
||||
duration != MDL_TRANSACTION);
|
||||
|
||||
m_tickets[MDL_TRANSACTION].remove(mdl_ticket);
|
||||
m_tickets[duration].push_front(mdl_ticket);
|
||||
#ifndef DBUG_OFF
|
||||
mdl_ticket->m_duration= duration;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Set explicit duration for all locks in the context.
|
||||
*/
|
||||
|
||||
void MDL_context::set_explicit_duration_for_all_locks()
|
||||
{
|
||||
int i;
|
||||
MDL_ticket *ticket;
|
||||
|
||||
/*
|
||||
In the most common case when this function is called list
|
||||
of transactional locks is bigger than list of locks with
|
||||
explicit duration. So we start by swapping these two lists
|
||||
and then move elements from new list of transactional
|
||||
locks and list of statement locks to list of locks with
|
||||
explicit duration.
|
||||
*/
|
||||
|
||||
m_tickets[MDL_EXPLICIT].swap(m_tickets[MDL_TRANSACTION]);
|
||||
|
||||
for (i= 0; i < MDL_EXPLICIT; i++)
|
||||
{
|
||||
Ticket_iterator it_ticket(m_tickets[i]);
|
||||
|
||||
while ((ticket= it_ticket++))
|
||||
{
|
||||
m_tickets[i].remove(ticket);
|
||||
m_tickets[MDL_EXPLICIT].push_front(ticket);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef DBUG_OFF
|
||||
Ticket_iterator exp_it(m_tickets[MDL_EXPLICIT]);
|
||||
|
||||
while ((ticket= exp_it++))
|
||||
ticket->m_duration= MDL_EXPLICIT;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Set transactional duration for all locks in the context.
|
||||
*/
|
||||
|
||||
void MDL_context::set_transaction_duration_for_all_locks()
|
||||
{
|
||||
MDL_ticket *ticket;
|
||||
|
||||
/*
|
||||
In the most common case when this function is called list
|
||||
of explicit locks is bigger than two other lists (in fact,
|
||||
list of statement locks is always empty). So we start by
|
||||
swapping list of explicit and transactional locks and then
|
||||
move contents of new list of explicit locks to list of
|
||||
locks with transactional duration.
|
||||
*/
|
||||
|
||||
DBUG_ASSERT(m_tickets[MDL_STATEMENT].is_empty());
|
||||
|
||||
m_tickets[MDL_TRANSACTION].swap(m_tickets[MDL_EXPLICIT]);
|
||||
|
||||
Ticket_iterator it_ticket(m_tickets[MDL_EXPLICIT]);
|
||||
|
||||
while ((ticket= it_ticket++))
|
||||
{
|
||||
m_tickets[MDL_EXPLICIT].remove(ticket);
|
||||
m_tickets[MDL_TRANSACTION].push_front(ticket);
|
||||
}
|
||||
|
||||
#ifndef DBUG_OFF
|
||||
Ticket_iterator trans_it(m_tickets[MDL_TRANSACTION]);
|
||||
|
||||
while ((ticket= trans_it++))
|
||||
ticket->m_duration= MDL_TRANSACTION;
|
||||
#endif
|
||||
}
|
||||
|
181
sql/mdl.h
181
sql/mdl.h
@ -150,6 +150,15 @@ enum enum_mdl_type {
|
||||
MDL_TYPE_END};
|
||||
|
||||
|
||||
/** Duration of metadata lock. */
|
||||
|
||||
enum enum_mdl_duration { MDL_STATEMENT= 0,
|
||||
MDL_TRANSACTION,
|
||||
MDL_EXPLICIT,
|
||||
/* This should be the last ! */
|
||||
MDL_DURATION_END };
|
||||
|
||||
|
||||
/** Maximal length of key for metadata locking subsystem. */
|
||||
#define MAX_MDLKEY_LENGTH (1 + NAME_LEN + 1 + NAME_LEN + 1)
|
||||
|
||||
@ -167,13 +176,16 @@ class MDL_key
|
||||
{
|
||||
public:
|
||||
/**
|
||||
Object namespaces
|
||||
Object namespaces.
|
||||
Sic: when adding a new member to this enum make sure to
|
||||
update m_namespace_to_wait_state_name array in mdl.cc!
|
||||
|
||||
Different types of objects exist in different namespaces
|
||||
- TABLE is for tables and views.
|
||||
- FUNCTION is for stored functions.
|
||||
- PROCEDURE is for stored procedures.
|
||||
- TRIGGER is for triggers.
|
||||
- EVENT is for event scheduler events
|
||||
Note that although there isn't metadata locking on triggers,
|
||||
it's necessary to have a separate namespace for them since
|
||||
MDL_key is also used outside of the MDL subsystem.
|
||||
@ -184,6 +196,8 @@ public:
|
||||
FUNCTION,
|
||||
PROCEDURE,
|
||||
TRIGGER,
|
||||
EVENT,
|
||||
COMMIT,
|
||||
/* This should be the last ! */
|
||||
NAMESPACE_END };
|
||||
|
||||
@ -305,6 +319,8 @@ class MDL_request
|
||||
public:
|
||||
/** Type of metadata lock. */
|
||||
enum enum_mdl_type type;
|
||||
/** Duration for requested lock. */
|
||||
enum enum_mdl_duration duration;
|
||||
|
||||
/**
|
||||
Pointers for participating in the list of lock requests for this context.
|
||||
@ -327,17 +343,16 @@ public:
|
||||
|
||||
void init(MDL_key::enum_mdl_namespace namespace_arg,
|
||||
const char *db_arg, const char *name_arg,
|
||||
enum_mdl_type mdl_type_arg);
|
||||
void init(const MDL_key *key_arg, enum_mdl_type mdl_type_arg);
|
||||
enum_mdl_type mdl_type_arg,
|
||||
enum_mdl_duration mdl_duration_arg);
|
||||
void init(const MDL_key *key_arg, enum_mdl_type mdl_type_arg,
|
||||
enum_mdl_duration mdl_duration_arg);
|
||||
/** Set type of lock request. Can be only applied to pending locks. */
|
||||
inline void set_type(enum_mdl_type type_arg)
|
||||
{
|
||||
DBUG_ASSERT(ticket == NULL);
|
||||
type= type_arg;
|
||||
}
|
||||
static MDL_request *create(MDL_key::enum_mdl_namespace mdl_namespace,
|
||||
const char *db, const char *name,
|
||||
enum_mdl_type mdl_type, MEM_ROOT *root);
|
||||
|
||||
/*
|
||||
This is to work around the ugliness of TABLE_LIST
|
||||
@ -363,6 +378,7 @@ public:
|
||||
|
||||
MDL_request(const MDL_request *rhs)
|
||||
:type(rhs->type),
|
||||
duration(rhs->duration),
|
||||
ticket(NULL),
|
||||
key(&rhs->key)
|
||||
{}
|
||||
@ -484,17 +500,35 @@ public:
|
||||
private:
|
||||
friend class MDL_context;
|
||||
|
||||
MDL_ticket(MDL_context *ctx_arg, enum_mdl_type type_arg)
|
||||
MDL_ticket(MDL_context *ctx_arg, enum_mdl_type type_arg
|
||||
#ifndef DBUG_OFF
|
||||
, enum_mdl_duration duration_arg
|
||||
#endif
|
||||
)
|
||||
: m_type(type_arg),
|
||||
#ifndef DBUG_OFF
|
||||
m_duration(duration_arg),
|
||||
#endif
|
||||
m_ctx(ctx_arg),
|
||||
m_lock(NULL)
|
||||
{}
|
||||
|
||||
static MDL_ticket *create(MDL_context *ctx_arg, enum_mdl_type type_arg);
|
||||
static MDL_ticket *create(MDL_context *ctx_arg, enum_mdl_type type_arg
|
||||
#ifndef DBUG_OFF
|
||||
, enum_mdl_duration duration_arg
|
||||
#endif
|
||||
);
|
||||
static void destroy(MDL_ticket *ticket);
|
||||
private:
|
||||
/** Type of metadata lock. Externally accessible. */
|
||||
enum enum_mdl_type m_type;
|
||||
#ifndef DBUG_OFF
|
||||
/**
|
||||
Duration of lock represented by this ticket.
|
||||
Context private. Debug-only.
|
||||
*/
|
||||
enum_mdl_duration m_duration;
|
||||
#endif
|
||||
/**
|
||||
Context of the owner of the metadata lock ticket. Externally accessible.
|
||||
*/
|
||||
@ -511,6 +545,39 @@ private:
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
Savepoint for MDL context.
|
||||
|
||||
Doesn't include metadata locks with explicit duration as
|
||||
they are not released during rollback to savepoint.
|
||||
*/
|
||||
|
||||
class MDL_savepoint
|
||||
{
|
||||
public:
|
||||
MDL_savepoint() {};
|
||||
|
||||
private:
|
||||
MDL_savepoint(MDL_ticket *stmt_ticket, MDL_ticket *trans_ticket)
|
||||
: m_stmt_ticket(stmt_ticket), m_trans_ticket(trans_ticket)
|
||||
{}
|
||||
|
||||
friend class MDL_context;
|
||||
|
||||
private:
|
||||
/**
|
||||
Pointer to last lock with statement duration which was taken
|
||||
before creation of savepoint.
|
||||
*/
|
||||
MDL_ticket *m_stmt_ticket;
|
||||
/**
|
||||
Pointer to last lock with transaction duration which was taken
|
||||
before creation of savepoint.
|
||||
*/
|
||||
MDL_ticket *m_trans_ticket;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
A reliable way to wait on an MDL lock.
|
||||
*/
|
||||
@ -559,9 +626,7 @@ public:
|
||||
typedef I_P_List<MDL_ticket,
|
||||
I_P_List_adapter<MDL_ticket,
|
||||
&MDL_ticket::next_in_context,
|
||||
&MDL_ticket::prev_in_context>,
|
||||
I_P_List_null_counter,
|
||||
I_P_List_fast_push_back<MDL_ticket> >
|
||||
&MDL_ticket::prev_in_context> >
|
||||
Ticket_list;
|
||||
|
||||
typedef Ticket_list::Iterator Ticket_iterator;
|
||||
@ -584,37 +649,28 @@ public:
|
||||
const char *db, const char *name,
|
||||
enum_mdl_type mdl_type);
|
||||
|
||||
bool has_lock(MDL_ticket *mdl_savepoint, MDL_ticket *mdl_ticket);
|
||||
bool has_lock(const MDL_savepoint &mdl_savepoint, MDL_ticket *mdl_ticket);
|
||||
|
||||
inline bool has_locks() const
|
||||
{
|
||||
return !m_tickets.is_empty();
|
||||
return !(m_tickets[MDL_STATEMENT].is_empty() &&
|
||||
m_tickets[MDL_TRANSACTION].is_empty() &&
|
||||
m_tickets[MDL_EXPLICIT].is_empty());
|
||||
}
|
||||
|
||||
MDL_ticket *mdl_savepoint()
|
||||
MDL_savepoint mdl_savepoint()
|
||||
{
|
||||
/*
|
||||
NULL savepoint represents the start of the transaction.
|
||||
Checking for m_trans_sentinel also makes sure we never
|
||||
return a pointer to HANDLER ticket as a savepoint.
|
||||
*/
|
||||
return m_tickets.front() == m_trans_sentinel ? NULL : m_tickets.front();
|
||||
return MDL_savepoint(m_tickets[MDL_STATEMENT].front(),
|
||||
m_tickets[MDL_TRANSACTION].front());
|
||||
}
|
||||
|
||||
void set_trans_sentinel()
|
||||
{
|
||||
m_trans_sentinel= m_tickets.front();
|
||||
}
|
||||
MDL_ticket *trans_sentinel() const { return m_trans_sentinel; }
|
||||
|
||||
void reset_trans_sentinel(MDL_ticket *sentinel_arg)
|
||||
{
|
||||
m_trans_sentinel= sentinel_arg;
|
||||
}
|
||||
void move_ticket_after_trans_sentinel(MDL_ticket *mdl_ticket);
|
||||
void set_explicit_duration_for_all_locks();
|
||||
void set_transaction_duration_for_all_locks();
|
||||
void set_lock_duration(MDL_ticket *mdl_ticket, enum_mdl_duration duration);
|
||||
|
||||
void release_statement_locks();
|
||||
void release_transactional_locks();
|
||||
void rollback_to_savepoint(MDL_ticket *mdl_savepoint);
|
||||
void rollback_to_savepoint(const MDL_savepoint &mdl_savepoint);
|
||||
|
||||
inline THD *get_thd() const { return m_thd; }
|
||||
|
||||
@ -655,46 +711,43 @@ public:
|
||||
MDL_wait m_wait;
|
||||
private:
|
||||
/**
|
||||
All MDL tickets acquired by this connection.
|
||||
Lists of all MDL tickets acquired by this connection.
|
||||
|
||||
The order of tickets in m_tickets list.
|
||||
---------------------------------------
|
||||
The entire set of locks acquired by a connection
|
||||
can be separated in two subsets: transactional and
|
||||
non-transactional locks.
|
||||
Lists of MDL tickets:
|
||||
---------------------
|
||||
The entire set of locks acquired by a connection can be separated
|
||||
in three subsets according to their: locks released at the end of
|
||||
statement, at the end of transaction and locks are released
|
||||
explicitly.
|
||||
|
||||
Transactional locks are locks with automatic scope. They
|
||||
are accumulated in the course of a transaction, and
|
||||
released only on COMMIT, ROLLBACK or ROLLBACK TO SAVEPOINT.
|
||||
They must not be (and never are) released manually,
|
||||
Statement and transactional locks are locks with automatic scope.
|
||||
They are accumulated in the course of a transaction, and released
|
||||
either at the end of uppermost statement (for statement locks) or
|
||||
on COMMIT, ROLLBACK or ROLLBACK TO SAVEPOINT (for transactional
|
||||
locks). They must not be (and never are) released manually,
|
||||
i.e. with release_lock() call.
|
||||
|
||||
Non-transactional locks are taken for locks that span
|
||||
Locks with explicit duration are taken for locks that span
|
||||
multiple transactions or savepoints.
|
||||
These are: HANDLER SQL locks (HANDLER SQL is
|
||||
transaction-agnostic), LOCK TABLES locks (you can COMMIT/etc
|
||||
under LOCK TABLES, and the locked tables stay locked), and
|
||||
SET GLOBAL READ_ONLY=1 global shared lock.
|
||||
locks implementing "global read lock".
|
||||
|
||||
Transactional locks are always prepended to the beginning
|
||||
of the list. In other words, they are stored in reverse
|
||||
temporal order. Thus, when we rollback to a savepoint,
|
||||
we start popping and releasing tickets from the front
|
||||
until we reach the last ticket acquired after the
|
||||
savepoint.
|
||||
Statement/transactional locks are always prepended to the
|
||||
beginning of the appropriate list. In other words, they are
|
||||
stored in reverse temporal order. Thus, when we rollback to
|
||||
a savepoint, we start popping and releasing tickets from the
|
||||
front until we reach the last ticket acquired after the savepoint.
|
||||
|
||||
Non-transactional locks are always stored after
|
||||
transactional ones, and among each other can be
|
||||
split into three sets:
|
||||
Locks with explicit duration stored are not stored in any
|
||||
particular order, and among each other can be split into
|
||||
three sets:
|
||||
|
||||
[LOCK TABLES locks] [HANDLER locks] [GLOBAL READ LOCK locks]
|
||||
|
||||
The following is known about these sets:
|
||||
|
||||
* we can never have both HANDLER and LOCK TABLES locks
|
||||
together -- HANDLER statements are prohibited under LOCK
|
||||
TABLES, entering LOCK TABLES implicitly closes all open
|
||||
HANDLERs.
|
||||
* GLOBAL READ LOCK locks are always stored after LOCK TABLES
|
||||
locks and after HANDLER locks. This is because one can't say
|
||||
SET GLOBAL read_only=1 or FLUSH TABLES WITH READ LOCK
|
||||
@ -709,14 +762,9 @@ private:
|
||||
However, one can open a few HANDLERs after entering the
|
||||
read only mode.
|
||||
* LOCK TABLES locks include intention exclusive locks on
|
||||
involved schemas.
|
||||
involved schemas and global intention exclusive lock.
|
||||
*/
|
||||
Ticket_list m_tickets;
|
||||
/**
|
||||
Separates transactional and non-transactional locks
|
||||
in m_tickets list, @sa m_tickets.
|
||||
*/
|
||||
MDL_ticket *m_trans_sentinel;
|
||||
Ticket_list m_tickets[MDL_DURATION_END];
|
||||
THD *m_thd;
|
||||
/**
|
||||
TRUE - if for this context we will break protocol and try to
|
||||
@ -747,8 +795,9 @@ private:
|
||||
MDL_wait_for_subgraph *m_waiting_for;
|
||||
private:
|
||||
MDL_ticket *find_ticket(MDL_request *mdl_req,
|
||||
bool *is_transactional);
|
||||
void release_locks_stored_before(MDL_ticket *sentinel);
|
||||
enum_mdl_duration *duration);
|
||||
void release_locks_stored_before(enum_mdl_duration duration, MDL_ticket *sentinel);
|
||||
void release_lock(enum_mdl_duration duration, MDL_ticket *ticket);
|
||||
bool try_acquire_lock_impl(MDL_request *mdl_request,
|
||||
MDL_ticket **out_ticket);
|
||||
|
||||
|
@ -604,8 +604,7 @@ pthread_key(MEM_ROOT**,THR_MALLOC);
|
||||
pthread_key(THD*, THR_THD);
|
||||
mysql_mutex_t LOCK_thread_count;
|
||||
mysql_mutex_t
|
||||
LOCK_status, LOCK_global_read_lock,
|
||||
LOCK_error_log, LOCK_uuid_generator,
|
||||
LOCK_status, LOCK_error_log, LOCK_uuid_generator,
|
||||
LOCK_delayed_insert, LOCK_delayed_status, LOCK_delayed_create,
|
||||
LOCK_crypt,
|
||||
LOCK_global_system_variables,
|
||||
@ -625,7 +624,6 @@ mysql_mutex_t LOCK_des_key_file;
|
||||
mysql_rwlock_t LOCK_grant, LOCK_sys_init_connect, LOCK_sys_init_slave;
|
||||
mysql_rwlock_t LOCK_system_variables_hash;
|
||||
mysql_cond_t COND_thread_count;
|
||||
mysql_cond_t COND_global_read_lock;
|
||||
pthread_t signal_thread;
|
||||
pthread_attr_t connection_attrib;
|
||||
mysql_mutex_t LOCK_server_started;
|
||||
@ -1542,7 +1540,6 @@ static void clean_up_mutexes()
|
||||
mysql_mutex_destroy(&LOCK_crypt);
|
||||
mysql_mutex_destroy(&LOCK_user_conn);
|
||||
mysql_mutex_destroy(&LOCK_connection_count);
|
||||
Events::destroy_mutexes();
|
||||
#ifdef HAVE_OPENSSL
|
||||
mysql_mutex_destroy(&LOCK_des_key_file);
|
||||
#ifndef HAVE_YASSL
|
||||
@ -1560,12 +1557,10 @@ static void clean_up_mutexes()
|
||||
mysql_rwlock_destroy(&LOCK_sys_init_slave);
|
||||
mysql_mutex_destroy(&LOCK_global_system_variables);
|
||||
mysql_rwlock_destroy(&LOCK_system_variables_hash);
|
||||
mysql_mutex_destroy(&LOCK_global_read_lock);
|
||||
mysql_mutex_destroy(&LOCK_uuid_generator);
|
||||
mysql_mutex_destroy(&LOCK_prepared_stmt_count);
|
||||
mysql_mutex_destroy(&LOCK_error_messages);
|
||||
mysql_cond_destroy(&COND_thread_count);
|
||||
mysql_cond_destroy(&COND_global_read_lock);
|
||||
mysql_cond_destroy(&COND_thread_cache);
|
||||
mysql_cond_destroy(&COND_flush_thread_cache);
|
||||
mysql_cond_destroy(&COND_manager);
|
||||
@ -3524,8 +3519,6 @@ static int init_thread_environment()
|
||||
&LOCK_global_system_variables, MY_MUTEX_INIT_FAST);
|
||||
mysql_rwlock_init(key_rwlock_LOCK_system_variables_hash,
|
||||
&LOCK_system_variables_hash);
|
||||
mysql_mutex_init(key_LOCK_global_read_lock,
|
||||
&LOCK_global_read_lock, MY_MUTEX_INIT_FAST);
|
||||
mysql_mutex_init(key_LOCK_prepared_stmt_count,
|
||||
&LOCK_prepared_stmt_count, MY_MUTEX_INIT_FAST);
|
||||
mysql_mutex_init(key_LOCK_error_messages,
|
||||
@ -3553,7 +3546,6 @@ static int init_thread_environment()
|
||||
mysql_rwlock_init(key_rwlock_LOCK_sys_init_slave, &LOCK_sys_init_slave);
|
||||
mysql_rwlock_init(key_rwlock_LOCK_grant, &LOCK_grant);
|
||||
mysql_cond_init(key_COND_thread_count, &COND_thread_count, NULL);
|
||||
mysql_cond_init(key_COND_global_read_lock, &COND_global_read_lock, NULL);
|
||||
mysql_cond_init(key_COND_thread_cache, &COND_thread_cache, NULL);
|
||||
mysql_cond_init(key_COND_flush_thread_cache, &COND_flush_thread_cache, NULL);
|
||||
mysql_cond_init(key_COND_manager, &COND_manager, NULL);
|
||||
@ -7669,7 +7661,7 @@ PSI_mutex_key key_BINLOG_LOCK_index, key_BINLOG_LOCK_prep_xids,
|
||||
key_delayed_insert_mutex, key_hash_filo_lock, key_LOCK_active_mi,
|
||||
key_LOCK_connection_count, key_LOCK_crypt, key_LOCK_delayed_create,
|
||||
key_LOCK_delayed_insert, key_LOCK_delayed_status, key_LOCK_error_log,
|
||||
key_LOCK_gdl, key_LOCK_global_read_lock, key_LOCK_global_system_variables,
|
||||
key_LOCK_gdl, key_LOCK_global_system_variables,
|
||||
key_LOCK_manager,
|
||||
key_LOCK_prepared_stmt_count,
|
||||
key_LOCK_rpl_status, key_LOCK_server_started, key_LOCK_status,
|
||||
@ -7707,7 +7699,6 @@ static PSI_mutex_info all_server_mutexes[]=
|
||||
{ &key_LOCK_delayed_status, "LOCK_delayed_status", PSI_FLAG_GLOBAL},
|
||||
{ &key_LOCK_error_log, "LOCK_error_log", PSI_FLAG_GLOBAL},
|
||||
{ &key_LOCK_gdl, "LOCK_gdl", PSI_FLAG_GLOBAL},
|
||||
{ &key_LOCK_global_read_lock, "LOCK_global_read_lock", PSI_FLAG_GLOBAL},
|
||||
{ &key_LOCK_global_system_variables, "LOCK_global_system_variables", PSI_FLAG_GLOBAL},
|
||||
{ &key_LOCK_manager, "LOCK_manager", PSI_FLAG_GLOBAL},
|
||||
{ &key_LOCK_prepared_stmt_count, "LOCK_prepared_stmt_count", PSI_FLAG_GLOBAL},
|
||||
@ -7756,7 +7747,7 @@ PSI_cond_key key_PAGE_cond, key_COND_active, key_COND_pool;
|
||||
#endif /* HAVE_MMAP */
|
||||
|
||||
PSI_cond_key key_BINLOG_COND_prep_xids, key_BINLOG_update_cond,
|
||||
key_COND_cache_status_changed, key_COND_global_read_lock, key_COND_manager,
|
||||
key_COND_cache_status_changed, key_COND_manager,
|
||||
key_COND_rpl_status, key_COND_server_started,
|
||||
key_delayed_insert_cond, key_delayed_insert_cond_client,
|
||||
key_item_func_sleep_cond, key_master_info_data_cond,
|
||||
@ -7779,7 +7770,6 @@ static PSI_cond_info all_server_conds[]=
|
||||
{ &key_BINLOG_COND_prep_xids, "MYSQL_BIN_LOG::COND_prep_xids", 0},
|
||||
{ &key_BINLOG_update_cond, "MYSQL_BIN_LOG::update_cond", 0},
|
||||
{ &key_COND_cache_status_changed, "Query_cache::COND_cache_status_changed", 0},
|
||||
{ &key_COND_global_read_lock, "COND_global_read_lock", PSI_FLAG_GLOBAL},
|
||||
{ &key_COND_manager, "COND_manager", PSI_FLAG_GLOBAL},
|
||||
{ &key_COND_rpl_status, "COND_rpl_status", PSI_FLAG_GLOBAL},
|
||||
{ &key_COND_server_started, "COND_server_started", PSI_FLAG_GLOBAL},
|
||||
|
@ -100,7 +100,7 @@ extern bool opt_ignore_builtin_innodb;
|
||||
extern my_bool opt_character_set_client_handshake;
|
||||
extern bool volatile abort_loop;
|
||||
extern bool in_bootstrap;
|
||||
extern uint volatile thread_count, global_read_lock;
|
||||
extern uint volatile thread_count;
|
||||
extern uint connection_count;
|
||||
extern my_bool opt_safe_user_create;
|
||||
extern my_bool opt_safe_show_db, opt_local_infile, opt_myisam_use_mmap;
|
||||
@ -227,7 +227,7 @@ extern PSI_mutex_key key_BINLOG_LOCK_index, key_BINLOG_LOCK_prep_xids,
|
||||
key_delayed_insert_mutex, key_hash_filo_lock, key_LOCK_active_mi,
|
||||
key_LOCK_connection_count, key_LOCK_crypt, key_LOCK_delayed_create,
|
||||
key_LOCK_delayed_insert, key_LOCK_delayed_status, key_LOCK_error_log,
|
||||
key_LOCK_gdl, key_LOCK_global_read_lock, key_LOCK_global_system_variables,
|
||||
key_LOCK_gdl, key_LOCK_global_system_variables,
|
||||
key_LOCK_logger, key_LOCK_manager,
|
||||
key_LOCK_prepared_stmt_count,
|
||||
key_LOCK_rpl_status, key_LOCK_server_started, key_LOCK_status,
|
||||
@ -248,7 +248,7 @@ extern PSI_cond_key key_PAGE_cond, key_COND_active, key_COND_pool;
|
||||
#endif /* HAVE_MMAP */
|
||||
|
||||
extern PSI_cond_key key_BINLOG_COND_prep_xids, key_BINLOG_update_cond,
|
||||
key_COND_cache_status_changed, key_COND_global_read_lock, key_COND_manager,
|
||||
key_COND_cache_status_changed, key_COND_manager,
|
||||
key_COND_rpl_status, key_COND_server_started,
|
||||
key_delayed_insert_cond, key_delayed_insert_cond_client,
|
||||
key_item_func_sleep_cond, key_master_info_data_cond,
|
||||
@ -320,7 +320,7 @@ extern mysql_mutex_t
|
||||
LOCK_user_locks, LOCK_status,
|
||||
LOCK_error_log, LOCK_delayed_insert, LOCK_uuid_generator,
|
||||
LOCK_delayed_status, LOCK_delayed_create, LOCK_crypt, LOCK_timezone,
|
||||
LOCK_slave_list, LOCK_active_mi, LOCK_manager, LOCK_global_read_lock,
|
||||
LOCK_slave_list, LOCK_active_mi, LOCK_manager,
|
||||
LOCK_global_system_variables, LOCK_user_conn,
|
||||
LOCK_prepared_stmt_count, LOCK_error_messages, LOCK_connection_count;
|
||||
extern MYSQL_PLUGIN_IMPORT mysql_mutex_t LOCK_thread_count;
|
||||
@ -333,7 +333,6 @@ extern mysql_rwlock_t LOCK_grant, LOCK_sys_init_connect, LOCK_sys_init_slave;
|
||||
extern mysql_rwlock_t LOCK_system_variables_hash;
|
||||
extern mysql_cond_t COND_thread_count;
|
||||
extern mysql_cond_t COND_manager;
|
||||
extern mysql_cond_t COND_global_read_lock;
|
||||
extern int32 thread_running;
|
||||
extern my_atomic_rwlock_t thread_running_lock;
|
||||
|
||||
|
@ -1274,6 +1274,9 @@ void Relay_log_info::slave_close_thread_tables(THD *thd)
|
||||
*/
|
||||
if (! thd->in_multi_stmt_transaction_mode())
|
||||
thd->mdl_context.release_transactional_locks();
|
||||
else
|
||||
thd->mdl_context.release_statement_locks();
|
||||
|
||||
clear_tables_to_lock();
|
||||
}
|
||||
#endif
|
||||
|
23
sql/sp.cc
23
sql/sp.cc
@ -27,7 +27,7 @@
|
||||
#include "sql_acl.h" // SUPER_ACL
|
||||
#include "sp_head.h"
|
||||
#include "sp_cache.h"
|
||||
#include "lock.h" // lock_routine_name
|
||||
#include "lock.h" // lock_object_name
|
||||
|
||||
#include <my_user.h>
|
||||
|
||||
@ -440,7 +440,7 @@ static TABLE *open_proc_table_for_update(THD *thd)
|
||||
{
|
||||
TABLE_LIST table_list;
|
||||
TABLE *table;
|
||||
MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
DBUG_ENTER("open_proc_table_for_update");
|
||||
|
||||
table_list.init_one_table("mysql", 5, "proc", 4, "proc", TL_WRITE);
|
||||
@ -925,6 +925,8 @@ sp_create_routine(THD *thd, int type, sp_head *sp)
|
||||
TABLE *table;
|
||||
char definer[USER_HOST_BUFF_SIZE];
|
||||
ulong saved_mode= thd->variables.sql_mode;
|
||||
MDL_key::enum_mdl_namespace mdl_type= type == TYPE_ENUM_FUNCTION ?
|
||||
MDL_key::FUNCTION : MDL_key::PROCEDURE;
|
||||
|
||||
CHARSET_INFO *db_cs= get_default_db_collation(thd, sp->m_db.str);
|
||||
|
||||
@ -944,8 +946,7 @@ sp_create_routine(THD *thd, int type, sp_head *sp)
|
||||
type == TYPE_ENUM_FUNCTION);
|
||||
|
||||
/* Grab an exclusive MDL lock. */
|
||||
if (lock_routine_name(thd, type == TYPE_ENUM_FUNCTION,
|
||||
sp->m_db.str, sp->m_name.str))
|
||||
if (lock_object_name(thd, mdl_type, sp->m_db.str, sp->m_name.str))
|
||||
DBUG_RETURN(SP_OPEN_TABLE_FAILED);
|
||||
|
||||
/* Reset sql_mode during data dictionary operations. */
|
||||
@ -1193,6 +1194,8 @@ sp_drop_routine(THD *thd, int type, sp_name *name)
|
||||
TABLE *table;
|
||||
int ret;
|
||||
bool save_binlog_row_based;
|
||||
MDL_key::enum_mdl_namespace mdl_type= type == TYPE_ENUM_FUNCTION ?
|
||||
MDL_key::FUNCTION : MDL_key::PROCEDURE;
|
||||
DBUG_ENTER("sp_drop_routine");
|
||||
DBUG_PRINT("enter", ("type: %d name: %.*s",
|
||||
type, (int) name->m_name.length, name->m_name.str));
|
||||
@ -1201,8 +1204,7 @@ sp_drop_routine(THD *thd, int type, sp_name *name)
|
||||
type == TYPE_ENUM_FUNCTION);
|
||||
|
||||
/* Grab an exclusive MDL lock. */
|
||||
if (lock_routine_name(thd, type == TYPE_ENUM_FUNCTION,
|
||||
name->m_db.str, name->m_name.str))
|
||||
if (lock_object_name(thd, mdl_type, name->m_db.str, name->m_name.str))
|
||||
DBUG_RETURN(SP_DELETE_ROW_FAILED);
|
||||
|
||||
if (!(table= open_proc_table_for_update(thd)))
|
||||
@ -1273,6 +1275,8 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics)
|
||||
TABLE *table;
|
||||
int ret;
|
||||
bool save_binlog_row_based;
|
||||
MDL_key::enum_mdl_namespace mdl_type= type == TYPE_ENUM_FUNCTION ?
|
||||
MDL_key::FUNCTION : MDL_key::PROCEDURE;
|
||||
DBUG_ENTER("sp_update_routine");
|
||||
DBUG_PRINT("enter", ("type: %d name: %.*s",
|
||||
type, (int) name->m_name.length, name->m_name.str));
|
||||
@ -1281,8 +1285,7 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics)
|
||||
type == TYPE_ENUM_FUNCTION);
|
||||
|
||||
/* Grab an exclusive MDL lock. */
|
||||
if (lock_routine_name(thd, type == TYPE_ENUM_FUNCTION,
|
||||
name->m_db.str, name->m_name.str))
|
||||
if (lock_object_name(thd, mdl_type, name->m_db.str, name->m_name.str))
|
||||
DBUG_RETURN(SP_OPEN_TABLE_FAILED);
|
||||
|
||||
if (!(table= open_proc_table_for_update(thd)))
|
||||
@ -1370,7 +1373,7 @@ sp_drop_db_routines(THD *thd, char *db)
|
||||
TABLE *table;
|
||||
int ret;
|
||||
uint key_len;
|
||||
MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
DBUG_ENTER("sp_drop_db_routines");
|
||||
DBUG_PRINT("enter", ("db: %s", db));
|
||||
|
||||
@ -1697,7 +1700,7 @@ bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena,
|
||||
(Sroutine_hash_entry *)arena->alloc(sizeof(Sroutine_hash_entry));
|
||||
if (!rn) // OOM. Error will be reported using fatal_error().
|
||||
return FALSE;
|
||||
rn->mdl_request.init(key, MDL_SHARED);
|
||||
rn->mdl_request.init(key, MDL_SHARED, MDL_TRANSACTION);
|
||||
if (my_hash_insert(&prelocking_ctx->sroutines, (uchar *)rn))
|
||||
return FALSE;
|
||||
prelocking_ctx->sroutines_list.link_in_list(rn, &rn->next);
|
||||
|
@ -2140,6 +2140,8 @@ sp_head::execute_procedure(THD *thd, List<Item> *args)
|
||||
|
||||
if (! thd->in_sub_stmt && ! thd->in_multi_stmt_transaction_mode())
|
||||
thd->mdl_context.release_transactional_locks();
|
||||
else if (! thd->in_sub_stmt)
|
||||
thd->mdl_context.release_statement_locks();
|
||||
|
||||
thd->rollback_item_tree_changes();
|
||||
|
||||
@ -2978,6 +2980,8 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp,
|
||||
|
||||
if (! thd->in_sub_stmt && ! thd->in_multi_stmt_transaction_mode())
|
||||
thd->mdl_context.release_transactional_locks();
|
||||
else if (! thd->in_sub_stmt)
|
||||
thd->mdl_context.release_statement_locks();
|
||||
}
|
||||
|
||||
if (m_lex->query_tables_own_last)
|
||||
@ -4176,7 +4180,8 @@ sp_head::add_used_tables_to_table_list(THD *thd,
|
||||
*/
|
||||
table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name,
|
||||
table->lock_type >= TL_WRITE_ALLOW_WRITE ?
|
||||
MDL_SHARED_WRITE : MDL_SHARED_READ);
|
||||
MDL_SHARED_WRITE : MDL_SHARED_READ,
|
||||
MDL_TRANSACTION);
|
||||
|
||||
/* Everyting else should be zeroed */
|
||||
|
||||
@ -4220,7 +4225,7 @@ sp_add_to_query_tables(THD *thd, LEX *lex,
|
||||
table->select_lex= lex->current_select;
|
||||
table->cacheable_table= 1;
|
||||
table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name,
|
||||
mdl_type);
|
||||
mdl_type, MDL_TRANSACTION);
|
||||
|
||||
lex->add_to_query_tables(table);
|
||||
return table;
|
||||
|
@ -85,7 +85,7 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list,
|
||||
key_length= create_table_def_key(thd, key, table_list, 0);
|
||||
table_list->mdl_request.init(MDL_key::TABLE,
|
||||
table_list->db, table_list->table_name,
|
||||
MDL_EXCLUSIVE);
|
||||
MDL_EXCLUSIVE, MDL_TRANSACTION);
|
||||
|
||||
if (lock_table_names(thd, table_list, table_list->next_global,
|
||||
thd->variables.lock_wait_timeout,
|
||||
|
130
sql/sql_base.cc
130
sql/sql_base.cc
@ -21,7 +21,7 @@
|
||||
#include "sql_priv.h"
|
||||
#include "unireg.h"
|
||||
#include "debug_sync.h"
|
||||
#include "lock.h" // broadcast_refresh, mysql_lock_remove,
|
||||
#include "lock.h" // mysql_lock_remove,
|
||||
// mysql_unlock_tables,
|
||||
// mysql_lock_have_duplicate
|
||||
#include "sql_show.h" // append_identifier
|
||||
@ -1285,20 +1285,12 @@ static void mark_used_tables_as_free_for_reuse(THD *thd, TABLE *table)
|
||||
|
||||
static void close_open_tables(THD *thd)
|
||||
{
|
||||
bool found_old_table= 0;
|
||||
|
||||
mysql_mutex_assert_not_owner(&LOCK_open);
|
||||
|
||||
DBUG_PRINT("info", ("thd->open_tables: 0x%lx", (long) thd->open_tables));
|
||||
|
||||
while (thd->open_tables)
|
||||
found_old_table|= close_thread_table(thd, &thd->open_tables);
|
||||
|
||||
if (found_old_table)
|
||||
{
|
||||
/* Tell threads waiting for refresh that something has happened */
|
||||
broadcast_refresh();
|
||||
}
|
||||
(void) close_thread_table(thd, &thd->open_tables);
|
||||
}
|
||||
|
||||
|
||||
@ -1364,11 +1356,6 @@ close_all_tables_for_name(THD *thd, TABLE_SHARE *share,
|
||||
/* Remove the table share from the cache. */
|
||||
tdc_remove_table(thd, TDC_RT_REMOVE_ALL, db, table_name,
|
||||
FALSE);
|
||||
/*
|
||||
There could be a FLUSH thread waiting
|
||||
on the table to go away. Wake it up.
|
||||
*/
|
||||
broadcast_refresh();
|
||||
}
|
||||
|
||||
|
||||
@ -2463,7 +2450,8 @@ open_table_get_mdl_lock(THD *thd, Open_table_context *ot_ctx,
|
||||
|
||||
mdl_request_shared.init(&mdl_request->key,
|
||||
(flags & MYSQL_OPEN_FORCE_SHARED_MDL) ?
|
||||
MDL_SHARED : MDL_SHARED_HIGH_PRIO);
|
||||
MDL_SHARED : MDL_SHARED_HIGH_PRIO,
|
||||
MDL_TRANSACTION);
|
||||
mdl_request= &mdl_request_shared;
|
||||
}
|
||||
|
||||
@ -2627,32 +2615,6 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
|
||||
key_length= (create_table_def_key(thd, key, table_list, 1) -
|
||||
TMP_TABLE_KEY_EXTRA);
|
||||
|
||||
/*
|
||||
We need this to work for all tables, including temporary
|
||||
tables, for backwards compatibility. But not under LOCK
|
||||
TABLES, since under LOCK TABLES one can't use a non-prelocked
|
||||
table. This code only works for updates done inside DO/SELECT
|
||||
f1() statements, normal DML is handled by means of
|
||||
sql_command_flags.
|
||||
*/
|
||||
if (global_read_lock && table_list->lock_type >= TL_WRITE_ALLOW_WRITE &&
|
||||
! (flags & MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK) &&
|
||||
! thd->locked_tables_mode)
|
||||
{
|
||||
/*
|
||||
Someone has issued FLUSH TABLES WITH READ LOCK and we want
|
||||
a write lock. Wait until the lock is gone.
|
||||
*/
|
||||
if (thd->global_read_lock.wait_if_global_read_lock(thd, 1, 1))
|
||||
DBUG_RETURN(TRUE);
|
||||
|
||||
if (thd->open_tables && thd->open_tables->s->version != refresh_version)
|
||||
{
|
||||
(void)ot_ctx->request_backoff_action(Open_table_context::OT_REOPEN_TABLES,
|
||||
NULL);
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
}
|
||||
/*
|
||||
Unless requested otherwise, try to resolve this table in the list
|
||||
of temporary tables of this thread. In MySQL temporary tables
|
||||
@ -2824,6 +2786,59 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
|
||||
|
||||
if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK))
|
||||
{
|
||||
/*
|
||||
We are not under LOCK TABLES and going to acquire write-lock/
|
||||
modify the base table. We need to acquire protection against
|
||||
global read lock until end of this statement in order to have
|
||||
this statement blocked by active FLUSH TABLES WITH READ LOCK.
|
||||
|
||||
We don't block acquire this protection under LOCK TABLES as
|
||||
such protection already acquired at LOCK TABLES time and
|
||||
not released until UNLOCK TABLES.
|
||||
|
||||
We don't block statements which modify only temporary tables
|
||||
as these tables are not preserved by backup by any form of
|
||||
backup which uses FLUSH TABLES WITH READ LOCK.
|
||||
|
||||
TODO: The fact that we sometimes acquire protection against
|
||||
GRL only when we encounter table to be write-locked
|
||||
slightly increases probability of deadlock.
|
||||
This problem will be solved once Alik pushes his
|
||||
temporary table refactoring patch and we can start
|
||||
pre-acquiring metadata locks at the beggining of
|
||||
open_tables() call.
|
||||
*/
|
||||
if (table_list->mdl_request.type >= MDL_SHARED_WRITE &&
|
||||
! (flags & (MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK |
|
||||
MYSQL_OPEN_FORCE_SHARED_MDL |
|
||||
MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL |
|
||||
MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK)) &&
|
||||
! ot_ctx->has_protection_against_grl())
|
||||
{
|
||||
MDL_request protection_request;
|
||||
MDL_deadlock_handler mdl_deadlock_handler(ot_ctx);
|
||||
|
||||
if (thd->global_read_lock.can_acquire_protection())
|
||||
DBUG_RETURN(TRUE);
|
||||
|
||||
protection_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
|
||||
MDL_STATEMENT);
|
||||
|
||||
/*
|
||||
Install error handler which if possible will convert deadlock error
|
||||
into request to back-off and restart process of opening tables.
|
||||
*/
|
||||
thd->push_internal_handler(&mdl_deadlock_handler);
|
||||
bool result= thd->mdl_context.acquire_lock(&protection_request,
|
||||
ot_ctx->get_timeout());
|
||||
thd->pop_internal_handler();
|
||||
|
||||
if (result)
|
||||
DBUG_RETURN(TRUE);
|
||||
|
||||
ot_ctx->set_has_protection_against_grl();
|
||||
}
|
||||
|
||||
if (open_table_get_mdl_lock(thd, ot_ctx, &table_list->mdl_request,
|
||||
flags, &mdl_ticket) ||
|
||||
mdl_ticket == NULL)
|
||||
@ -3379,7 +3394,6 @@ unlink_all_closed_tables(THD *thd, MYSQL_LOCK *lock, size_t reopen_count)
|
||||
|
||||
close_thread_table(thd, &thd->open_tables);
|
||||
}
|
||||
broadcast_refresh();
|
||||
}
|
||||
/* Exclude all closed tables from the LOCK TABLES list. */
|
||||
for (TABLE_LIST *table_list= m_locked_tables; table_list; table_list=
|
||||
@ -3856,7 +3870,8 @@ Open_table_context::Open_table_context(THD *thd, uint flags)
|
||||
LONG_TIMEOUT : thd->variables.lock_wait_timeout),
|
||||
m_flags(flags),
|
||||
m_action(OT_NO_ACTION),
|
||||
m_has_locks(thd->mdl_context.has_locks())
|
||||
m_has_locks(thd->mdl_context.has_locks()),
|
||||
m_has_protection_against_grl(FALSE)
|
||||
{}
|
||||
|
||||
|
||||
@ -4013,6 +4028,12 @@ recover_from_failed_open(THD *thd)
|
||||
for safety.
|
||||
*/
|
||||
m_failed_table= NULL;
|
||||
/*
|
||||
Reset flag indicating that we have already acquired protection
|
||||
against GRL. It is no longer valid as the corresponding lock was
|
||||
released by close_tables_for_reopen().
|
||||
*/
|
||||
m_has_protection_against_grl= FALSE;
|
||||
/* Prepare for possible another back-off. */
|
||||
m_action= OT_NO_ACTION;
|
||||
return result;
|
||||
@ -4551,11 +4572,20 @@ lock_table_names(THD *thd,
|
||||
if (schema_request == NULL)
|
||||
return TRUE;
|
||||
schema_request->init(MDL_key::SCHEMA, table->db, "",
|
||||
MDL_INTENTION_EXCLUSIVE);
|
||||
MDL_INTENTION_EXCLUSIVE,
|
||||
MDL_TRANSACTION);
|
||||
mdl_requests.push_front(schema_request);
|
||||
}
|
||||
/* Take the global intention exclusive lock. */
|
||||
global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE);
|
||||
|
||||
/*
|
||||
Protect this statement against concurrent global read lock
|
||||
by acquiring global intention exclusive lock with statement
|
||||
duration.
|
||||
*/
|
||||
if (thd->global_read_lock.can_acquire_protection())
|
||||
return TRUE;
|
||||
global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
|
||||
MDL_STATEMENT);
|
||||
mdl_requests.push_front(&global_request);
|
||||
}
|
||||
|
||||
@ -5362,7 +5392,7 @@ bool open_and_lock_tables(THD *thd, TABLE_LIST *tables,
|
||||
Prelocking_strategy *prelocking_strategy)
|
||||
{
|
||||
uint counter;
|
||||
MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
DBUG_ENTER("open_and_lock_tables");
|
||||
DBUG_PRINT("enter", ("derived handling: %d", derived));
|
||||
|
||||
@ -5419,7 +5449,7 @@ bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags)
|
||||
{
|
||||
DML_prelocking_strategy prelocking_strategy;
|
||||
uint counter;
|
||||
MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
DBUG_ENTER("open_normal_and_derived_tables");
|
||||
DBUG_ASSERT(!thd->fill_derived_tables());
|
||||
if (open_tables(thd, &tables, &counter, flags, &prelocking_strategy) ||
|
||||
@ -5676,7 +5706,7 @@ bool lock_tables(THD *thd, TABLE_LIST *tables, uint count,
|
||||
*/
|
||||
|
||||
void close_tables_for_reopen(THD *thd, TABLE_LIST **tables,
|
||||
MDL_ticket *start_of_statement_svp)
|
||||
const MDL_savepoint &start_of_statement_svp)
|
||||
{
|
||||
TABLE_LIST *first_not_own_table= thd->lex->first_not_own_table();
|
||||
TABLE_LIST *tmp;
|
||||
|
@ -159,7 +159,7 @@ thr_lock_type read_lock_type_for_table(THD *thd,
|
||||
my_bool mysql_rm_tmp_tables(void);
|
||||
bool rm_temporary_table(handlerton *base, char *path);
|
||||
void close_tables_for_reopen(THD *thd, TABLE_LIST **tables,
|
||||
MDL_ticket *start_of_statement_svp);
|
||||
const MDL_savepoint &start_of_statement_svp);
|
||||
TABLE_LIST *find_table_in_list(TABLE_LIST *table,
|
||||
TABLE_LIST *TABLE_LIST::*link,
|
||||
const char *db_name,
|
||||
@ -507,7 +507,7 @@ public:
|
||||
the statement, so that we can rollback to it before waiting on
|
||||
locks.
|
||||
*/
|
||||
MDL_ticket *start_of_statement_svp() const
|
||||
const MDL_savepoint &start_of_statement_svp() const
|
||||
{
|
||||
return m_start_of_statement_svp;
|
||||
}
|
||||
@ -518,6 +518,21 @@ public:
|
||||
}
|
||||
|
||||
uint get_flags() const { return m_flags; }
|
||||
|
||||
/**
|
||||
Set flag indicating that we have already acquired metadata lock
|
||||
protecting this statement against GRL while opening tables.
|
||||
*/
|
||||
void set_has_protection_against_grl()
|
||||
{
|
||||
m_has_protection_against_grl= TRUE;
|
||||
}
|
||||
|
||||
bool has_protection_against_grl() const
|
||||
{
|
||||
return m_has_protection_against_grl;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
For OT_DISCOVER and OT_REPAIR actions, the table list element for
|
||||
@ -525,7 +540,7 @@ private:
|
||||
should be repaired.
|
||||
*/
|
||||
TABLE_LIST *m_failed_table;
|
||||
MDL_ticket *m_start_of_statement_svp;
|
||||
MDL_savepoint m_start_of_statement_svp;
|
||||
/**
|
||||
Lock timeout in seconds. Initialized to LONG_TIMEOUT when opening system
|
||||
tables or to the "lock_wait_timeout" system variable for regular tables.
|
||||
@ -541,6 +556,11 @@ private:
|
||||
and we can't safely do back-off (and release them).
|
||||
*/
|
||||
bool m_has_locks;
|
||||
/**
|
||||
Indicates that in the process of opening tables we have acquired
|
||||
protection against global read lock.
|
||||
*/
|
||||
bool m_has_protection_against_grl;
|
||||
};
|
||||
|
||||
|
||||
|
@ -3497,11 +3497,15 @@ void THD::set_mysys_var(struct st_my_thread_var *new_mysys_var)
|
||||
void THD::leave_locked_tables_mode()
|
||||
{
|
||||
locked_tables_mode= LTM_NONE;
|
||||
/* Make sure we don't release the global read lock when leaving LTM. */
|
||||
mdl_context.reset_trans_sentinel(global_read_lock.global_shared_lock());
|
||||
mdl_context.set_transaction_duration_for_all_locks();
|
||||
/*
|
||||
Make sure we don't release the global read lock and commit blocker
|
||||
when leaving LTM.
|
||||
*/
|
||||
global_read_lock.set_explicit_lock_duration(this);
|
||||
/* Also ensure that we don't release metadata locks for open HANDLERs. */
|
||||
if (handler_tables_hash.records)
|
||||
mysql_ha_move_tickets_after_trans_sentinel(this);
|
||||
mysql_ha_set_explicit_lock_duration(this);
|
||||
}
|
||||
|
||||
void THD::get_definer(LEX_USER *definer)
|
||||
|
@ -822,8 +822,8 @@ struct st_savepoint {
|
||||
char *name;
|
||||
uint length;
|
||||
Ha_trx_info *ha_list;
|
||||
/** Last acquired lock before this savepoint was set. */
|
||||
MDL_ticket *mdl_savepoint;
|
||||
/** State of metadata locks before this savepoint was set. */
|
||||
MDL_savepoint mdl_savepoint;
|
||||
};
|
||||
|
||||
enum xa_states {XA_NOTR=0, XA_ACTIVE, XA_IDLE, XA_PREPARED, XA_ROLLBACK_ONLY};
|
||||
@ -1058,12 +1058,12 @@ class Open_tables_backup: public Open_tables_state
|
||||
public:
|
||||
/**
|
||||
When we backup the open tables state to open a system
|
||||
table or tables, points at the last metadata lock
|
||||
acquired before the backup. Is used to release
|
||||
metadata locks on system tables after they are
|
||||
table or tables, we want to save state of metadata
|
||||
locks which were acquired before the backup. It is used
|
||||
to release metadata locks on system tables after they are
|
||||
no longer used.
|
||||
*/
|
||||
MDL_ticket *mdl_system_tables_svp;
|
||||
MDL_savepoint mdl_system_tables_svp;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1336,26 +1336,43 @@ public:
|
||||
};
|
||||
|
||||
Global_read_lock()
|
||||
:m_protection_count(0), m_state(GRL_NONE), m_mdl_global_shared_lock(NULL)
|
||||
: m_state(GRL_NONE),
|
||||
m_mdl_global_shared_lock(NULL),
|
||||
m_mdl_blocks_commits_lock(NULL)
|
||||
{}
|
||||
|
||||
bool lock_global_read_lock(THD *thd);
|
||||
void unlock_global_read_lock(THD *thd);
|
||||
bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh,
|
||||
bool is_not_commit);
|
||||
void start_waiting_global_read_lock(THD *thd);
|
||||
/**
|
||||
Check if this connection can acquire protection against GRL and
|
||||
emit error if otherwise.
|
||||
*/
|
||||
bool can_acquire_protection() const
|
||||
{
|
||||
if (m_state)
|
||||
{
|
||||
my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0));
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
bool make_global_read_lock_block_commit(THD *thd);
|
||||
bool is_acquired() const { return m_state != GRL_NONE; }
|
||||
bool has_protection() const { return m_protection_count > 0; }
|
||||
MDL_ticket *global_shared_lock() const { return m_mdl_global_shared_lock; }
|
||||
void set_explicit_lock_duration(THD *thd);
|
||||
private:
|
||||
uint m_protection_count; // GRL protection count
|
||||
enum_grl_state m_state;
|
||||
/**
|
||||
In order to acquire the global read lock, the connection must
|
||||
acquire a global shared metadata lock, to prohibit all DDL.
|
||||
acquire shared metadata lock in GLOBAL namespace, to prohibit
|
||||
all DDL.
|
||||
*/
|
||||
enum_grl_state m_state;
|
||||
MDL_ticket *m_mdl_global_shared_lock;
|
||||
/**
|
||||
Also in order to acquire the global read lock, the connection
|
||||
must acquire a shared metadata lock in COMMIT namespace, to
|
||||
prohibit commits.
|
||||
*/
|
||||
MDL_ticket *m_mdl_blocks_commits_lock;
|
||||
};
|
||||
|
||||
|
||||
@ -2697,7 +2714,7 @@ public:
|
||||
{
|
||||
DBUG_ASSERT(locked_tables_mode == LTM_NONE);
|
||||
|
||||
mdl_context.set_trans_sentinel();
|
||||
mdl_context.set_explicit_duration_for_all_locks();
|
||||
locked_tables_mode= mode_arg;
|
||||
}
|
||||
void leave_locked_tables_mode();
|
||||
@ -3502,21 +3519,11 @@ public:
|
||||
*/
|
||||
#define CF_DIAGNOSTIC_STMT (1U << 8)
|
||||
|
||||
/**
|
||||
SQL statements that must be protected against impending global read lock
|
||||
to prevent deadlock. This deadlock could otherwise happen if the statement
|
||||
starts waiting for the GRL to go away inside mysql_lock_tables while at the
|
||||
same time having "old" opened tables. The thread holding the GRL can be
|
||||
waiting for these "old" opened tables to be closed, causing a deadlock
|
||||
(FLUSH TABLES WITH READ LOCK).
|
||||
*/
|
||||
#define CF_PROTECT_AGAINST_GRL (1U << 10)
|
||||
|
||||
/**
|
||||
Identifies statements that may generate row events
|
||||
and that may end up in the binary log.
|
||||
*/
|
||||
#define CF_CAN_GENERATE_ROW_EVENTS (1U << 11)
|
||||
#define CF_CAN_GENERATE_ROW_EVENTS (1U << 9)
|
||||
|
||||
/* Bits in server_command_flags */
|
||||
|
||||
|
@ -1054,7 +1054,8 @@ static long mysql_rm_known_files(THD *thd, MY_DIR *dirp, const char *db,
|
||||
table_list->alias= table_list->table_name; // If lower_case_table_names=2
|
||||
table_list->internal_tmp_table= is_prefix(file->name, tmp_file_prefix);
|
||||
table_list->mdl_request.init(MDL_key::TABLE, table_list->db,
|
||||
table_list->table_name, MDL_EXCLUSIVE);
|
||||
table_list->table_name, MDL_EXCLUSIVE,
|
||||
MDL_TRANSACTION);
|
||||
/* Link into list */
|
||||
(*tot_list_next_local)= table_list;
|
||||
(*tot_list_next_global)= table_list;
|
||||
|
@ -55,7 +55,7 @@
|
||||
#include "sql_handler.h"
|
||||
#include "unireg.h" // REQUIRED: for other includes
|
||||
#include "sql_base.h" // close_thread_tables
|
||||
#include "lock.h" // broadcast_refresh, mysql_unlock_tables
|
||||
#include "lock.h" // mysql_unlock_tables
|
||||
#include "key.h" // key_copy
|
||||
#include "sql_base.h" // insert_fields
|
||||
#include "sql_select.h"
|
||||
@ -131,11 +131,7 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables)
|
||||
/* Non temporary table. */
|
||||
tables->table->file->ha_index_or_rnd_end();
|
||||
tables->table->open_by_handler= 0;
|
||||
if (close_thread_table(thd, &tables->table))
|
||||
{
|
||||
/* Tell threads waiting for refresh that something has happened */
|
||||
broadcast_refresh();
|
||||
}
|
||||
(void) close_thread_table(thd, &tables->table);
|
||||
thd->mdl_context.release_lock(tables->mdl_request.ticket);
|
||||
}
|
||||
else if (tables->table)
|
||||
@ -183,7 +179,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen)
|
||||
uint dblen, namelen, aliaslen, counter;
|
||||
bool error;
|
||||
TABLE *backup_open_tables;
|
||||
MDL_ticket *mdl_savepoint;
|
||||
MDL_savepoint mdl_savepoint;
|
||||
DBUG_ENTER("mysql_ha_open");
|
||||
DBUG_PRINT("enter",("'%s'.'%s' as '%s' reopen: %d",
|
||||
tables->db, tables->table_name, tables->alias,
|
||||
@ -252,7 +248,13 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen)
|
||||
memcpy(hash_tables->db, tables->db, dblen);
|
||||
memcpy(hash_tables->table_name, tables->table_name, namelen);
|
||||
memcpy(hash_tables->alias, tables->alias, aliaslen);
|
||||
hash_tables->mdl_request.init(MDL_key::TABLE, db, name, MDL_SHARED);
|
||||
/*
|
||||
We can't request lock with explicit duration for this table
|
||||
right from the start as open_tables() can't handle properly
|
||||
back-off for such locks.
|
||||
*/
|
||||
hash_tables->mdl_request.init(MDL_key::TABLE, db, name, MDL_SHARED,
|
||||
MDL_TRANSACTION);
|
||||
/* for now HANDLER can be used only for real TABLES */
|
||||
hash_tables->required_type= FRMTYPE_TABLE;
|
||||
|
||||
@ -332,8 +334,8 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen)
|
||||
thd->set_open_tables(backup_open_tables);
|
||||
if (hash_tables->mdl_request.ticket)
|
||||
{
|
||||
thd->mdl_context.
|
||||
move_ticket_after_trans_sentinel(hash_tables->mdl_request.ticket);
|
||||
thd->mdl_context.set_lock_duration(hash_tables->mdl_request.ticket,
|
||||
MDL_EXPLICIT);
|
||||
thd->mdl_context.set_needs_thr_lock_abort(TRUE);
|
||||
}
|
||||
|
||||
@ -969,24 +971,23 @@ void mysql_ha_cleanup(THD *thd)
|
||||
|
||||
|
||||
/**
|
||||
Move tickets for metadata locks corresponding to open HANDLERs
|
||||
after transaction sentinel in order to protect them from being
|
||||
released at the end of transaction.
|
||||
Set explicit duration for metadata locks corresponding to open HANDLERs
|
||||
to protect them from being released at the end of transaction.
|
||||
|
||||
@param thd Thread identifier.
|
||||
*/
|
||||
|
||||
void mysql_ha_move_tickets_after_trans_sentinel(THD *thd)
|
||||
void mysql_ha_set_explicit_lock_duration(THD *thd)
|
||||
{
|
||||
TABLE_LIST *hash_tables;
|
||||
DBUG_ENTER("mysql_ha_move_tickets_after_trans_sentinel");
|
||||
DBUG_ENTER("mysql_ha_set_explicit_lock_duration");
|
||||
|
||||
for (uint i= 0; i < thd->handler_tables_hash.records; i++)
|
||||
{
|
||||
hash_tables= (TABLE_LIST*) my_hash_element(&thd->handler_tables_hash, i);
|
||||
if (hash_tables->table && hash_tables->table->mdl_ticket)
|
||||
thd->mdl_context.
|
||||
move_ticket_after_trans_sentinel(hash_tables->table->mdl_ticket);
|
||||
thd->mdl_context.set_lock_duration(hash_tables->table->mdl_ticket,
|
||||
MDL_EXPLICIT);
|
||||
}
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
@ -31,6 +31,6 @@ void mysql_ha_flush(THD *thd);
|
||||
void mysql_ha_flush_tables(THD *thd, TABLE_LIST *all_tables);
|
||||
void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables);
|
||||
void mysql_ha_cleanup(THD *thd);
|
||||
void mysql_ha_move_tickets_after_trans_sentinel(THD *thd);
|
||||
void mysql_ha_set_explicit_lock_duration(THD *thd);
|
||||
|
||||
#endif /* SQL_HANDLER_INCLUDED */
|
||||
|
@ -77,7 +77,8 @@
|
||||
#include "sql_audit.h"
|
||||
|
||||
#ifndef EMBEDDED_LIBRARY
|
||||
static bool delayed_get_table(THD *thd, TABLE_LIST *table_list);
|
||||
static bool delayed_get_table(THD *thd, MDL_request *grl_protection_request,
|
||||
TABLE_LIST *table_list);
|
||||
static int write_delayed(THD *thd, TABLE *table, enum_duplicates duplic,
|
||||
LEX_STRING query, bool ignore, bool log_on);
|
||||
static void end_delayed_insert(THD *thd);
|
||||
@ -529,32 +530,28 @@ void upgrade_lock_type(THD *thd, thr_lock_type *lock_type,
|
||||
static
|
||||
bool open_and_lock_for_insert_delayed(THD *thd, TABLE_LIST *table_list)
|
||||
{
|
||||
MDL_request protection_request;
|
||||
DBUG_ENTER("open_and_lock_for_insert_delayed");
|
||||
|
||||
#ifndef EMBEDDED_LIBRARY
|
||||
if (thd->locked_tables_mode && thd->global_read_lock.is_acquired())
|
||||
{
|
||||
/*
|
||||
If this connection has the global read lock, the handler thread
|
||||
will not be able to lock the table. It will wait for the global
|
||||
read lock to go away, but this will never happen since the
|
||||
connection thread will be stuck waiting for the handler thread
|
||||
to open and lock the table.
|
||||
If we are not in locked tables mode, INSERT will seek protection
|
||||
against the global read lock (and fail), thus we will only get
|
||||
to this point in locked tables mode.
|
||||
*/
|
||||
my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0));
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
|
||||
/*
|
||||
In order for the deadlock detector to be able to find any deadlocks
|
||||
caused by the handler thread locking this table, we take the metadata
|
||||
lock inside the connection thread. If this goes ok, the ticket is cloned
|
||||
and added to the list of granted locks held by the handler thread.
|
||||
caused by the handler thread waiting for GRL or this table, we acquire
|
||||
protection against GRL (global IX metadata lock) and metadata lock on
|
||||
table to being inserted into inside the connection thread.
|
||||
If this goes ok, the tickets are cloned and added to the list of granted
|
||||
locks held by the handler thread.
|
||||
*/
|
||||
MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
if (thd->global_read_lock.can_acquire_protection())
|
||||
DBUG_RETURN(TRUE);
|
||||
|
||||
protection_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
|
||||
MDL_STATEMENT);
|
||||
|
||||
if (thd->mdl_context.acquire_lock(&protection_request,
|
||||
thd->variables.lock_wait_timeout))
|
||||
DBUG_RETURN(TRUE);
|
||||
|
||||
if (thd->mdl_context.acquire_lock(&table_list->mdl_request,
|
||||
thd->variables.lock_wait_timeout))
|
||||
/*
|
||||
@ -564,7 +561,7 @@ bool open_and_lock_for_insert_delayed(THD *thd, TABLE_LIST *table_list)
|
||||
DBUG_RETURN(TRUE);
|
||||
|
||||
bool error= FALSE;
|
||||
if (delayed_get_table(thd, table_list))
|
||||
if (delayed_get_table(thd, &protection_request, table_list))
|
||||
error= TRUE;
|
||||
else if (table_list->table)
|
||||
{
|
||||
@ -589,13 +586,13 @@ bool open_and_lock_for_insert_delayed(THD *thd, TABLE_LIST *table_list)
|
||||
}
|
||||
|
||||
/*
|
||||
If a lock was acquired above, we should release it after
|
||||
handle_delayed_insert() has cloned the ticket. Note that acquire_lock() can
|
||||
succeed because the connection already has the lock. In this case the ticket
|
||||
will be before the mdl_savepoint and we should not release it here.
|
||||
We can't release protection against GRL and metadata lock on the table
|
||||
being inserted into here. These locks might be required, for example,
|
||||
because this INSERT DELAYED calls functions which may try to update
|
||||
this or another tables (updating the same table is of course illegal,
|
||||
but such an attempt can be discovered only later during statement
|
||||
execution).
|
||||
*/
|
||||
if (!thd->mdl_context.has_lock(mdl_savepoint, table_list->mdl_request.ticket))
|
||||
thd->mdl_context.release_lock(table_list->mdl_request.ticket);
|
||||
|
||||
/*
|
||||
Reset the ticket in case we end up having to use normal insert and
|
||||
@ -1873,10 +1870,11 @@ public:
|
||||
mysql_cond_t cond, cond_client;
|
||||
volatile uint tables_in_use,stacked_inserts;
|
||||
volatile bool status;
|
||||
/*
|
||||
/**
|
||||
When the handler thread starts, it clones a metadata lock ticket
|
||||
for the table to be inserted. This is done to allow the deadlock
|
||||
detector to detect deadlocks resulting from this lock.
|
||||
which protects against GRL and ticket for the table to be inserted.
|
||||
This is done to allow the deadlock detector to detect deadlocks
|
||||
resulting from these locks.
|
||||
Before this is done, the connection thread cannot safely exit
|
||||
without causing problems for clone_ticket().
|
||||
Once handler_thread_initialized has been set, it is safe for the
|
||||
@ -1888,6 +1886,11 @@ public:
|
||||
I_List<delayed_row> rows;
|
||||
ulong group_count;
|
||||
TABLE_LIST table_list; // Argument
|
||||
/**
|
||||
Request for IX metadata lock protecting against GRL which is
|
||||
passed from connection thread to the handler thread.
|
||||
*/
|
||||
MDL_request grl_protection;
|
||||
|
||||
Delayed_insert()
|
||||
:locks_in_memory(0), table(0),tables_in_use(0),stacked_inserts(0),
|
||||
@ -2066,7 +2069,8 @@ Delayed_insert *find_handler(THD *thd, TABLE_LIST *table_list)
|
||||
*/
|
||||
|
||||
static
|
||||
bool delayed_get_table(THD *thd, TABLE_LIST *table_list)
|
||||
bool delayed_get_table(THD *thd, MDL_request *grl_protection_request,
|
||||
TABLE_LIST *table_list)
|
||||
{
|
||||
int error;
|
||||
Delayed_insert *di;
|
||||
@ -2110,7 +2114,10 @@ bool delayed_get_table(THD *thd, TABLE_LIST *table_list)
|
||||
/* Replace volatile strings with local copies */
|
||||
di->table_list.alias= di->table_list.table_name= di->thd.query();
|
||||
di->table_list.db= di->thd.db;
|
||||
/* We need the ticket so that it can be cloned in handle_delayed_insert */
|
||||
/* We need the tickets so that they can be cloned in handle_delayed_insert */
|
||||
di->grl_protection.init(MDL_key::GLOBAL, "", "",
|
||||
MDL_INTENTION_EXCLUSIVE, MDL_STATEMENT);
|
||||
di->grl_protection.ticket= grl_protection_request->ticket;
|
||||
init_mdl_requests(&di->table_list);
|
||||
di->table_list.mdl_request.ticket= table_list->mdl_request.ticket;
|
||||
|
||||
@ -2650,13 +2657,15 @@ pthread_handler_t handle_delayed_insert(void *arg)
|
||||
thd->set_current_stmt_binlog_format_row_if_mixed();
|
||||
|
||||
/*
|
||||
Clone the ticket representing the lock on the target table for
|
||||
the insert and add it to the list of granted metadata locks held by
|
||||
the handler thread. This is safe since the handler thread is
|
||||
not holding nor waiting on any metadata locks.
|
||||
Clone tickets representing protection against GRL and the lock on
|
||||
the target table for the insert and add them to the list of granted
|
||||
metadata locks held by the handler thread. This is safe since the
|
||||
handler thread is not holding nor waiting on any metadata locks.
|
||||
*/
|
||||
if (thd->mdl_context.clone_ticket(&di->table_list.mdl_request))
|
||||
if (thd->mdl_context.clone_ticket(&di->grl_protection) ||
|
||||
thd->mdl_context.clone_ticket(&di->table_list.mdl_request))
|
||||
{
|
||||
thd->mdl_context.release_transactional_locks();
|
||||
di->handler_thread_initialized= TRUE;
|
||||
goto err;
|
||||
}
|
||||
|
@ -417,7 +417,6 @@ void lex_start(THD *thd)
|
||||
lex->nest_level=0 ;
|
||||
lex->allow_sum_func= 0;
|
||||
lex->in_sum_func= NULL;
|
||||
lex->protect_against_global_read_lock= FALSE;
|
||||
/*
|
||||
ok, there must be a better solution for this, long-term
|
||||
I tried "bzero" in the sql_yacc.yy code, but that for
|
||||
|
@ -2394,22 +2394,6 @@ struct LEX: public Query_tables_list
|
||||
bool escape_used;
|
||||
bool is_lex_started; /* If lex_start() did run. For debugging. */
|
||||
|
||||
/*
|
||||
Special case for SELECT .. FOR UPDATE and LOCK TABLES .. WRITE.
|
||||
|
||||
Protect from a impending GRL as otherwise the thread might deadlock
|
||||
if it starts waiting for the GRL in mysql_lock_tables.
|
||||
|
||||
The protection is needed because there is a race between setting
|
||||
the global read lock and waiting for all open tables to be closed.
|
||||
The problem is a circular wait where a thread holding "old" open
|
||||
tables will wait for the global read lock to be released while the
|
||||
thread holding the global read lock will wait for all "old" open
|
||||
tables to be closed -- the flush part of flush tables with read
|
||||
lock.
|
||||
*/
|
||||
bool protect_against_global_read_lock;
|
||||
|
||||
LEX();
|
||||
|
||||
virtual ~LEX()
|
||||
|
154
sql/sql_parse.cc
154
sql/sql_parse.cc
@ -18,12 +18,9 @@
|
||||
#include "sql_priv.h"
|
||||
#include "unireg.h" // REQUIRED: for other includes
|
||||
#include "sql_parse.h" // sql_kill, *_precheck, *_prepare
|
||||
#include "lock.h" // wait_if_global_read_lock,
|
||||
// unlock_global_read_lock,
|
||||
// try_transactional_lock,
|
||||
#include "lock.h" // try_transactional_lock,
|
||||
// check_transactional_lock,
|
||||
// set_handler_table_locks,
|
||||
// start_waiting_global_read_lock,
|
||||
// lock_global_read_lock,
|
||||
// make_global_read_lock_block_commit
|
||||
#include "sql_base.h" // find_temporary_tablesx
|
||||
@ -260,21 +257,20 @@ void init_update_queries(void)
|
||||
the code, in particular in the Query_log_event's constructor.
|
||||
*/
|
||||
sql_command_flags[SQLCOM_CREATE_TABLE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
|
||||
CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL |
|
||||
CF_AUTO_COMMIT_TRANS |
|
||||
CF_CAN_GENERATE_ROW_EVENTS;
|
||||
sql_command_flags[SQLCOM_CREATE_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_ALTER_TABLE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND |
|
||||
CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL;
|
||||
CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_TRUNCATE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND |
|
||||
CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL;
|
||||
CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_DROP_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_LOAD]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
|
||||
CF_PROTECT_AGAINST_GRL |
|
||||
CF_CAN_GENERATE_ROW_EVENTS;
|
||||
sql_command_flags[SQLCOM_CREATE_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL;
|
||||
sql_command_flags[SQLCOM_DROP_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL;
|
||||
sql_command_flags[SQLCOM_ALTER_DB_UPGRADE]= CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL;
|
||||
sql_command_flags[SQLCOM_ALTER_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL;
|
||||
sql_command_flags[SQLCOM_CREATE_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_DROP_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_ALTER_DB_UPGRADE]= CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_ALTER_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_RENAME_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_DROP_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_CREATE_VIEW]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
|
||||
@ -285,26 +281,18 @@ void init_update_queries(void)
|
||||
sql_command_flags[SQLCOM_CREATE_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_ALTER_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_DROP_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_CREATE_TRIGGER]= CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_DROP_TRIGGER]= CF_AUTO_COMMIT_TRANS;
|
||||
|
||||
sql_command_flags[SQLCOM_UPDATE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
|
||||
CF_PROTECT_AGAINST_GRL |
|
||||
CF_CAN_GENERATE_ROW_EVENTS;
|
||||
sql_command_flags[SQLCOM_UPDATE_MULTI]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
|
||||
CF_PROTECT_AGAINST_GRL |
|
||||
CF_CAN_GENERATE_ROW_EVENTS;
|
||||
sql_command_flags[SQLCOM_INSERT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
|
||||
CF_PROTECT_AGAINST_GRL |
|
||||
CF_CAN_GENERATE_ROW_EVENTS;
|
||||
sql_command_flags[SQLCOM_INSERT_SELECT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
|
||||
CF_PROTECT_AGAINST_GRL |
|
||||
CF_CAN_GENERATE_ROW_EVENTS;
|
||||
sql_command_flags[SQLCOM_DELETE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
|
||||
CF_PROTECT_AGAINST_GRL |
|
||||
CF_CAN_GENERATE_ROW_EVENTS;
|
||||
sql_command_flags[SQLCOM_DELETE_MULTI]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
|
||||
CF_PROTECT_AGAINST_GRL |
|
||||
CF_CAN_GENERATE_ROW_EVENTS;
|
||||
sql_command_flags[SQLCOM_REPLACE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
|
||||
CF_CAN_GENERATE_ROW_EVENTS;
|
||||
@ -366,20 +354,19 @@ void init_update_queries(void)
|
||||
CF_REEXECUTION_FRAGILE);
|
||||
|
||||
|
||||
sql_command_flags[SQLCOM_CREATE_USER]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL;
|
||||
sql_command_flags[SQLCOM_RENAME_USER]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL;
|
||||
sql_command_flags[SQLCOM_DROP_USER]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL;
|
||||
sql_command_flags[SQLCOM_CREATE_USER]= CF_CHANGES_DATA;
|
||||
sql_command_flags[SQLCOM_RENAME_USER]= CF_CHANGES_DATA;
|
||||
sql_command_flags[SQLCOM_DROP_USER]= CF_CHANGES_DATA;
|
||||
sql_command_flags[SQLCOM_GRANT]= CF_CHANGES_DATA;
|
||||
sql_command_flags[SQLCOM_REVOKE]= CF_CHANGES_DATA;
|
||||
sql_command_flags[SQLCOM_REVOKE_ALL]= CF_PROTECT_AGAINST_GRL;
|
||||
sql_command_flags[SQLCOM_OPTIMIZE]= CF_CHANGES_DATA;
|
||||
sql_command_flags[SQLCOM_CREATE_FUNCTION]= CF_CHANGES_DATA;
|
||||
sql_command_flags[SQLCOM_CREATE_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_CREATE_SPFUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_DROP_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_DROP_FUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_ALTER_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_ALTER_FUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_CREATE_PROCEDURE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_CREATE_SPFUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_DROP_PROCEDURE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_DROP_FUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_ALTER_PROCEDURE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_ALTER_FUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
|
||||
sql_command_flags[SQLCOM_INSTALL_PLUGIN]= CF_CHANGES_DATA;
|
||||
sql_command_flags[SQLCOM_UNINSTALL_PLUGIN]= CF_CHANGES_DATA;
|
||||
|
||||
@ -1111,7 +1098,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
|
||||
SHOW statements should not add the used tables to the list of tables
|
||||
used in a transaction.
|
||||
*/
|
||||
MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
|
||||
status_var_increment(thd->status_var.com_stat[SQLCOM_SHOW_FIELDS]);
|
||||
if (thd->copy_db_to(&db.str, &db.length))
|
||||
@ -1754,16 +1741,6 @@ bool sp_process_definer(THD *thd)
|
||||
/**
|
||||
Execute command saved in thd and lex->sql_command.
|
||||
|
||||
Before every operation that can request a write lock for a table
|
||||
wait if a global read lock exists. However do not wait if this
|
||||
thread has locked tables already. No new locks can be requested
|
||||
until the other locks are released. The thread that requests the
|
||||
global read lock waits for write locked tables to become unlocked.
|
||||
|
||||
Note that wait_if_global_read_lock() sets a protection against a new
|
||||
global read lock when it succeeds. This needs to be released by
|
||||
start_waiting_global_read_lock() after the operation.
|
||||
|
||||
@param thd Thread handle
|
||||
|
||||
@todo
|
||||
@ -1797,7 +1774,6 @@ mysql_execute_command(THD *thd)
|
||||
/* have table map for update for multi-update statement (BUG#37051) */
|
||||
bool have_table_map_for_update= FALSE;
|
||||
#endif
|
||||
/* Saved variable value */
|
||||
DBUG_ENTER("mysql_execute_command");
|
||||
#ifdef WITH_PARTITION_STORAGE_ENGINE
|
||||
thd->work_part_info= 0;
|
||||
@ -1989,17 +1965,6 @@ mysql_execute_command(THD *thd)
|
||||
thd->mdl_context.release_transactional_locks();
|
||||
}
|
||||
|
||||
/*
|
||||
Check if this command needs protection against the global read lock
|
||||
to avoid deadlock. See CF_PROTECT_AGAINST_GRL.
|
||||
start_waiting_global_read_lock() is called at the end of
|
||||
mysql_execute_command().
|
||||
*/
|
||||
if (((sql_command_flags[lex->sql_command] & CF_PROTECT_AGAINST_GRL) != 0) &&
|
||||
!thd->locked_tables_mode)
|
||||
if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE))
|
||||
goto error;
|
||||
|
||||
#ifndef DBUG_OFF
|
||||
if (lex->sql_command != SQLCOM_SET_OPTION)
|
||||
DEBUG_SYNC(thd,"before_execute_sql_command");
|
||||
@ -2074,10 +2039,6 @@ mysql_execute_command(THD *thd)
|
||||
if (res)
|
||||
break;
|
||||
|
||||
if (!thd->locked_tables_mode && lex->protect_against_global_read_lock &&
|
||||
thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE))
|
||||
break;
|
||||
|
||||
res= execute_sqlcom_select(thd, all_tables);
|
||||
break;
|
||||
}
|
||||
@ -2336,20 +2297,6 @@ case SQLCOM_PREPARE:
|
||||
create_info.default_table_charset= create_info.table_charset;
|
||||
create_info.table_charset= 0;
|
||||
}
|
||||
/*
|
||||
The create-select command will open and read-lock the select table
|
||||
and then create, open and write-lock the new table. If a global
|
||||
read lock steps in, we get a deadlock. The write lock waits for
|
||||
the global read lock, while the global read lock waits for the
|
||||
select table to be closed. So we wait until the global readlock is
|
||||
gone before starting both steps. Note that
|
||||
wait_if_global_read_lock() sets a protection against a new global
|
||||
read lock when it succeeds. This needs to be released by
|
||||
start_waiting_global_read_lock(). We protect the normal CREATE
|
||||
TABLE in the same way. That way we avoid that a new table is
|
||||
created during a global read lock.
|
||||
Protection against grl is covered by the CF_PROTECT_AGAINST_GRL flag.
|
||||
*/
|
||||
|
||||
#ifdef WITH_PARTITION_STORAGE_ENGINE
|
||||
{
|
||||
@ -3143,9 +3090,6 @@ end_with_restore_list:
|
||||
if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables,
|
||||
FALSE, UINT_MAX, FALSE))
|
||||
goto error;
|
||||
if (lex->protect_against_global_read_lock &&
|
||||
thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE))
|
||||
goto error;
|
||||
|
||||
thd->variables.option_bits|= OPTION_TABLE_LOCK;
|
||||
thd->in_lock_tables=1;
|
||||
@ -3801,13 +3745,22 @@ end_with_restore_list:
|
||||
Security_context *backup= NULL;
|
||||
LEX_USER *definer= thd->lex->definer;
|
||||
/*
|
||||
We're going to issue an implicit GRANT statement.
|
||||
It takes metadata locks and updates system tables.
|
||||
Make sure that sp_create_routine() did not leave any
|
||||
locks in the MDL context, so there is no risk to
|
||||
deadlock.
|
||||
We're going to issue an implicit GRANT statement so we close all
|
||||
open tables. We have to keep metadata locks as this ensures that
|
||||
this statement is atomic against concurent FLUSH TABLES WITH READ
|
||||
LOCK. Deadlocks which can arise due to fact that this implicit
|
||||
statement takes metadata locks should be detected by a deadlock
|
||||
detector in MDL subsystem and reported as errors.
|
||||
|
||||
No need to commit/rollback statement transaction, it's not started.
|
||||
|
||||
TODO: Long-term we should either ensure that implicit GRANT statement
|
||||
is written into binary log as a separate statement or make both
|
||||
creation of routine and implicit GRANT parts of one fully atomic
|
||||
statement.
|
||||
*/
|
||||
close_mysql_tables(thd);
|
||||
DBUG_ASSERT(thd->transaction.stmt.is_empty());
|
||||
close_thread_tables(thd);
|
||||
/*
|
||||
Check if the definer exists on slave,
|
||||
then use definer privilege to insert routine privileges to mysql.procs_priv.
|
||||
@ -4067,13 +4020,22 @@ create_sp_error:
|
||||
|
||||
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
||||
/*
|
||||
We're going to issue an implicit REVOKE statement.
|
||||
It takes metadata locks and updates system tables.
|
||||
Make sure that sp_create_routine() did not leave any
|
||||
locks in the MDL context, so there is no risk to
|
||||
deadlock.
|
||||
We're going to issue an implicit REVOKE statement so we close all
|
||||
open tables. We have to keep metadata locks as this ensures that
|
||||
this statement is atomic against concurent FLUSH TABLES WITH READ
|
||||
LOCK. Deadlocks which can arise due to fact that this implicit
|
||||
statement takes metadata locks should be detected by a deadlock
|
||||
detector in MDL subsystem and reported as errors.
|
||||
|
||||
No need to commit/rollback statement transaction, it's not started.
|
||||
|
||||
TODO: Long-term we should either ensure that implicit REVOKE statement
|
||||
is written into binary log as a separate statement or make both
|
||||
dropping of routine and implicit REVOKE parts of one fully atomic
|
||||
statement.
|
||||
*/
|
||||
close_mysql_tables(thd);
|
||||
DBUG_ASSERT(thd->transaction.stmt.is_empty());
|
||||
close_thread_tables(thd);
|
||||
|
||||
if (sp_result != SP_KEY_NOT_FOUND &&
|
||||
sp_automatic_privileges && !opt_noacl &&
|
||||
@ -4362,14 +4324,6 @@ error:
|
||||
res= TRUE;
|
||||
|
||||
finish:
|
||||
if (thd->global_read_lock.has_protection())
|
||||
{
|
||||
/*
|
||||
Release the protection against the global read lock and wake
|
||||
everyone, who might want to set a global read lock.
|
||||
*/
|
||||
thd->global_read_lock.start_waiting_global_read_lock(thd);
|
||||
}
|
||||
|
||||
DBUG_ASSERT(!thd->in_active_multi_stmt_transaction() ||
|
||||
thd->in_multi_stmt_transaction_mode());
|
||||
@ -4405,6 +4359,11 @@ finish:
|
||||
close_thread_tables(thd);
|
||||
thd_proc_info(thd, 0);
|
||||
|
||||
#ifndef DBUG_OFF
|
||||
if (lex->sql_command != SQLCOM_SET_OPTION && ! thd->in_sub_stmt)
|
||||
DEBUG_SYNC(thd, "execute_command_after_close_tables");
|
||||
#endif
|
||||
|
||||
if (stmt_causes_implicit_commit(thd, CF_IMPLICIT_COMMIT_END))
|
||||
{
|
||||
/* No transaction control allowed in sub-statements. */
|
||||
@ -4430,6 +4389,10 @@ finish:
|
||||
*/
|
||||
thd->mdl_context.release_transactional_locks();
|
||||
}
|
||||
else if (! thd->in_sub_stmt)
|
||||
{
|
||||
thd->mdl_context.release_statement_locks();
|
||||
}
|
||||
|
||||
DBUG_RETURN(res || thd->is_error());
|
||||
}
|
||||
@ -5899,7 +5862,8 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd,
|
||||
ptr->next_name_resolution_table= NULL;
|
||||
/* Link table in global list (all used tables) */
|
||||
lex->add_to_query_tables(ptr);
|
||||
ptr->mdl_request.init(MDL_key::TABLE, ptr->db, ptr->table_name, mdl_type);
|
||||
ptr->mdl_request.init(MDL_key::TABLE, ptr->db, ptr->table_name, mdl_type,
|
||||
MDL_TRANSACTION);
|
||||
DBUG_RETURN(ptr);
|
||||
}
|
||||
|
||||
|
@ -3172,7 +3172,6 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len)
|
||||
bool error;
|
||||
Statement stmt_backup;
|
||||
Query_arena *old_stmt_arena;
|
||||
MDL_ticket *mdl_savepoint= NULL;
|
||||
DBUG_ENTER("Prepared_statement::prepare");
|
||||
/*
|
||||
If this is an SQLCOM_PREPARE, we also increase Com_prepare_sql.
|
||||
@ -3244,7 +3243,7 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len)
|
||||
Marker used to release metadata locks acquired while the prepared
|
||||
statement is being checked.
|
||||
*/
|
||||
mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
|
||||
/*
|
||||
The only case where we should have items in the thd->free_list is
|
||||
|
@ -24,8 +24,7 @@
|
||||
#include "sql_table.h" // build_table_filename
|
||||
#include "sql_view.h" // mysql_frm_type, mysql_rename_view
|
||||
#include "sql_trigger.h"
|
||||
#include "lock.h" // wait_if_global_read_lock
|
||||
// start_waiting_global_read_lock
|
||||
#include "lock.h" // MYSQL_OPEN_SKIP_TEMPORARY
|
||||
#include "sql_base.h" // tdc_remove_table, lock_table_names,
|
||||
#include "sql_handler.h" // mysql_ha_rm_tables
|
||||
#include "datadict.h"
|
||||
@ -63,9 +62,6 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent)
|
||||
|
||||
mysql_ha_rm_tables(thd, table_list);
|
||||
|
||||
if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE))
|
||||
DBUG_RETURN(1);
|
||||
|
||||
if (logger.is_log_table_enabled(QUERY_LOG_GENERAL) ||
|
||||
logger.is_log_table_enabled(QUERY_LOG_SLOW))
|
||||
{
|
||||
@ -189,7 +185,6 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent)
|
||||
query_cache_invalidate3(thd, table_list, 0);
|
||||
|
||||
err:
|
||||
thd->global_read_lock.start_waiting_global_read_lock(thd);
|
||||
DBUG_RETURN(error || binlog_error);
|
||||
}
|
||||
|
||||
|
@ -671,7 +671,7 @@ mysqld_show_create(THD *thd, TABLE_LIST *table_list)
|
||||
Metadata locks taken during SHOW CREATE should be released when
|
||||
the statmement completes as it is an information statement.
|
||||
*/
|
||||
MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
|
||||
/* We want to preserve the tree for views. */
|
||||
thd->lex->view_prepare_mode= TRUE;
|
||||
@ -3186,7 +3186,7 @@ try_acquire_high_prio_shared_mdl_lock(THD *thd, TABLE_LIST *table,
|
||||
{
|
||||
bool error;
|
||||
table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name,
|
||||
MDL_SHARED_HIGH_PRIO);
|
||||
MDL_SHARED_HIGH_PRIO, MDL_TRANSACTION);
|
||||
|
||||
if (can_deadlock)
|
||||
{
|
||||
@ -7745,7 +7745,7 @@ bool show_create_trigger(THD *thd, const sp_name *trg_name)
|
||||
Metadata locks taken during SHOW CREATE TRIGGER should be released when
|
||||
the statement completes as it is an information statement.
|
||||
*/
|
||||
MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
|
||||
/*
|
||||
Open the table by name in order to load Table_triggers_list object.
|
||||
|
@ -23,9 +23,7 @@
|
||||
#include "sql_parse.h" // test_if_data_home_dir
|
||||
#include "sql_cache.h" // query_cache_*
|
||||
#include "sql_base.h" // open_table_uncached, lock_table_names
|
||||
#include "lock.h" // wait_if_global_read_lock
|
||||
// start_waiting_global_read_lock,
|
||||
// mysql_unlock_tables
|
||||
#include "lock.h" // mysql_unlock_tables
|
||||
#include "strfunc.h" // find_type2, find_set
|
||||
#include "sql_view.h" // view_checksum
|
||||
#include "sql_truncate.h" // regenerate_locked_table
|
||||
@ -1855,21 +1853,10 @@ bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists,
|
||||
DBUG_ENTER("mysql_rm_table");
|
||||
|
||||
/* mark for close and remove all cached entries */
|
||||
|
||||
if (!drop_temporary)
|
||||
{
|
||||
if (!thd->locked_tables_mode &&
|
||||
thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE))
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
|
||||
thd->push_internal_handler(&err_handler);
|
||||
error= mysql_rm_table_part2(thd, tables, if_exists, drop_temporary, 0, 0);
|
||||
thd->pop_internal_handler();
|
||||
|
||||
if (thd->global_read_lock.has_protection())
|
||||
thd->global_read_lock.start_waiting_global_read_lock(thd);
|
||||
|
||||
if (error)
|
||||
DBUG_RETURN(TRUE);
|
||||
my_ok(thd);
|
||||
@ -5592,7 +5579,6 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
|
||||
TABLE *table, *new_table= 0;
|
||||
MDL_ticket *mdl_ticket;
|
||||
MDL_request target_mdl_request;
|
||||
bool has_target_mdl_lock= FALSE;
|
||||
int error= 0;
|
||||
char tmp_name[80],old_name[32],new_name_buff[FN_REFLEN + 1];
|
||||
char new_alias_buff[FN_REFLEN], *table_name, *db, *new_alias, *alias;
|
||||
@ -5754,7 +5740,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
|
||||
else
|
||||
{
|
||||
target_mdl_request.init(MDL_key::TABLE, new_db, new_name,
|
||||
MDL_EXCLUSIVE);
|
||||
MDL_EXCLUSIVE, MDL_TRANSACTION);
|
||||
/*
|
||||
Global intention exclusive lock must have been already acquired when
|
||||
table to be altered was open, so there is no need to do it here.
|
||||
@ -5772,7 +5758,6 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
DEBUG_SYNC(thd, "locked_table_name");
|
||||
has_target_mdl_lock= TRUE;
|
||||
/*
|
||||
Table maybe does not exist, but we got an exclusive lock
|
||||
on the name, now we can safely try to find out for sure.
|
||||
@ -5959,10 +5944,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
|
||||
along with the implicit commit.
|
||||
*/
|
||||
if (new_name != table_name || new_db != db)
|
||||
{
|
||||
thd->mdl_context.release_lock(target_mdl_request.ticket);
|
||||
thd->mdl_context.release_all_locks_for_name(mdl_ticket);
|
||||
}
|
||||
else
|
||||
mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE);
|
||||
}
|
||||
@ -6667,10 +6649,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
|
||||
thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)
|
||||
{
|
||||
if ((new_name != table_name || new_db != db))
|
||||
{
|
||||
thd->mdl_context.release_lock(target_mdl_request.ticket);
|
||||
thd->mdl_context.release_all_locks_for_name(mdl_ticket);
|
||||
}
|
||||
else
|
||||
mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE);
|
||||
}
|
||||
@ -6731,8 +6710,6 @@ err:
|
||||
alter_info->datetime_field->field_name);
|
||||
thd->abort_on_warning= save_abort_on_warning;
|
||||
}
|
||||
if (has_target_mdl_lock)
|
||||
thd->mdl_context.release_lock(target_mdl_request.ticket);
|
||||
|
||||
DBUG_RETURN(TRUE);
|
||||
|
||||
@ -6744,9 +6721,6 @@ err_with_mdl:
|
||||
tables and release the exclusive metadata lock.
|
||||
*/
|
||||
thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0);
|
||||
if (has_target_mdl_lock)
|
||||
thd->mdl_context.release_lock(target_mdl_request.ticket);
|
||||
|
||||
thd->mdl_context.release_all_locks_for_name(mdl_ticket);
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
|
@ -24,8 +24,6 @@
|
||||
#include "parse_file.h"
|
||||
#include "sp.h"
|
||||
#include "sql_base.h" // find_temporary_table
|
||||
#include "lock.h" // wait_if_global_read_lock,
|
||||
// start_waiting_global_read_lock
|
||||
#include "sql_show.h" // append_definer, append_identifier
|
||||
#include "sql_table.h" // build_table_filename,
|
||||
// check_n_cut_mysql50_prefix
|
||||
@ -391,15 +389,6 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
|
||||
/*
|
||||
We don't want perform our operations while global read lock is held
|
||||
so we have to wait until its end and then prevent it from occurring
|
||||
again until we are done, unless we are under lock tables.
|
||||
*/
|
||||
if (!thd->locked_tables_mode &&
|
||||
thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE))
|
||||
DBUG_RETURN(TRUE);
|
||||
|
||||
if (!create)
|
||||
{
|
||||
bool if_exists= thd->lex->drop_if_exists;
|
||||
@ -547,9 +536,6 @@ end:
|
||||
if (!create)
|
||||
thd->lex->restore_backup_query_tables_list(&backup);
|
||||
|
||||
if (thd->global_read_lock.has_protection())
|
||||
thd->global_read_lock.start_waiting_global_read_lock(thd);
|
||||
|
||||
if (!result)
|
||||
my_ok(thd);
|
||||
|
||||
|
@ -1026,9 +1026,17 @@ int mysql_multi_update_prepare(THD *thd)
|
||||
/* following need for prepared statements, to run next time multi-update */
|
||||
thd->lex->sql_command= SQLCOM_UPDATE_MULTI;
|
||||
|
||||
/* open tables and create derived ones, but do not lock and fill them */
|
||||
/*
|
||||
Open tables and create derived ones, but do not lock and fill them yet.
|
||||
|
||||
During prepare phase acquire only S metadata locks instead of SW locks to
|
||||
keep prepare of multi-UPDATE compatible with concurrent LOCK TABLES WRITE
|
||||
and global read lock.
|
||||
*/
|
||||
if ((original_multiupdate &&
|
||||
open_tables(thd, &table_list, &table_count, 0)) ||
|
||||
open_tables(thd, &table_list, &table_count,
|
||||
(thd->stmt_arena->is_stmt_prepare() ?
|
||||
MYSQL_OPEN_FORCE_SHARED_MDL : 0))) ||
|
||||
mysql_handle_derived(lex, &mysql_derived_prepare))
|
||||
DBUG_RETURN(TRUE);
|
||||
/*
|
||||
|
@ -22,7 +22,7 @@
|
||||
#include "sql_base.h" // find_table_in_global_list, lock_table_names
|
||||
#include "sql_parse.h" // sql_parse
|
||||
#include "sql_cache.h" // query_cache_*
|
||||
#include "lock.h" // wait_if_global_read_lock
|
||||
#include "lock.h" // MYSQL_OPEN_SKIP_TEMPORARY
|
||||
#include "sql_show.h" // append_identifier
|
||||
#include "sql_table.h" // build_table_filename
|
||||
#include "sql_db.h" // mysql_opt_change_db, mysql_change_db
|
||||
@ -649,13 +649,6 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views,
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE))
|
||||
{
|
||||
res= TRUE;
|
||||
goto err;
|
||||
}
|
||||
|
||||
res= mysql_register_view(thd, view, mode);
|
||||
|
||||
if (mysql_bin_log.is_open())
|
||||
@ -704,7 +697,6 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views,
|
||||
|
||||
if (mode != VIEW_CREATE_NEW)
|
||||
query_cache_invalidate3(thd, view, 0);
|
||||
thd->global_read_lock.start_waiting_global_read_lock(thd);
|
||||
if (res)
|
||||
goto err;
|
||||
|
||||
|
@ -7385,7 +7385,6 @@ select_lock_type:
|
||||
LEX *lex=Lex;
|
||||
lex->current_select->set_lock_for_tables(TL_WRITE);
|
||||
lex->safe_to_cache_query=0;
|
||||
lex->protect_against_global_read_lock= TRUE;
|
||||
}
|
||||
| LOCK_SYM IN_SYM SHARE_SYM MODE_SYM
|
||||
{
|
||||
@ -13182,9 +13181,6 @@ table_lock:
|
||||
MDL_SHARED_NO_READ_WRITE :
|
||||
MDL_SHARED_READ)))
|
||||
MYSQL_YYABORT;
|
||||
/* If table is to be write locked, protect from a impending GRL. */
|
||||
if (lock_for_write)
|
||||
Lex->protect_against_global_read_lock= TRUE;
|
||||
}
|
||||
;
|
||||
|
||||
|
@ -5219,7 +5219,8 @@ void init_mdl_requests(TABLE_LIST *table_list)
|
||||
table_list->mdl_request.init(MDL_key::TABLE,
|
||||
table_list->db, table_list->table_name,
|
||||
table_list->lock_type >= TL_WRITE_ALLOW_WRITE ?
|
||||
MDL_SHARED_WRITE : MDL_SHARED_READ);
|
||||
MDL_SHARED_WRITE : MDL_SHARED_READ,
|
||||
MDL_TRANSACTION);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1384,7 +1384,8 @@ struct TABLE_LIST
|
||||
lock_type= lock_type_arg;
|
||||
mdl_request.init(MDL_key::TABLE, db, table_name,
|
||||
(lock_type >= TL_WRITE_ALLOW_WRITE) ?
|
||||
MDL_SHARED_WRITE : MDL_SHARED_READ);
|
||||
MDL_SHARED_WRITE : MDL_SHARED_READ,
|
||||
MDL_TRANSACTION);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "sql_priv.h"
|
||||
#include "transaction.h"
|
||||
#include "rpl_handler.h"
|
||||
#include "debug_sync.h" // DEBUG_SYNC
|
||||
|
||||
/* Conditions under which the transaction state must not change. */
|
||||
static bool trans_check(THD *thd)
|
||||
@ -391,15 +392,15 @@ bool trans_savepoint(THD *thd, LEX_STRING name)
|
||||
thd->transaction.savepoints= newsv;
|
||||
|
||||
/*
|
||||
Remember the last acquired lock before the savepoint was set.
|
||||
This is used as a marker to only release locks acquired after
|
||||
Remember locks acquired before the savepoint was set.
|
||||
They are used as a marker to only release locks acquired after
|
||||
the setting of this savepoint.
|
||||
Note: this works just fine if we're under LOCK TABLES,
|
||||
since mdl_savepoint() is guaranteed to be beyond
|
||||
the last locked table. This allows to release some
|
||||
locks acquired during LOCK TABLES.
|
||||
*/
|
||||
newsv->mdl_savepoint = thd->mdl_context.mdl_savepoint();
|
||||
newsv->mdl_savepoint= thd->mdl_context.mdl_savepoint();
|
||||
|
||||
DBUG_RETURN(FALSE);
|
||||
}
|
||||
@ -645,17 +646,31 @@ bool trans_xa_commit(THD *thd)
|
||||
}
|
||||
else if (xa_state == XA_PREPARED && thd->lex->xa_opt == XA_NONE)
|
||||
{
|
||||
if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, FALSE))
|
||||
MDL_request mdl_request;
|
||||
|
||||
/*
|
||||
Acquire metadata lock which will ensure that COMMIT is blocked
|
||||
by active FLUSH TABLES WITH READ LOCK (and vice versa COMMIT in
|
||||
progress blocks FTWRL).
|
||||
|
||||
We allow FLUSHer to COMMIT; we assume FLUSHer knows what it does.
|
||||
*/
|
||||
mdl_request.init(MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE,
|
||||
MDL_TRANSACTION);
|
||||
|
||||
if (thd->mdl_context.acquire_lock(&mdl_request,
|
||||
thd->variables.lock_wait_timeout))
|
||||
{
|
||||
ha_rollback_trans(thd, TRUE);
|
||||
my_error(ER_XAER_RMERR, MYF(0));
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_SYNC(thd, "trans_xa_commit_after_acquire_commit_lock");
|
||||
|
||||
res= test(ha_commit_one_phase(thd, 1));
|
||||
if (res)
|
||||
my_error(ER_XAER_RMERR, MYF(0));
|
||||
thd->global_read_lock.start_waiting_global_read_lock(thd);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
Loading…
x
Reference in New Issue
Block a user