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:
Dmitry Lenev 2010-11-11 20:11:05 +03:00
parent aee8fce233
commit 378cdc58c1
75 changed files with 5492 additions and 1243 deletions

View 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

View 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

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -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';

View File

@ -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

View File

@ -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

View File

@ -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';

View File

@ -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

View File

@ -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';

View File

@ -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;

View File

@ -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';

View File

@ -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

View File

@ -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';

View File

@ -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;

View File

@ -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;

View File

@ -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";

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@
--loose-debug=+d,make_global_read_lock_block_commit_loop

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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,&not_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,

View File

@ -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);
};

View File

@ -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);
fields[ET_FIELD_LAST_EXECUTED]->set_notnull();
fields[ET_FIELD_LAST_EXECUTED]->store_time(&time,
MYSQL_TIMESTAMP_DATETIME);
}
if (update_status)
{
fields[ET_FIELD_LAST_EXECUTED]->store_time(&time, MYSQL_TIMESTAMP_DATETIME);
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])))
{

View File

@ -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

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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();

View File

@ -7382,23 +7382,21 @@ 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)
{
// 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
/* 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,
@ -7414,7 +7412,6 @@ int ndbcluster_find_files(handlerton *hton, THD *thd,
/* Clear error message that is returned when table is deleted */
thd->clear_error();
}
}
/* Lock mutex before creating .FRM files. */
/* Create new files. */

View File

@ -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,13 +1166,29 @@ 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)
{
/*
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 &&
opt_readonly &&
!(thd->security_ctx->master_access & SUPER_ACL) &&
@ -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)

View File

@ -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
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;
thd->exit_cond(old_message); // this unlocks LOCK_global_read_lock
DBUG_RETURN(error);
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);
}
/**

View File

@ -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 */

View File

@ -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;);

View File

@ -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;
for (i= 0; i < MDL_DURATION_END; i++)
{
enum_mdl_duration duration= (enum_mdl_duration)((mdl_request->duration+i) %
MDL_DURATION_END);
Ticket_iterator it(m_tickets[duration]);
while ((ticket= it++))
{
if (ticket == m_trans_sentinel)
*is_transactional= FALSE;
if (mdl_request->key.is_equal(&ticket->m_lock->key) &&
ticket->has_stronger_or_equal_type(mdl_request->type))
break;
}
{
*result_duration= duration;
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,37 +1664,75 @@ 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)
{
Ticket_iterator it(m_granted);
MDL_ticket *conflicting_ticket;
while ((conflicting_ticket= it++))
{
/* Only try to abort locks on which we back off. */
if (conflicting_ticket->get_type() < MDL_SHARED_NO_WRITE)
if (conflicting_ticket->get_ctx() != ctx &&
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 */
/*
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,
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 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, &not_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,7 +2479,16 @@ 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;
}
@ -2445,57 +2496,133 @@ void MDL_context::release_transactional_locks()
/**
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;
}
/* 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
View File

@ -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);

View File

@ -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},

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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,

View File

@ -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;

View File

@ -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;
};

View File

@ -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)

View File

@ -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 */

View File

@ -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;

View File

@ -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;
}

View File

@ -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 */

View File

@ -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;
}

View File

@ -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

View File

@ -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()

View File

@ -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);
}

View File

@ -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

View File

@ -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);
}

View File

@ -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.

View File

@ -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);
}

View File

@ -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);

View File

@ -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);
/*

View File

@ -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;

View File

@ -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;
}
;

View File

@ -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);
}

View File

@ -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);
}
/*

View File

@ -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