diff options
75 files changed, 5493 insertions, 1244 deletions
diff --git a/mysql-test/include/check_ftwrl_compatible.inc b/mysql-test/include/check_ftwrl_compatible.inc new file mode 100644 index 00000000000..76c1915957c --- /dev/null +++ b/mysql-test/include/check_ftwrl_compatible.inc @@ -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 diff --git a/mysql-test/include/check_ftwrl_incompatible.inc b/mysql-test/include/check_ftwrl_incompatible.inc new file mode 100644 index 00000000000..56deef8e92f --- /dev/null +++ b/mysql-test/include/check_ftwrl_incompatible.inc @@ -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 diff --git a/mysql-test/include/handler.inc b/mysql-test/include/handler.inc index b86d5d9287f..57d368960bf 100644 --- a/mysql-test/include/handler.inc +++ b/mysql-test/include/handler.inc @@ -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; diff --git a/mysql-test/include/wait_show_condition.inc b/mysql-test/include/wait_show_condition.inc index f683ca7b47b..07bde560f20 100644 --- a/mysql-test/include/wait_show_condition.inc +++ b/mysql-test/include/wait_show_condition.inc @@ -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; diff --git a/mysql-test/r/delayed.result b/mysql-test/r/delayed.result index 77aa0d49407..d9082914d05 100644 --- a/mysql-test/r/delayed.result +++ b/mysql-test/r/delayed.result @@ -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 diff --git a/mysql-test/r/events_2.result b/mysql-test/r/events_2.result index 530d8559f11..66ec00d7357 100644 --- a/mysql-test/r/events_2.result +++ b/mysql-test/r/events_2.result @@ -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 diff --git a/mysql-test/r/flush.result b/mysql-test/r/flush.result index ced8306c3ab..b1e2e48eca8 100644 --- a/mysql-test/r/flush.result +++ b/mysql-test/r/flush.result @@ -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; diff --git a/mysql-test/r/flush_read_lock.result b/mysql-test/r/flush_read_lock.result new file mode 100644 index 00000000000..2b1071a92b2 --- /dev/null +++ b/mysql-test/r/flush_read_lock.result @@ -0,0 +1,1683 @@ +# FTWRL takes two global metadata locks -- a global shared +# metadata lock and the commit blocker lock. +# The first lock prevents DDL from taking place. +# Let's say that all DDL statements that take metadata +# locks form class #1 -- incompatible with FTWRL because +# take incompatible MDL table locks. +# The first global lock doesn't, however, prevent standalone +# COMMITs (or implicit COMMITs) from taking place, since a +# COMMIT doesn't take table locks. It doesn't prevent +# DDL on temporary tables either, since they don't +# take any table locks either. +# Most DDL statements do not perform an implicit commit +# if operate on a temporary table. Examples are CREATE +# TEMPORARY TABLE and DROP TEMPORARY TABLE. +# Thus, these DDL statements can go through in presence +# of FTWRL. This is class #2 -- compatible because +# do not take incompatible MDL locks and do not issue +# implicit commit.. +# (Although these operations do not commit, their effects +# cannot be rolled back either.) +# ALTER TABLE, ANALYZE, OPTIMIZE and some others always +# issue an implicit commit, even if its argument is a +# temporary table. +# *Howewer* an implicit commit is a no-op if all engines +# used since the start of transactiona are non- +# transactional. Thus, for non-transactional engines, +# these operations are not blocked by FTWRL. +# This is class #3 -- compatible because do not take +# MDL table locks and are non-transactional. +# On the contrary, for transactional engines, there +# is always a commit, regardless of whether a table +# is temporary or not. Thus, for example, ALTER TABLE +# for a transactional engine will wait for FTWRL, +# even if the subject table is temporary. +# Thus ALTER TABLE <temporary> is incompatible +# with FTWRL. This is class #4 -- incompatible +# becuase issue implicit COMMIT which is not a no-op. +# Finally, there are administrative statements (such as +# RESET SLAVE) that do not take any locks and do not +# issue COMMIT. +# This is class #5. +# The goal of this coverage is to test statements +# of all classes. +# @todo: documents the effects of @@autocommit, +# DML and temporary transactional tables. +# Use MyISAM engine for the most of the tables +# used in this test in order to be able to +# check that DDL statements on temporary tables +# are compatible with FTRWL. +drop tables if exists t1_base, t2_base, t3_trans; +drop tables if exists tm_base, tm_base_temp; +drop database if exists mysqltest1; +# We're going to test ALTER DATABASE UPGRADE +drop database if exists `#mysql50#mysqltest-2`; +drop procedure if exists p1; +drop function if exists f1; +drop view if exists v1; +drop procedure if exists p2; +drop function if exists f2_base; +drop function if exists f2_temp; +drop event if exists e1; +drop event if exists e2; +create table t1_base(i int) engine=myisam; +create table t2_base(j int) engine=myisam; +create table t3_trans(i int) engine=innodb; +create temporary table t1_temp(i int) engine=myisam; +create temporary table t2_temp(j int) engine=myisam; +create temporary table t3_temp_trans(i int) engine=innodb; +create database mysqltest1; +create database `#mysql50#mysqltest-2`; +create procedure p1() begin end; +create function f1() returns int return 0; +create view v1 as select 1 as i; +create procedure p2(i int) begin end; +create function f2_base() returns int +begin +insert into t1_base values (1); +return 0; +end| +create function f2_temp() returns int +begin +insert into t1_temp values (1); +return 0; +end| +create event e1 on schedule every 1 minute do begin end; +# +# Test compatibility of FLUSH TABLES WITH READ LOCK +# with various statements. +# +# These tests don't cover some classes of statements: +# - Replication-related - CHANGE MASTER TO, START/STOP SLAVE and etc +# (all compatible with FTWRL). +# - Plugin-related - INSTALL/UNINSTALL (incompatible with FTWRL, +# require plugin support). +# +# 1) ALTER variants. +# +# 1.1) ALTER TABLE +# +# 1.1.a) For base table should be incompatible with FTWRL. +# +Success: Was not able to run 'alter table t1_base add column c1 int' under FTWRL. +Success: 'alter table t1_base add column c1 int' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'alter table t1_base add column c1 int' is active in another connection. +# +# 1.1.b) For a temporary table should be compatible with FTWRL. +# +Success: Was able to run 'alter table t1_temp add column c1 int' under FTWRL. +Success: Was able to run 'alter table t1_temp add column c1 int' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'alter table t1_temp add column c1 int' was active in another connection. +# +# 1.2) ALTER DATABASE should be incompatible with FTWRL. +# +Success: Was not able to run 'alter database mysqltest1 default character set utf8' under FTWRL. +Success: 'alter database mysqltest1 default character set utf8' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'alter database mysqltest1 default character set utf8' is active in another connection. +# +# 1.3) ALTER DATABASE UPGRADE DATA DIRECTORY NAME should be +# incompatible with FTWRL. +# +Success: Was not able to run 'alter database `#mysql50#mysqltest-2` upgrade data directory name' under FTWRL. +Success: 'alter database `#mysql50#mysqltest-2` upgrade data directory name' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'alter database `#mysql50#mysqltest-2` upgrade data directory name' is active in another connection. +# +# 1.4) ALTER PROCEDURE should be incompatible with FTWRL. +# +Success: Was not able to run 'alter procedure p1 comment 'a'' under FTWRL. +Success: 'alter procedure p1 comment 'a'' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'alter procedure p1 comment 'a'' is active in another connection. +# +# 1.5) ALTER FUNCTION should be incompatible with FTWRL. +# +Success: Was not able to run 'alter function f1 comment 'a'' under FTWRL. +Success: 'alter function f1 comment 'a'' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'alter function f1 comment 'a'' is active in another connection. +# +# 1.6) ALTER VIEW should be incompatible with FTWRL. +# +Success: Was not able to run 'alter view v1 as select 2 as j' under FTWRL. +Success: 'alter view v1 as select 2 as j' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'alter view v1 as select 2 as j' is active in another connection. +# +# 1.7) ALTER EVENT should be incompatible with FTWRL. +# +Success: Was not able to run 'alter event e1 comment 'test'' under FTWRL. +Success: 'alter event e1 comment 'test'' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'alter event e1 comment 'test'' is active in another connection. +# +# 1.x) The rest of ALTER statements (ALTER TABLESPACE, +# ALTER LOGFILE GROUP and ALTER SERVER) are too +# special to be tested here. +# +# +# 2) ANALYZE TABLE statement is compatible with FTWRL. +# See Bug#43336 ANALYZE and OPTIMIZE do not honour +# --read-only for a discussion why. +# +Success: Was able to run 'analyze table t1_base' under FTWRL. +Success: Was able to run 'analyze table t1_base' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'analyze table t1_base' was active in another connection. +# +# 3) BEGIN, ROLLBACK and COMMIT statements. +# BEGIN and ROLLBACK are compatible with FTWRL. +# COMMIT is not. +# +# We need a special test for these statements as +# FTWRL commits a transaction and because COMMIT +# is handled in a special way. +flush tables with read lock; +begin; +# ROLLBACK is allowed under FTWRL although there +# no much sense in it. FTWRL commits any previous +# changes and doesn't allows any DML after it. +# So such a ROLLBACK is always a no-op. +rollback; +# Although COMMIT is incompatible with FTWRL in +# other senses it is still allowed under FTWRL. +# This fact relied upon by some versions of +# innobackup tool. +# Similarly to ROLLBACK it is a no-op in this situation. +commit; +unlock tables; +# Check that BEGIN/ROLLBACK are not blocked and +# COMMIT is blocked by active FTWRL in another +# connection. +# +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +begin; +# Switching to connection 'con1'. +unlock tables; +# Switching to connection 'default'. +# Do some work so ROLLBACK is not a no-op. +insert into t3_trans values (1); +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +rollback; +# Switching to connection 'con1'. +unlock tables; +# Switching to connection 'default'. +begin; +# Do some work so COMMIT is not a no-op. +insert into t3_trans values (1); +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +# Send: +commit; +# Switching to connection 'con1'. +# Wait until COMMIT is blocked. +unlock tables; +# Switching to connection 'default'. +# Reap COMMIT. +delete from t3_trans; +# +# Check that COMMIT blocks FTWRL in another connection. +begin; +insert into t3_trans values (1); +set debug_sync='RESET'; +set debug_sync='ha_commit_trans_after_acquire_commit_lock SIGNAL parked WAIT_FOR go'; +commit; +# Switching to connection 'con1'. +set debug_sync='now WAIT_FOR parked'; +flush tables with read lock; +# Switching to connection 'con2'. +# Wait until FTWRL is blocked. +set debug_sync='now SIGNAL go'; +# Switching to connection 'default'. +# Reap COMMIT. +# Switching to connection 'con1'. +# Reap FTWRL. +unlock tables; +# Switching to connection 'default'. +delete from t3_trans; +set debug_sync= "RESET"; +# We don't run similar test for BEGIN and ROLLBACK as +# they release metadata locks in non-standard place. +# +# 4) BINLOG statement should be incompatible with FTWRL. +# +# +# Provide format description BINLOG statement first. +BINLOG ' +MfmqTA8BAAAAZwAAAGsAAAABAAQANS41LjctbTMtZGVidWctbG9nAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAx+apMEzgNAAgAEgAEBAQEEgAAVAAEGggAAAAICAgCAA== +'; +# Now test compatibility for BINLOG statement which is +# equivalent to INSERT INTO t1_base VALUES (1). +# Skip last part of compatibility testing as this statement +# releases metadata locks in non-standard place. +Success: Was not able to run 'BINLOG ' +MfmqTBMBAAAALgAAAN0AAAAAACgAAAAAAAEABHRlc3QAB3QxX2Jhc2UAAQMAAQ== +MfmqTBcBAAAAIgAAAP8AAAAAACgAAAAAAAEAAf/+AQAAAA== +'' under FTWRL. +Success: 'BINLOG ' +MfmqTBMBAAAALgAAAN0AAAAAACgAAAAAAAEABHRlc3QAB3QxX2Jhc2UAAQMAAQ== +MfmqTBcBAAAAIgAAAP8AAAAAACgAAAAAAAEAAf/+AQAAAA== +'' is blocked by FTWRL active in another connection. +# +# 5) CALL statement. This statement uses resources in two +# ways: through expressions used as parameters and through +# sub-statements. This test covers only usage through +# parameters as sub-statements do locking individually. +# +# 5.a) In simple cases a parameter expression should be +# compatible with FTWRL. +# Skip last part of compatibility testing as this statement +# releases metadata locks in non-standard place. +Success: Was able to run 'call p2((select count(*) from t1_base))' under FTWRL. +Success: Was able to run 'call p2((select count(*) from t1_base))' with FTWRL active in another connection. +# +# 5.b) In case when an expression uses function which updates +# base tables CALL should be incompatible with FTWRL. +# +# Skip last part of compatibility testing as this statement +# releases metadata locks in non-standard place. +Success: Was not able to run 'call p2(f2_base())' under FTWRL. +Success: 'call p2(f2_base())' is blocked by FTWRL active in another connection. +# +# 5.c) If function used as argument updates temporary tables +# CALL statement should be compatible with FTWRL. +# +# Skip last part of compatibility testing as this statement +# releases metadata locks in non-standard place. +Success: Was able to run 'call p2(f2_temp())' under FTWRL. +Success: Was able to run 'call p2(f2_temp())' with FTWRL active in another connection. +# +# 6) CHECK TABLE statement is compatible with FTWRL. +# +Success: Was able to run 'check table t1_base' under FTWRL. +Success: Was able to run 'check table t1_base' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'check table t1_base' was active in another connection. +# +# 7) CHECKSUM TABLE statement is compatible with FTWRL. +# +Success: Was able to run 'checksum table t1_base' under FTWRL. +Success: Was able to run 'checksum table t1_base' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'checksum table t1_base' was active in another connection. +# +# 8) CREATE variants. +# +# 8.1) CREATE TABLE statement. +# +# 8.1.a) CREATE TABLE is incompatible with FTWRL when +# base table is created. +Success: Was not able to run 'create table t3_base(i int)' under FTWRL. +Success: 'create table t3_base(i int)' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'create table t3_base(i int)' is active in another connection. +# 8.1.b) CREATE TABLE is compatible with FTWRL when +# temporary table is created. +Success: Was able to run 'create temporary table t3_temp(i int)' under FTWRL. +Success: Was able to run 'create temporary table t3_temp(i int)' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'create temporary table t3_temp(i int)' was active in another connection. +# 8.1.c) CREATE TABLE LIKE is incompatible with FTWRL when +# base table is created. +Success: Was not able to run 'create table t3_base like t1_temp' under FTWRL. +Success: 'create table t3_base like t1_temp' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'create table t3_base like t1_temp' is active in another connection. +# 8.1.d) CREATE TABLE LIKE is compatible with FTWRL when +# temporary table is created. +Success: Was able to run 'create temporary table t3_temp like t1_base' under FTWRL. +Success: Was able to run 'create temporary table t3_temp like t1_base' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'create temporary table t3_temp like t1_base' was active in another connection. +# 8.1.e) CREATE TABLE SELECT is incompatible with FTWRL when +# base table is created. +Success: Was not able to run 'create table t3_base select 1 as i' under FTWRL. +Success: 'create table t3_base select 1 as i' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'create table t3_base select 1 as i' is active in another connection. +# 8.1.f) CREATE TABLE SELECT is compatible with FTWRL when +# temporary table is created. +Success: Was able to run 'create temporary table t3_temp select 1 as i' under FTWRL. +Success: Was able to run 'create temporary table t3_temp select 1 as i' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'create temporary table t3_temp select 1 as i' was active in another connection. +# 8.2) CREATE INDEX statement. +# +# 8.2.a) CREATE INDEX is incompatible with FTWRL when +# applied to base table. +Success: Was not able to run 'create index i on t1_base (i)' under FTWRL. +Success: 'create index i on t1_base (i)' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'create index i on t1_base (i)' is active in another connection. +# 8.2.b) CREATE INDEX is compatible with FTWRL when +# applied to temporary table. +Success: Was able to run 'create index i on t1_temp (i)' under FTWRL. +Success: Was able to run 'create index i on t1_temp (i)' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'create index i on t1_temp (i)' was active in another connection. +# +# 8.3) CREATE DATABASE is incompatible with FTWRL. +# +Success: Was not able to run 'create database mysqltest2' under FTWRL. +Success: 'create database mysqltest2' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'create database mysqltest2' is active in another connection. +# +# 8.4) CREATE VIEW is incompatible with FTWRL. +# +Success: Was not able to run 'create view v2 as select 1 as j' under FTWRL. +Success: 'create view v2 as select 1 as j' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'create view v2 as select 1 as j' is active in another connection. +# +# 8.5) CREATE TRIGGER is incompatible with FTWRL. +# +Success: Was not able to run 'create trigger t1_bi before insert on t1_base for each row begin end' under FTWRL. +Success: 'create trigger t1_bi before insert on t1_base for each row begin end' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'create trigger t1_bi before insert on t1_base for each row begin end' is active in another connection. +# +# 8.6) CREATE FUNCTION is incompatible with FTWRL. +# +Success: Was not able to run 'create function f2() returns int return 0' under FTWRL. +Success: 'create function f2() returns int return 0' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'create function f2() returns int return 0' is active in another connection. +# +# 8.7) CREATE PROCEDURE is incompatible with FTWRL. +# +Success: Was not able to run 'create procedure p3() begin end' under FTWRL. +Success: 'create procedure p3() begin end' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'create procedure p3() begin end' is active in another connection. +# +# 8.8) CREATE EVENT should be incompatible with FTWRL. +# +Success: Was not able to run 'create event e2 on schedule every 1 minute do begin end' under FTWRL. +Success: 'create event e2 on schedule every 1 minute do begin end' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'create event e2 on schedule every 1 minute do begin end' is active in another connection. +# +# 8.9) CREATE USER should be incompatible with FTWRL. +# +Success: Was not able to run 'create user mysqltest_u1' under FTWRL. +Success: 'create user mysqltest_u1' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'create user mysqltest_u1' is active in another connection. +# +# 8.x) The rest of CREATE variants (CREATE LOGFILE GROUP, +# CREATE TABLESPACE and CREATE SERVER) are too special +# to test here. +# +# +# 9) PREPARE, EXECUTE and DEALLOCATE PREPARE statements. +# +# 9.1) PREPARE statement is compatible with FTWRL as it +# doesn't change any data. +# +# 9.1.a) Prepare of simple INSERT statement. +# +# Skip last part of compatibility testing as this statement +# releases metadata locks in non-standard place. +Success: Was able to run 'prepare stmt1 from 'insert into t1_base values (1)'' under FTWRL. +Success: Was able to run 'prepare stmt1 from 'insert into t1_base values (1)'' with FTWRL active in another connection. +# +# 9.1.b) Prepare of multi-UPDATE. At some point such statements +# tried to acquire thr_lock.c locks during prepare phase. +# This no longer happens and thus it is compatible with +# FTWRL. +# Skip last part of compatibility testing as this statement +# releases metadata locks in non-standard place. +Success: Was able to run 'prepare stmt1 from 'update t1_base, t2_base set t1_base.i= 1 where t1_base.i = t2_base.j'' under FTWRL. +Success: Was able to run 'prepare stmt1 from 'update t1_base, t2_base set t1_base.i= 1 where t1_base.i = t2_base.j'' with FTWRL active in another connection. +# +# 9.1.c) Prepare of multi-DELETE. Again PREPARE of such +# statement should be compatible with FTWRL. +# Skip last part of compatibility testing as this statement +# releases metadata locks in non-standard place. +Success: Was able to run 'prepare stmt1 from 'delete t1_base from t1_base, t2_base where t1_base.i = t2_base.j'' under FTWRL. +Success: Was able to run 'prepare stmt1 from 'delete t1_base from t1_base, t2_base where t1_base.i = t2_base.j'' with FTWRL active in another connection. +# +# 9.2) Compatibility of EXECUTE statement depends on statement +# to be executed. +# +# 9.2.a) EXECUTE for statement which is itself compatible with +# FTWRL should be compatible. +prepare stmt1 from 'select * from t1_base'; +Success: Was able to run 'execute stmt1' under FTWRL. +Success: Was able to run 'execute stmt1' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'execute stmt1' was active in another connection. +deallocate prepare stmt1; +# +# 9.2.b) EXECUTE for statement which is incompatible with FTWRL +# should be also incompatible. +# +# Check that EXECUTE is not allowed under FTWRL. +prepare stmt1 from 'insert into t1_base values (1)'; +flush tables with read lock; +execute stmt1; +ERROR HY000: Can't execute the query because you have a conflicting read lock +unlock tables; +# Check that active FTWRL in another connection +# blocks EXECUTE which changes data. +# +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +execute stmt1 ; +# Switching to connection 'con1'. +# Check that EXECUTE is blocked. +unlock tables; +# Switching to connection 'default'. +# Reap EXECUTE. +set debug_sync='RESET'; +set debug_sync='execute_command_after_close_tables SIGNAL parked WAIT_FOR go'; +execute stmt1; ; +# Switching to connection 'con1'. +set debug_sync='now WAIT_FOR parked'; +flush tables with read lock; +# Switching to connection 'con2'. +# Wait until FTWRL is blocked. +set debug_sync='now SIGNAL go'; +# Switching to connection 'default'. +# Reap EXECUTE. +# Switching to connection 'con1'. +# Reap FTWRL. +unlock tables; +# Switching to connection 'default'. +set debug_sync= "RESET"; +delete from t1_base; +deallocate prepare stmt1; +# +# 9.3) DEALLOCATE PREPARE is compatible with FTWRL. +# +prepare stmt1 from 'insert into t1_base values (1)'; +Success: Was able to run 'deallocate prepare stmt1' under FTWRL. +Success: Was able to run 'deallocate prepare stmt1' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'deallocate prepare stmt1' was active in another connection. +deallocate prepare stmt1; +# +# 10) DELETE variations. +# +# 10.1) Simple DELETE. +# +# 10.1.a) Simple DELETE on base table is incompatible with FTWRL. +Success: Was not able to run 'delete from t1_base' under FTWRL. +Success: 'delete from t1_base' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'delete from t1_base' is active in another connection. +# +# 10.1.b) Simple DELETE on temporary table is compatible with FTWRL. +Success: Was able to run 'delete from t1_temp' under FTWRL. +Success: Was able to run 'delete from t1_temp' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'delete from t1_temp' was active in another connection. +# +# 10.2) Multi DELETE. +# +# 10.2.a) Multi DELETE on base tables is incompatible with FTWRL. +Success: Was not able to run 'delete t1_base from t1_base, t2_base where t1_base.i = t2_base.j' under FTWRL. +Success: 'delete t1_base from t1_base, t2_base where t1_base.i = t2_base.j' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'delete t1_base from t1_base, t2_base where t1_base.i = t2_base.j' is active in another connection. +# +# 10.2.b) Multi DELETE on temporary tables is compatible with FTWRL. +Success: Was able to run 'delete t1_temp from t1_temp, t2_temp where t1_temp.i = t2_temp.j' under FTWRL. +Success: Was able to run 'delete t1_temp from t1_temp, t2_temp where t1_temp.i = t2_temp.j' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'delete t1_temp from t1_temp, t2_temp where t1_temp.i = t2_temp.j' was active in another connection. +# +# 11) DESCRIBE should be compatible with FTWRL. +# +Success: Was able to run 'describe t1_base' under FTWRL. +Success: Was able to run 'describe t1_base' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'describe t1_base' was active in another connection. +# +# 12) Compatibility of DO statement with FTWRL depends on its +# expression. +# +# 12.a) DO with expression which does not change base table +# should be compatible with FTWRL. +Success: Was able to run 'do (select count(*) from t1_base)' under FTWRL. +Success: Was able to run 'do (select count(*) from t1_base)' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'do (select count(*) from t1_base)' was active in another connection. +# +# 12.b) DO which calls SF updating base table should be +# incompatible with FTWRL. +Success: Was not able to run 'do f2_base()' under FTWRL. +Success: 'do f2_base()' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'do f2_base()' is active in another connection. +# +# 12.c) DO which calls SF updating temporary table should be +# compatible with FTWRL. +Success: Was able to run 'do f2_temp()' under FTWRL. +Success: Was able to run 'do f2_temp()' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'do f2_temp()' was active in another connection. +# +# 13) DROP variants. +# +# 13.1) DROP TABLES. +# +# 13.1.a) DROP TABLES which affects base tables is incompatible +# with FTWRL. +Success: Was not able to run 'drop table t2_base' under FTWRL. +Success: 'drop table t2_base' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'drop table t2_base' is active in another connection. +# 13.1.b) DROP TABLES which affects only temporary tables +# in theory can be compatible with FTWRL. +# In practice it is not yet. +Success: Was not able to run 'drop table t2_temp' under FTWRL. +Success: 'drop table t2_temp' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'drop table t2_temp' is active in another connection. +# +# 13.1.c) DROP TEMPORARY TABLES should be compatible with FTWRL. +Success: Was able to run 'drop temporary table t2_temp' under FTWRL. +Success: Was able to run 'drop temporary table t2_temp' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'drop temporary table t2_temp' was active in another connection. +# +# 13.2) DROP INDEX. +# +# 13.2.a) DROP INDEX on a base table is incompatible with FTWRL. +create index i on t1_base (i); +Success: Was not able to run 'drop index i on t1_base' under FTWRL. +Success: 'drop index i on t1_base' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'drop index i on t1_base' is active in another connection. +drop index i on t1_base; +# +# 13.2.b) DROP INDEX on a temporary table is compatible with FTWRL. +create index i on t1_temp (i); +Success: Was able to run 'drop index i on t1_temp' under FTWRL. +Success: Was able to run 'drop index i on t1_temp' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'drop index i on t1_temp' was active in another connection. +drop index i on t1_temp; +# +# 13.3) DROP DATABASE is incompatible with FTWRL +# +Success: Was not able to run 'drop database mysqltest1' under FTWRL. +Success: 'drop database mysqltest1' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'drop database mysqltest1' is active in another connection. +# +# 13.4) DROP FUNCTION is incompatible with FTWRL. +# +Success: Was not able to run 'drop function f1' under FTWRL. +Success: 'drop function f1' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'drop function f1' is active in another connection. +# +# 13.5) DROP PROCEDURE is incompatible with FTWRL. +# +Success: Was not able to run 'drop procedure p1' under FTWRL. +Success: 'drop procedure p1' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'drop procedure p1' is active in another connection. +# +# 13.6) DROP USER should be incompatible with FTWRL. +# +create user mysqltest_u1; +Success: Was not able to run 'drop user mysqltest_u1' under FTWRL. +Success: 'drop user mysqltest_u1' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'drop user mysqltest_u1' is active in another connection. +drop user mysqltest_u1; +# +# 13.7) DROP VIEW should be incompatible with FTWRL. +# +Success: Was not able to run 'drop view v1' under FTWRL. +Success: 'drop view v1' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'drop view v1' is active in another connection. +# +# 13.8) DROP EVENT should be incompatible with FTWRL. +# +Success: Was not able to run 'drop event e1' under FTWRL. +Success: 'drop event e1' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'drop event e1' is active in another connection. +# +# 13.9) DROP TRIGGER is incompatible with FTWRL. +# +create trigger t1_bi before insert on t1_base for each row begin end; +Success: Was not able to run 'drop trigger t1_bi' under FTWRL. +Success: 'drop trigger t1_bi' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'drop trigger t1_bi' is active in another connection. +drop trigger t1_bi; +# +# 13.x) The rest of DROP variants (DROP TABLESPACE, DROP LOGFILE +# GROUP and DROP SERVER) are too special to test here. +# +# +# 14) FLUSH variants. +# +# Test compatibility of _some_ important FLUSH variants with FTWRL. +# +# 14.1) FLUSH TABLES WITH READ LOCK is compatible with itself. +# +# Check that FTWRL statements can be run while FTWRL +# is active in another connection. +# +# Switching to connection 'con1'. +flush tables with read lock; +# The second FTWRL in a row is allowed at the moment. +# It does not make much sense as it does only flush. +flush tables with read lock; +unlock tables; +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +flush tables with read lock; +unlock tables; +# Switching to connection 'con1'. +unlock tables; +# Switching to connection 'default'. +# +# 14.2) FLUSH TABLES <list> WITH READ LOCK is not blocked by +# active FTWRL. But since the latter keeps tables open +# FTWRL is blocked by FLUSH TABLES <list> WITH READ LOCK. +flush tables with read lock; +# FT <list> WRL is allowed under FTWRL at the moment. +# It does not make much sense though. +flush tables t1_base, t2_base with read lock; +unlock tables; +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +flush tables t1_base, t2_base with read lock; +unlock tables; +# Switching to connection 'con1'. +unlock tables; +# Switching to connection 'default'. +flush tables t1_base, t2_base with read lock; +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'con2'. +# Wait until FTWRL is blocked. +# Switching to connection 'default'. +unlock tables; +# Switching to connection 'con1'. +# Reap FTWRL. +unlock tables; +# Switching to connection 'default'. +# +# 14.3) FLUSH TABLES is compatible with FTWRL. +Success: Was able to run 'flush tables' under FTWRL. +Success: Was able to run 'flush tables' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'flush tables' was active in another connection. +# +# 14.4) FLUSH TABLES <list> is compatible with FTWRL. +Success: Was able to run 'flush table t1_base, t2_base' under FTWRL. +Success: Was able to run 'flush table t1_base, t2_base' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'flush table t1_base, t2_base' was active in another connection. +# +# 14.5) FLUSH PRIVILEGES is compatible with FTWRL. +Success: Was able to run 'flush privileges' under FTWRL. +Success: Was able to run 'flush privileges' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'flush privileges' was active in another connection. +# +# 15) GRANT statement should be incompatible with FTWRL. +# +Success: Was not able to run 'grant all privileges on t1_base to mysqltest_u1' under FTWRL. +Success: 'grant all privileges on t1_base to mysqltest_u1' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'grant all privileges on t1_base to mysqltest_u1' is active in another connection. +drop user mysqltest_u1; +# +# 16) All HANDLER variants are half-compatible with FTWRL. +# I.e. they are not blocked by active FTWRL. But since open +# HANDLER means open table instance FTWRL is blocked while +# HANDLER is not closed. +# +# Check that HANDLER statements succeed under FTWRL. +flush tables with read lock; +handler t1_base open; +handler t1_base read first; +i +handler t1_base close; +unlock tables; +# Check that HANDLER statements can be run while FTWRL +# is active in another connection. +# +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +handler t1_base open; +handler t1_base read first; +i +handler t1_base close; +# Switching to connection 'con1'. +unlock tables; +# Switching to connection 'default'. +# +# 17) HELP statement is compatible with FTWRL. +# +Success: Was able to run 'help no_such_topic' under FTWRL. +Success: Was able to run 'help no_such_topic' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'help no_such_topic' was active in another connection. +# +# 18) INSERT statement. +# +# 18.a) Ordinary INSERT into base table is incompatible with FTWRL. +Success: Was not able to run 'insert into t1_base values (1)' under FTWRL. +Success: 'insert into t1_base values (1)' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'insert into t1_base values (1)' is active in another connection. +# +# 18.b) Ordinary INSERT into temp table is compatible with FTWRL. +Success: Was able to run 'insert into t1_temp values (1)' under FTWRL. +Success: Was able to run 'insert into t1_temp values (1)' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'insert into t1_temp values (1)' was active in another connection. +# +# 18.c) INSERT DELAYED is incompatible with FTWRL. +Success: Was not able to run 'insert delayed into t1_base values (1)' under FTWRL. +Success: 'insert delayed into t1_base values (1)' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'insert delayed into t1_base values (1)' is active in another connection. +delete from t1_base; +# +# 18.d) INSERT SELECT into base table is incompatible with FTWRL. +Success: Was not able to run 'insert into t1_base select * from t1_temp' under FTWRL. +Success: 'insert into t1_base select * from t1_temp' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'insert into t1_base select * from t1_temp' is active in another connection. +# +# 18.e) INSERT SELECT into temp table is compatible with FTWRL. +Success: Was able to run 'insert into t1_temp select * from t1_base' under FTWRL. +Success: Was able to run 'insert into t1_temp select * from t1_base' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'insert into t1_temp select * from t1_base' was active in another connection. +# +# 19) KILL statement is compatible with FTWRL. +# +# Check that KILL can be run under FTWRL. +flush tables with read lock; +set @id:= connection_id(); +kill query @id; +ERROR 70100: Query execution was interrupted +unlock tables; +# Check that KILL statements can be run while FTWRL +# is active in another connection. +# +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +kill query @id; +ERROR 70100: Query execution was interrupted +# Switching to connection 'con1'. +unlock tables; +# Switching to connection 'default'. +# Finally check that KILL doesn't block FTWRL +set debug_sync='RESET'; +set debug_sync='execute_command_after_close_tables SIGNAL parked WAIT_FOR go'; +kill query @id; +# Switching to connection 'con1'. +set debug_sync='now WAIT_FOR parked'; +flush tables with read lock; +unlock tables; +set debug_sync='now SIGNAL go'; +# Switching to connection 'default'. +# Reap KILL. +ERROR 70100: Query execution was interrupted +set debug_sync='RESET'; +# +# 20) LOAD DATA statement. +# +# 20.a) LOAD DATA into base table is incompatible with FTWRL. +Success: Was not able to run 'load data infile '../../std_data/rpl_loaddata.dat' into table t1_base (@dummy, i)' under FTWRL. +Success: 'load data infile '../../std_data/rpl_loaddata.dat' into table t1_base (@dummy, i)' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'load data infile '../../std_data/rpl_loaddata.dat' into table t1_base (@dummy, i)' is active in another connection. +# +# 20.b) LOAD DATA into temporary table is compatible with FTWRL. +Success: Was able to run 'load data infile '../../std_data/rpl_loaddata.dat' into table t1_temp (@dummy, i)' under FTWRL. +Success: Was able to run 'load data infile '../../std_data/rpl_loaddata.dat' into table t1_temp (@dummy, i)' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'load data infile '../../std_data/rpl_loaddata.dat' into table t1_temp (@dummy, i)' was active in another connection. +# +# 21) LOCK/UNLOCK TABLES statements. +# +# LOCK TABLES statement always (almost) blocks FTWRL as it +# keeps tables open until UNLOCK TABLES. +# Active FTWRL on the other hand blocks only those +# LOCK TABLES which allow updating of base tables. +# +# 21.a) LOCK TABLES READ is allowed under FTWRL and +# is not blocked by active FTWRL. +flush tables with read lock; +lock tables t1_base read; +unlock tables; +# +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +lock tables t1_base read; +unlock tables; +# Switching to connection 'con1'. +unlock tables; +# Switching to connection 'default'. +# +# 21.b) LOCK TABLES WRITE on a base table is disallowed +# under FTWRL and should be blocked by active FTWRL. +flush tables with read lock; +lock tables t1_base write; +ERROR HY000: Can't execute the query because you have a conflicting read lock +unlock tables; +# +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +lock tables t1_base write ; +# Switching to connection 'con1'. +# Check that LOCK TABLES WRITE is blocked. +unlock tables; +# Switching to connection 'default'. +# Reap LOCK TABLES WRITE +unlock tables; +# +# 21.c) LOCK TABLES WRITE on temporary table doesn't +# make much sense but is allowed under FTWRL +# and should not be blocked by active FTWRL. +flush tables with read lock; +lock tables t1_temp write; +unlock tables; +# +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +lock tables t1_temp write; +unlock tables; +# Switching to connection 'con1'. +unlock tables; +# Switching to connection 'default'. +# +# 22) OPTIMIZE TABLE statement. +# +# 22.a) OPTIMIZE TABLE of base table is incompatible with FTWRL. +flush tables with read lock; +# OPTIMIZE statement returns errors as part of result-set. +optimize table t1_base; +Table Op Msg_type Msg_text +test.t1_base optimize Error Can't execute the query because you have a conflicting read lock +test.t1_base optimize error Corrupt +unlock tables; +# +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +optimize table t1_base; +# Switching to connection 'con1'. +# Check that OPTIMIZE TABLE is blocked. +unlock tables; +# Switching to connection 'default'. +# Reap OPTIMIZE TABLE +Table Op Msg_type Msg_text +test.t1_base optimize status OK +# We don't check that active OPTIMIZE TABLE blocks +# FTWRL as this one of statements releasing metadata +# locks in non-standard place. +# +# 22.b) OPTIMIZE TABLE of temporary table is compatible with FTWRL. +# Skip last part of compatibility testing as this statement +# releases metadata locks in non-standard place. +Success: Was able to run 'optimize table t1_temp' under FTWRL. +Success: Was able to run 'optimize table t1_temp' with FTWRL active in another connection. +# +# 23) CACHE statement is compatible with FTWRL. +# +# Skip last part of compatibility testing as this statement +# releases metadata locks in non-standard place. +Success: Was able to run 'cache index t1_base in default' under FTWRL. +Success: Was able to run 'cache index t1_base in default' with FTWRL active in another connection. +# +# 24) LOAD INDEX statement is compatible with FTWRL. +# +# Skip last part of compatibility testing as this statement +# releases metadata locks in non-standard place. +Success: Was able to run 'load index into cache t1_base' under FTWRL. +Success: Was able to run 'load index into cache t1_base' with FTWRL active in another connection. +# +# 25) SAVEPOINT/RELEASE SAVEPOINT/ROLLBACK TO SAVEPOINT are +# compatible with FTWRL. +# +# Since manipulations on savepoint have to be done +# inside transaction and FTWRL commits transaction we +# need a special test for these statements. +flush tables with read lock; +begin; +savepoint sv1; +rollback to savepoint sv1; +release savepoint sv1; +unlock tables; +commit; +# Check that these statements are not blocked by +# active FTWRL in another connection. +# +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +begin; +# Switching to connection 'con1'. +unlock tables; +# Switching to connection 'default'. +# Do some changes to avoid SAVEPOINT and friends +# being almost no-ops. +insert into t3_trans values (1); +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +savepoint sv1; +# Switching to connection 'con1'. +unlock tables; +# Switching to connection 'default'. +insert into t3_trans values (2); +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +rollback to savepoint sv1; +release savepoint sv1; +# Switching to connection 'con1'. +unlock tables; +# Switching to connection 'default'. +rollback; +# Check that these statements don't block FTWRL in +# another connection. +begin; +# Do some changes to avoid SAVEPOINT and friends +# being almost no-ops. +insert into t3_trans values (1); +set debug_sync='RESET'; +set debug_sync='execute_command_after_close_tables SIGNAL parked WAIT_FOR go'; +savepoint sv1; +# Switching to connection 'con1'. +set debug_sync='now WAIT_FOR parked'; +flush tables with read lock; +unlock tables; +set debug_sync='now SIGNAL go'; +# Switching to connection 'default'. +# Reap SAVEPOINT +insert into t3_trans values (2); +set debug_sync='execute_command_after_close_tables SIGNAL parked WAIT_FOR go'; +rollback to savepoint sv1; +# Switching to connection 'con1'. +set debug_sync='now WAIT_FOR parked'; +flush tables with read lock; +unlock tables; +set debug_sync='now SIGNAL go'; +# Switching to connection 'default'. +# Reap ROLLBACK TO SAVEPOINT +set debug_sync='execute_command_after_close_tables SIGNAL parked WAIT_FOR go'; +release savepoint sv1; +# Switching to connection 'con1'. +set debug_sync='now WAIT_FOR parked'; +flush tables with read lock; +unlock tables; +set debug_sync='now SIGNAL go'; +# Switching to connection 'default'. +# Reap RELEASE SAVEPOINT +rollback; +set debug_sync= "RESET"; +# +# 26) RENAME variants. +# +# 26.1) RENAME TABLES is incompatible with FTWRL. +Success: Was not able to run 'rename table t1_base to t3_base' under FTWRL. +Success: 'rename table t1_base to t3_base' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'rename table t1_base to t3_base' is active in another connection. +# +# 26.2) RENAME USER is incompatible with FTWRL. +create user mysqltest_u1; +Success: Was not able to run 'rename user mysqltest_u1 to mysqltest_u2' under FTWRL. +Success: 'rename user mysqltest_u1 to mysqltest_u2' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'rename user mysqltest_u1 to mysqltest_u2' is active in another connection. +drop user mysqltest_u1; +# +# 27) REPAIR TABLE statement. +# +# 27.a) REPAIR TABLE of base table is incompatible with FTWRL. +flush tables with read lock; +# REPAIR statement returns errors as part of result-set. +repair table t1_base; +Table Op Msg_type Msg_text +test.t1_base repair Error Can't execute the query because you have a conflicting read lock +test.t1_base repair error Corrupt +unlock tables; +# +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +repair table t1_base; +# Switching to connection 'con1'. +# Check that REPAIR TABLE is blocked. +unlock tables; +# Switching to connection 'default'. +# Reap REPAIR TABLE +Table Op Msg_type Msg_text +test.t1_base repair status OK +# We don't check that active REPAIR TABLE blocks +# FTWRL as this one of statements releasing metadata +# locks in non-standard place. +# +# 27.b) REPAIR TABLE of temporary table is compatible with FTWRL. +# Skip last part of compatibility testing as this statement +# releases metadata locks in non-standard place. +Success: Was able to run 'repair table t1_temp' under FTWRL. +Success: Was able to run 'repair table t1_temp' with FTWRL active in another connection. +# +# 28) REPLACE statement. +# +# 28.a) Ordinary REPLACE into base table is incompatible with FTWRL. +Success: Was not able to run 'replace into t1_base values (1)' under FTWRL. +Success: 'replace into t1_base values (1)' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'replace into t1_base values (1)' is active in another connection. +# +# 28.b) Ordinary REPLACE into temp table is compatible with FTWRL. +Success: Was able to run 'replace into t1_temp values (1)' under FTWRL. +Success: Was able to run 'replace into t1_temp values (1)' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'replace into t1_temp values (1)' was active in another connection. +# +# 28.c) REPLACE SELECT into base table is incompatible with FTWRL. +Success: Was not able to run 'replace into t1_base select * from t1_temp' under FTWRL. +Success: 'replace into t1_base select * from t1_temp' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'replace into t1_base select * from t1_temp' is active in another connection. +# +# 28.d) REPLACE SELECT into temp table is compatible with FTWRL. +Success: Was able to run 'replace into t1_temp select * from t1_base' under FTWRL. +Success: Was able to run 'replace into t1_temp select * from t1_base' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'replace into t1_temp select * from t1_base' was active in another connection. +# +# 29) REVOKE variants. +# +# 29.1) REVOKE privileges is incompatible with FTWRL. +grant all privileges on t1_base to mysqltest_u1; +Success: Was not able to run 'revoke all privileges on t1_base from mysqltest_u1' under FTWRL. +Success: 'revoke all privileges on t1_base from mysqltest_u1' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'revoke all privileges on t1_base from mysqltest_u1' is active in another connection. +# +# 29.2) REVOKE ALL PRIVILEGES, GRANT OPTION is incompatible with FTWRL. +Success: Was not able to run 'revoke all privileges, grant option from mysqltest_u1' under FTWRL. +Success: 'revoke all privileges, grant option from mysqltest_u1' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'revoke all privileges, grant option from mysqltest_u1' is active in another connection. +drop user mysqltest_u1; +# +# 30) Compatibility of SELECT statement with FTWRL depends on +# locking mode used and on functions being invoked by it. +# +# 30.a) Simple SELECT which does not change tables should be +# compatible with FTWRL. +Success: Was able to run 'select count(*) from t1_base' under FTWRL. +Success: Was able to run 'select count(*) from t1_base' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'select count(*) from t1_base' was active in another connection. +# 30.b) SELECT ... FOR UPDATE is incompatible with FTWRL. +Success: Was not able to run 'select count(*) from t1_base for update' under FTWRL. +Success: 'select count(*) from t1_base for update' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'select count(*) from t1_base for update' is active in another connection. +# 30.c) SELECT ... LOCK IN SHARE MODE is compatible with FTWRL. +Success: Was able to run 'select count(*) from t1_base lock in share mode' under FTWRL. +Success: Was able to run 'select count(*) from t1_base lock in share mode' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'select count(*) from t1_base lock in share mode' was active in another connection. +# +# 30.d) SELECT which calls SF updating base table should be +# incompatible with FTWRL. +Success: Was not able to run 'select f2_base()' under FTWRL. +Success: 'select f2_base()' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'select f2_base()' is active in another connection. +# +# 30.e) SELECT which calls SF updating temporary table should be +# compatible with FTWRL. +Success: Was able to run 'select f2_temp()' under FTWRL. +Success: Was able to run 'select f2_temp()' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'select f2_temp()' was active in another connection. +# +# 31) Compatibility of SET statement with FTWRL depends on its +# expression and on whether it is a special SET statement. +# +# 31.a) Ordinary SET with expression which does not +# changes base table should be compatible with FTWRL. +# Skip last part of compatibility testing as our helper debug +# sync-point doesn't work for SET statements. +Success: Was able to run 'set @a:= (select count(*) from t1_base)' under FTWRL. +Success: Was able to run 'set @a:= (select count(*) from t1_base)' with FTWRL active in another connection. +# +# 31.b) Ordinary SET which calls SF updating base table should +# be incompatible with FTWRL. +# Skip last part of compatibility testing as our helper debug +# sync-point doesn't work for SET statements. +Success: Was not able to run 'set @a:= f2_base()' under FTWRL. +Success: 'set @a:= f2_base()' is blocked by FTWRL active in another connection. +# +# 31.c) Ordinary SET which calls SF updating temporary table +# should be compatible with FTWRL. +# Skip last part of compatibility testing as our helper debug +# sync-point doesn't work for SET statements. +Success: Was able to run 'set @a:= f2_temp()' under FTWRL. +Success: Was able to run 'set @a:= f2_temp()' with FTWRL active in another connection. +# +# 31.d) Special SET variants have different compatibility with FTWRL. +# +# 31.d.I) SET PASSWORD is incompatible with FTWRL as it changes data. +create user mysqltest_u1; +# Skip last part of compatibility testing as our helper debug +# sync-point doesn't work for SET statements. +Success: Was not able to run 'set password for 'mysqltest_u1' = password('')' under FTWRL. +Success: 'set password for 'mysqltest_u1' = password('')' is blocked by FTWRL active in another connection. +drop user mysqltest_u1; +# +# 31.d.II) SET READ_ONLY is compatible with FTWRL (but has no +# effect when executed under it). +# Skip last part of compatibility testing as our helper debug +# sync-point doesn't work for SET statements. +Success: Was able to run 'set global read_only= 1' under FTWRL. +Success: Was able to run 'set global read_only= 1' with FTWRL active in another connection. +# +# 31.d.III) Situation with SET AUTOCOMMIT is complex. +# Turning auto-commit off is always compatible with FTWRL. +# Turning auto-commit on causes implicit commit and so +# is incompatible with FTWRL if there are changes to be +# committed. +flush tables with read lock; +set autocommit= 0; +# Turning auto-commit on causes implicit commit so can +# be incompatible with FTWRL if there is something to +# commit. But since even in this case we allow commits +# under active FTWRL such statement should always succeed. +insert into t3_temp_trans values (1); +set autocommit= 1; +unlock tables; +delete from t3_temp_trans; +# Check that SET AUTOCOMMIT=0 is not blocked and +# SET AUTOCOMMIT=1 is blocked by active FTWRL in +# another connection. +# +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +set autocommit= 0; +# Switching to connection 'con1'. +unlock tables; +# Switching to connection 'default'. +# Do some work so implicit commit in SET AUTOCOMMIT=1 +# is not a no-op. +insert into t3_trans values (1); +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +# Send: +set autocommit= 1; +# Switching to connection 'con1'. +# Wait until SET AUTOCOMMIT=1 is blocked. +unlock tables; +# Switching to connection 'default'. +# Reap SET AUTOCOMMIT=1. +delete from t3_trans; +# +# Check that SET AUTOCOMMIT=1 blocks FTWRL in another connection. +set autocommit= 0; +insert into t3_trans values (1); +set debug_sync='RESET'; +set debug_sync='ha_commit_trans_after_acquire_commit_lock SIGNAL parked WAIT_FOR go'; +set autocommit= 1; +# Switching to connection 'con1'. +set debug_sync='now WAIT_FOR parked'; +flush tables with read lock; +# Switching to connection 'con2'. +# Wait until FTWRL is blocked. +set debug_sync='now SIGNAL go'; +# Switching to connection 'default'. +# Reap SET AUTOCOMMIT=1. +# Switching to connection 'con1'. +# Reap FTWRL. +unlock tables; +# Switching to connection 'default'. +delete from t3_trans; +set debug_sync= "RESET"; +# +# 32) SHOW statements are compatible with FTWRL. +# Let us test _some_ of them. +# +# 32.1) SHOW TABLES. +Success: Was able to run 'show tables from test' under FTWRL. +Success: Was able to run 'show tables from test' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'show tables from test' was active in another connection. +# +# 32.1) SHOW TABLES. +Success: Was able to run 'show tables from test' under FTWRL. +Success: Was able to run 'show tables from test' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'show tables from test' was active in another connection. +# +# 32.2) SHOW EVENTS. +Success: Was able to run 'show events from test' under FTWRL. +Success: Was able to run 'show events from test' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'show events from test' was active in another connection. +# +# 32.3) SHOW GRANTS. +create user mysqltest_u1; +Success: Was able to run 'show grants for mysqltest_u1' under FTWRL. +Success: Was able to run 'show grants for mysqltest_u1' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'show grants for mysqltest_u1' was active in another connection. +drop user mysqltest_u1; +# +# 32.4) SHOW CREATE TABLE. +Success: Was able to run 'show create table t1_base' under FTWRL. +Success: Was able to run 'show create table t1_base' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'show create table t1_base' was active in another connection. +# +# 32.5) SHOW CREATE FUNCTION. +Success: Was able to run 'show create function f1' under FTWRL. +Success: Was able to run 'show create function f1' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'show create function f1' was active in another connection. +# +# 33) SIGNAL statement is compatible with FTWRL. +# +# Note that we don't cover RESIGNAL as it requires +# active handler context. +Success: Was able to run 'signal sqlstate '01000'' under FTWRL. +Success: Was able to run 'signal sqlstate '01000'' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'signal sqlstate '01000'' was active in another connection. +# +# 34) TRUNCATE TABLE statement. +# +# 34.a) TRUNCATE of base table is incompatible with FTWRL. +Success: Was not able to run 'truncate table t1_base' under FTWRL. +Success: 'truncate table t1_base' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'truncate table t1_base' is active in another connection. +# +# 34.b) TRUNCATE of temporary table is compatible with FTWRL. +Success: Was able to run 'truncate table t1_temp' under FTWRL. +Success: Was able to run 'truncate table t1_temp' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'truncate table t1_temp' was active in another connection. +# +# 35) UPDATE variants. +# +# 35.1) Simple UPDATE. +# +# 35.1.a) Simple UPDATE on base table is incompatible with FTWRL. +Success: Was not able to run 'update t1_base set i= 1 where i = 0' under FTWRL. +Success: 'update t1_base set i= 1 where i = 0' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'update t1_base set i= 1 where i = 0' is active in another connection. +# +# 35.1.b) Simple UPDATE on temporary table is compatible with FTWRL. +Success: Was able to run 'update t1_temp set i= 1 where i = 0' under FTWRL. +Success: Was able to run 'update t1_temp set i= 1 where i = 0' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'update t1_temp set i= 1 where i = 0' was active in another connection. +# +# 35.2) Multi UPDATE. +# +# 35.2.a) Multi UPDATE on base tables is incompatible with FTWRL. +Success: Was not able to run 'update t1_base, t2_base set t1_base.i= 1 where t1_base.i = t2_base.j' under FTWRL. +Success: 'update t1_base, t2_base set t1_base.i= 1 where t1_base.i = t2_base.j' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'update t1_base, t2_base set t1_base.i= 1 where t1_base.i = t2_base.j' is active in another connection. +# +# 35.2.b) Multi UPDATE on temporary tables is compatible with FTWRL. +Success: Was able to run 'update t1_temp, t2_temp set t1_temp.i= 1 where t1_temp.i = t2_temp.j' under FTWRL. +Success: Was able to run 'update t1_temp, t2_temp set t1_temp.i= 1 where t1_temp.i = t2_temp.j' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'update t1_temp, t2_temp set t1_temp.i= 1 where t1_temp.i = t2_temp.j' was active in another connection. +# +# 36) USE statement is compatible with FTWRL. +# +Success: Was able to run 'use mysqltest1' under FTWRL. +Success: Was able to run 'use mysqltest1' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'use mysqltest1' was active in another connection. +# +# 37) XA statements. +# +# XA statements are similar to BEGIN/COMMIT/ROLLBACK. +# +# XA BEGIN, END, PREPARE, ROLLBACK and RECOVER are compatible +# with FTWRL. XA COMMIT is not. +flush tables with read lock; +# Although all below statements are allowed under FTWRL they +# are almost no-ops as FTWRL does commit and does not allows +# any non-temporary DML under it. +xa start 'test1'; +xa end 'test1'; +xa prepare 'test1'; +xa rollback 'test1'; +xa start 'test1'; +xa end 'test1'; +xa prepare 'test1'; +xa commit 'test1'; +xa recover; +unlock tables; +# Check that XA non-COMMIT statements are not and COMMIT is +# blocked by active FTWRL in another connection +# +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +xa start 'test1'; +# Switching to connection 'con1'. +unlock tables; +# Switching to connection 'default'. +insert into t3_trans values (1); +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +xa end 'test1'; +xa prepare 'test1'; +xa rollback 'test1'; +# Switching to connection 'con1'. +unlock tables; +# Switching to connection 'default'. +xa start 'test1'; +insert into t3_trans values (1); +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +xa end 'test1'; +xa prepare 'test1'; +# Send: +xa commit 'test1';; +# Switching to connection 'con1'. +# Wait until XA COMMIT is blocked. +unlock tables; +# Switching to connection 'default'. +# Reap XA COMMIT. +delete from t3_trans; +# +# Check that XA COMMIT blocks FTWRL in another connection. +xa start 'test1'; +insert into t3_trans values (1); +xa end 'test1'; +xa prepare 'test1'; +set debug_sync='RESET'; +set debug_sync='trans_xa_commit_after_acquire_commit_lock SIGNAL parked WAIT_FOR go'; +xa commit 'test1'; +# Switching to connection 'con1'. +set debug_sync='now WAIT_FOR parked'; +flush tables with read lock; +# Switching to connection 'con2'. +# Wait until FTWRL is blocked. +set debug_sync='now SIGNAL go'; +# Switching to connection 'default'. +# Reap XA COMMIT. +# Switching to connection 'con1'. +# Reap FTWRL. +unlock tables; +# Switching to connection 'default'. +delete from t3_trans; +set debug_sync= "RESET"; +# +# 38) Test effect of auto-commit mode for DML on transactional +# temporary tables. +# +# 38.1) When auto-commit is on each such a statement ends with commit +# of changes to temporary tables. But since transactions doing +# such changes are considered read only [sic!/QQ] this commit +# is compatible with FTWRL. +# +# Let us demostrate this fact for some common DML statements. +Success: Was able to run 'delete from t3_temp_trans' under FTWRL. +Success: Was able to run 'delete from t3_temp_trans' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'delete from t3_temp_trans' was active in another connection. +Success: Was able to run 'insert into t3_temp_trans values (1)' under FTWRL. +Success: Was able to run 'insert into t3_temp_trans values (1)' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'insert into t3_temp_trans values (1)' was active in another connection. +Success: Was able to run 'update t3_temp_trans, t2_temp set t3_temp_trans.i= 1 where t3_temp_trans.i = t2_temp.j' under FTWRL. +Success: Was able to run 'update t3_temp_trans, t2_temp set t3_temp_trans.i= 1 where t3_temp_trans.i = t2_temp.j' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'update t3_temp_trans, t2_temp set t3_temp_trans.i= 1 where t3_temp_trans.i = t2_temp.j' was active in another connection. +# +# 38.2) When auto-commit is off DML on transaction temporary tables +# is compatible with FTWRL. +# +set autocommit= 0; +Success: Was able to run 'delete from t3_temp_trans' under FTWRL. +Success: Was able to run 'delete from t3_temp_trans' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'delete from t3_temp_trans' was active in another connection. +Success: Was able to run 'insert into t3_temp_trans values (1)' under FTWRL. +Success: Was able to run 'insert into t3_temp_trans values (1)' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'insert into t3_temp_trans values (1)' was active in another connection. +Success: Was able to run 'update t3_temp_trans, t2_temp set t3_temp_trans.i= 1 where t3_temp_trans.i = t2_temp.j' under FTWRL. +Success: Was able to run 'update t3_temp_trans, t2_temp set t3_temp_trans.i= 1 where t3_temp_trans.i = t2_temp.j' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'update t3_temp_trans, t2_temp set t3_temp_trans.i= 1 where t3_temp_trans.i = t2_temp.j' was active in another connection. +set autocommit= 1; +# +# 39) Test effect of DDL on transactional tables. +# +# 39.1) Due to implicit commit at the end of statement some of DDL +# statements which are compatible with FTWRL in non-transactional +# case are not compatible in case of transactional tables. +# +# 39.1.a) ANALYZE TABLE for transactional table is incompatible with +# FTWRL. +flush tables with read lock; +# Implicit commits are allowed under FTWRL. +analyze table t3_trans; +Table Op Msg_type Msg_text +test.t3_trans analyze status OK +unlock tables; +# +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +analyze table t3_trans; +# Switching to connection 'con1'. +# Check that ANALYZE TABLE is blocked. +unlock tables; +# Switching to connection 'default'. +# Reap ANALYZE TABLE +Table Op Msg_type Msg_text +test.t3_trans analyze status OK +# +# 39.1.b) CHECK TABLE for transactional table is compatible with FTWRL. +# Although it does implicit commit at the end of statement it +# is considered to be read-only operation. +# Skip last part of compatibility testing as this statement +# releases metadata locks in non-standard place. +Success: Was able to run 'check table t3_trans' under FTWRL. +Success: Was able to run 'check table t3_trans' with FTWRL active in another connection. +# +# 39.2) Situation with DDL on temporary transactional tables is +# complex. +# +# 39.2.a) Some statements compatible with FTWRL since they don't +# do implicit commit. +# +# For example, CREATE TEMPORARY TABLE: +Success: Was able to run 'create temporary table t4_temp_trans(i int) engine=innodb' under FTWRL. +Success: Was able to run 'create temporary table t4_temp_trans(i int) engine=innodb' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'create temporary table t4_temp_trans(i int) engine=innodb' was active in another connection. +# +# Or DROP TEMPORARY TABLE: +Success: Was able to run 'drop temporary tables t3_temp_trans' under FTWRL. +Success: Was able to run 'drop temporary tables t3_temp_trans' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'drop temporary tables t3_temp_trans' was active in another connection. +# +# 39.2.b) Some statements do implicit commit but are considered +# read-only and so are compatible with FTWRL. +# +# For example, REPAIR TABLE: +Success: Was able to run 'repair table t3_temp_trans' under FTWRL. +Success: Was able to run 'repair table t3_temp_trans' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'repair table t3_temp_trans' was active in another connection. +# +# And ANALYZE TABLE: +Success: Was able to run 'analyze table t3_temp_trans' under FTWRL. +Success: Was able to run 'analyze table t3_temp_trans' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'analyze table t3_temp_trans' was active in another connection. +# +# 39.2.c) Some statements do implicit commit and not +# considered read-only. As result they are +# not compatible with FTWRL. +# +flush tables with read lock; +# Implicit commits are allowed under FTWRL. +alter table t3_temp_trans add column c1 int; +unlock tables; +# +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +alter table t3_temp_trans drop column c1; +# Switching to connection 'con1'. +# Check that ALTER TABLE is blocked. +unlock tables; +# Switching to connection 'default'. +# Reap ALTER TABLE +# +# 40) Test effect of implicit commit for DDL which is otherwise +# compatible with FTWRL. Implicit commit at the start of DDL +# statement can make it incompatible with FTWRL if there are +# some changes to be commited even in case when DDL statement +# itself is compatible with FTWRL. +# +# For example CHECK TABLE for base non-transactional tables and +# ALTER TABLE for temporary non-transactional tables are affected. +begin; +insert into t3_trans values (1); +# +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +check table t1_base; +# Switching to connection 'con1'. +# Check that CHECK TABLE is blocked. +unlock tables; +# Switching to connection 'default'. +# Reap CHECK TABLE +Table Op Msg_type Msg_text +test.t1_base check status OK +begin; +delete from t3_trans; +# +# Switching to connection 'con1'. +flush tables with read lock; +# Switching to connection 'default'. +alter table t1_temp add column c1 int; +# Switching to connection 'con1'. +# Check that ALTER TABLE is blocked. +unlock tables; +# Switching to connection 'default'. +# Reap ALTER TABLE +alter table t1_temp drop column c1; +# +# Check that FLUSH TABLES WITH READ LOCK is blocked by individual +# statements and is not blocked in the presence of transaction which +# has done some changes earlier but is idle now (or does only reads). +# This allows to use this statement even on systems which has long +# running transactions. +# +begin; +insert into t1_base values (1); +insert into t3_trans values (1); +# Switching to connection 'con1'. +# The below FTWRL should not be blocked by transaction in 'default'. +flush tables with read lock; +# Switching to connection 'default'. +# Transaction still is able to read even with FTWRL active in another +# connection. +select * from t1_base; +i +1 +select * from t2_base; +j +select * from t3_trans; +i +1 +# Switching to connection 'con1'. +unlock tables; +# Switching to connection 'default'. +commit; +delete from t1_base; +delete from t3_trans; +# +# Check that impending FTWRL blocks new DML statements and +# so can't be starved by a constant flow of DML. +# (a.k.a. test for bug #54673 "It takes too long to get +# readlock for 'FLUSH TABLES WITH READ LOCK'"). +# +set debug_sync='RESET'; +set debug_sync='execute_command_after_close_tables SIGNAL parked WAIT_FOR go'; +insert into t1_base values (1); +# Switching to connection 'con1'. +set debug_sync='now WAIT_FOR parked'; +flush tables with read lock; +# Switching to connection 'con2'. +# Wait until FTWRL is blocked. +# Try to run another INSERT and see that it is blocked. +insert into t2_base values (1);; +# Switching to connection 'con3'. +# Wait until new INSERT is blocked. +# Unblock INSERT in the first connection. +set debug_sync='now SIGNAL go'; +# Switching to connection 'default'. +# Reap first INSERT. +# Switching to connection 'con1'. +# Reap FTWRL. +unlock tables; +# Switching to connection 'con2'. +# Reap second INSERT. +# Switching to connection 'default'. +set debug_sync= "RESET"; +delete from t1_base; +delete from t2_base; + +# Check that COMMIT thas is issued after +# FLUSH TABLES WITH READ LOCK is not blocked by +# FLUSH TABLES WITH READ LOCK from another connection. +# This scenario is used in innobackup.pl. The COMMIT goes +# through because the transaction started by FTWRL does +# not modify any tables, and the commit blocker lock is +# only taken when there were such modifications. + +flush tables with read lock; +# Switching to connection 'con1'. +# The below FTWRL should not be blocked by transaction in 'default'. +flush tables with read lock; +# Switching to connection 'default'. +select * from t1_base; +i +select * from t3_trans; +i +commit; +# Switching to connection 'con1'. +select * from t1_base; +i +select * from t3_trans; +i +commit; +unlock tables; +# Switching to connection 'default'. +unlock tables; +# +# Check how FLUSH TABLE WITH READ LOCK is handled for MERGE tables. +# As usual there are tricky cases related to this type of tables. +# +# +# 1) Most typical case - base MERGE table with base underlying tables. +# +# 1.a) DML statements which change data should be incompatible with FTWRL. +create table tm_base (i int) engine=merge union=(t1_base) insert_method=last; +Success: Was not able to run 'insert into tm_base values (1)' under FTWRL. +Success: 'insert into tm_base values (1)' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'insert into tm_base values (1)' is active in another connection. +# +# 1.b) DDL statement on such table should be incompatible with FTWRL as well. +Success: Was not able to run 'alter table tm_base insert_method=first' under FTWRL. +Success: 'alter table tm_base insert_method=first' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'alter table tm_base insert_method=first' is active in another connection. +drop table tm_base; +# +# 2) Temporary MERGE table with base underlying tables. +# +# 2.a) DML statements which change data should be incompatible with FTWRL +# as they affect base tables. +create temporary table tm_temp_base (i int) engine=merge union=(t1_base) insert_method=last; +Success: Was not able to run 'insert into tm_temp_base values (1)' under FTWRL. +Success: 'insert into tm_temp_base values (1)' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'insert into tm_temp_base values (1)' is active in another connection. +# +# 2.b) Some of DDL statements on such table can be compatible with FTWRL +# as they don't affect base tables. +Success: Was able to run 'drop temporary tables tm_temp_base' under FTWRL. +Success: Was able to run 'drop temporary tables tm_temp_base' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'drop temporary tables tm_temp_base' was active in another connection. +# +# 2.c) ALTER statement is incompatible with FTWRL. Even though it does +# not change data in base table it still acquires strong metadata +# locks on them. +Success: Was not able to run 'alter table tm_temp_base insert_method=first' under FTWRL. +Success: 'alter table tm_temp_base insert_method=first' is blocked by FTWRL active in another connection. +Success: FTWRL is blocked when 'alter table tm_temp_base insert_method=first' is active in another connection. +drop table tm_temp_base; +# +# 3) Temporary MERGE table with temporary underlying tables. +# +# 3.a) DML statements should be compatible with FTWRL as +# no base table is going to be affected. +create temporary table tm_temp_temp (i int) engine=merge union=(t1_temp) insert_method=last; +Success: Was able to run 'insert into tm_temp_temp values (1)' under FTWRL. +Success: Was able to run 'insert into tm_temp_temp values (1)' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'insert into tm_temp_temp values (1)' was active in another connection. +# +# 3.b) DDL statements should be compatible with FTWRL as well +# as no base table is going to be affected too. +Success: Was able to run 'alter table tm_temp_temp union=(t1_temp) insert_method=first' under FTWRL. +Success: Was able to run 'alter table tm_temp_temp union=(t1_temp) insert_method=first' with FTWRL active in another connection. +Success: Was able to run FTWRL while 'alter table tm_temp_temp union=(t1_temp) insert_method=first' was active in another connection. +drop table tm_temp_temp; +# +# 4) For the sake of completeness let us check that base MERGE tables +# with temporary underlying tables are not functional. +create table tm_base_temp (i int) engine=merge union=(t1_temp) insert_method=last; +select * from tm_base_temp; +ERROR HY000: Unable to open underlying table which is differently defined or of non-MyISAM type or doesn't exist +drop table tm_base_temp; +# +# Clean-up. +# +drop event e1; +drop function f2_temp; +drop function f2_base; +drop procedure p2; +drop view v1; +drop function f1; +drop procedure p1; +drop database `#mysql50#mysqltest-2`; +drop database mysqltest1; +drop temporary tables t1_temp, t2_temp; +drop tables t1_base, t2_base, t3_trans; diff --git a/mysql-test/r/flush_read_lock_kill.result b/mysql-test/r/flush_read_lock_kill.result index b16a8b114b3..8453d26cbea 100644 --- a/mysql-test/r/flush_read_lock_kill.result +++ b/mysql-test/r/flush_read_lock_kill.result @@ -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'; diff --git a/mysql-test/r/handler_innodb.result b/mysql-test/r/handler_innodb.result index 66914285733..dd4cac669c8 100644 --- a/mysql-test/r/handler_innodb.result +++ b/mysql-test/r/handler_innodb.result @@ -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 diff --git a/mysql-test/r/handler_myisam.result b/mysql-test/r/handler_myisam.result index d5d43ca0717..69d791b8263 100644 --- a/mysql-test/r/handler_myisam.result +++ b/mysql-test/r/handler_myisam.result @@ -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 diff --git a/mysql-test/r/mdl_sync.result b/mysql-test/r/mdl_sync.result index d2a32c25201..594cf433692 100644 --- a/mysql-test/r/mdl_sync.result +++ b/mysql-test/r/mdl_sync.result @@ -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'; diff --git a/mysql-test/suite/perfschema/r/dml_setup_instruments.result b/mysql-test/suite/perfschema/r/dml_setup_instruments.result index 2338252976c..15174ee40bf 100644 --- a/mysql-test/suite/perfschema/r/dml_setup_instruments.result +++ b/mysql-test/suite/perfschema/r/dml_setup_instruments.result @@ -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 diff --git a/mysql-test/suite/perfschema/r/func_file_io.result b/mysql-test/suite/perfschema/r/func_file_io.result index 655ce1394f9..8314bed94bf 100644 --- a/mysql-test/suite/perfschema/r/func_file_io.result +++ b/mysql-test/suite/perfschema/r/func_file_io.result @@ -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'; diff --git a/mysql-test/suite/perfschema/r/func_mutex.result b/mysql-test/suite/perfschema/r/func_mutex.result index e32d7267bb1..93ba4064a90 100644 --- a/mysql-test/suite/perfschema/r/func_mutex.result +++ b/mysql-test/suite/perfschema/r/func_mutex.result @@ -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; diff --git a/mysql-test/suite/perfschema/r/global_read_lock.result b/mysql-test/suite/perfschema/r/global_read_lock.result index 93d6adfd049..365ac095e84 100644 --- a/mysql-test/suite/perfschema/r/global_read_lock.result +++ b/mysql-test/suite/perfschema/r/global_read_lock.result @@ -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'; diff --git a/mysql-test/suite/perfschema/r/server_init.result b/mysql-test/suite/perfschema/r/server_init.result index 0c1e06d157c..474a481929a 100644 --- a/mysql-test/suite/perfschema/r/server_init.result +++ b/mysql-test/suite/perfschema/r/server_init.result @@ -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 diff --git a/mysql-test/suite/perfschema/t/func_file_io.test b/mysql-test/suite/perfschema/t/func_file_io.test index dc9a7a09e40..9ba8d061b12 100644 --- a/mysql-test/suite/perfschema/t/func_file_io.test +++ b/mysql-test/suite/perfschema/t/func_file_io.test @@ -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'; diff --git a/mysql-test/suite/perfschema/t/func_mutex.test b/mysql-test/suite/perfschema/t/func_mutex.test index 98cb905c67c..624f5734c40 100644 --- a/mysql-test/suite/perfschema/t/func_mutex.test +++ b/mysql-test/suite/perfschema/t/func_mutex.test @@ -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; diff --git a/mysql-test/suite/perfschema/t/global_read_lock.test b/mysql-test/suite/perfschema/t/global_read_lock.test index b953ea32ce0..9b7c54b1411 100644 --- a/mysql-test/suite/perfschema/t/global_read_lock.test +++ b/mysql-test/suite/perfschema/t/global_read_lock.test @@ -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; diff --git a/mysql-test/suite/perfschema/t/server_init.test b/mysql-test/suite/perfschema/t/server_init.test index cc461374a79..7036464ddb9 100644 --- a/mysql-test/suite/perfschema/t/server_init.test +++ b/mysql-test/suite/perfschema/t/server_init.test @@ -101,9 +101,6 @@ 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"; select count(name) from MUTEX_INSTANCES @@ -133,9 +130,6 @@ 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"; select count(name) from MUTEX_INSTANCES @@ -189,9 +183,6 @@ 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"; select count(name) from COND_INSTANCES diff --git a/mysql-test/suite/rpl/r/rpl_tmp_table_and_DDL.result b/mysql-test/suite/rpl/r/rpl_tmp_table_and_DDL.result index 3136599e5aa..14af782a4d5 100644 --- a/mysql-test/suite/rpl/r/rpl_tmp_table_and_DDL.result +++ b/mysql-test/suite/rpl/r/rpl_tmp_table_and_DDL.result @@ -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 diff --git a/mysql-test/t/delayed.test b/mysql-test/t/delayed.test index 3a2bc982ad3..c47db78a11b 100644 --- a/mysql-test/t/delayed.test +++ b/mysql-test/t/delayed.test @@ -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; diff --git a/mysql-test/t/events_2.test b/mysql-test/t/events_2.test index 08412d2f5b0..3d609654b21 100644 --- a/mysql-test/t/events_2.test +++ b/mysql-test/t/events_2.test @@ -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 diff --git a/mysql-test/t/flush.test b/mysql-test/t/flush.test index 1cafbe3e6bd..944c9c43019 100644 --- a/mysql-test/t/flush.test +++ b/mysql-test/t/flush.test @@ -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; diff --git a/mysql-test/t/flush_block_commit.test b/mysql-test/t/flush_block_commit.test index 0b3bede1684..90443dc9242 100644 --- a/mysql-test/t/flush_block_commit.test +++ b/mysql-test/t/flush_block_commit.test @@ -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; diff --git a/mysql-test/t/flush_block_commit_notembedded.test b/mysql-test/t/flush_block_commit_notembedded.test index 7e3f4838ffb..fe9dbf7c19e 100644 --- a/mysql-test/t/flush_block_commit_notembedded.test +++ b/mysql-test/t/flush_block_commit_notembedded.test @@ -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; diff --git a/mysql-test/t/flush_read_lock.test b/mysql-test/t/flush_read_lock.test new file mode 100644 index 00000000000..b234d941e6e --- /dev/null +++ b/mysql-test/t/flush_read_lock.test @@ -0,0 +1,2187 @@ +# +# Test coverage for various aspects of FLUSH TABLES WITH READ LOCK +# functionality. +# + +# We need InnoDB for COMMIT/ROLLBACK related tests. +--source include/have_innodb.inc +# We need the Debug Sync Facility. +--source include/have_debug_sync.inc +# Save the initial number of concurrent sessions. +--source include/count_sessions.inc + +--echo # FTWRL takes two global metadata locks -- a global shared +--echo # metadata lock and the commit blocker lock. +--echo # The first lock prevents DDL from taking place. +--echo # Let's say that all DDL statements that take metadata +--echo # locks form class #1 -- incompatible with FTWRL because +--echo # take incompatible MDL table locks. +--echo # The first global lock doesn't, however, prevent standalone +--echo # COMMITs (or implicit COMMITs) from taking place, since a +--echo # COMMIT doesn't take table locks. It doesn't prevent +--echo # DDL on temporary tables either, since they don't +--echo # take any table locks either. +--echo # Most DDL statements do not perform an implicit commit +--echo # if operate on a temporary table. Examples are CREATE +--echo # TEMPORARY TABLE and DROP TEMPORARY TABLE. +--echo # Thus, these DDL statements can go through in presence +--echo # of FTWRL. This is class #2 -- compatible because +--echo # do not take incompatible MDL locks and do not issue +--echo # implicit commit.. +--echo # (Although these operations do not commit, their effects +--echo # cannot be rolled back either.) +--echo # ALTER TABLE, ANALYZE, OPTIMIZE and some others always +--echo # issue an implicit commit, even if its argument is a +--echo # temporary table. +--echo # *Howewer* an implicit commit is a no-op if all engines +--echo # used since the start of transactiona are non- +--echo # transactional. Thus, for non-transactional engines, +--echo # these operations are not blocked by FTWRL. +--echo # This is class #3 -- compatible because do not take +--echo # MDL table locks and are non-transactional. +--echo # On the contrary, for transactional engines, there +--echo # is always a commit, regardless of whether a table +--echo # is temporary or not. Thus, for example, ALTER TABLE +--echo # for a transactional engine will wait for FTWRL, +--echo # even if the subject table is temporary. +--echo # Thus ALTER TABLE <temporary> is incompatible +--echo # with FTWRL. This is class #4 -- incompatible +--echo # becuase issue implicit COMMIT which is not a no-op. +--echo # Finally, there are administrative statements (such as +--echo # RESET SLAVE) that do not take any locks and do not +--echo # issue COMMIT. +--echo # This is class #5. +--echo # The goal of this coverage is to test statements +--echo # of all classes. +--echo # @todo: documents the effects of @@autocommit, +--echo # DML and temporary transactional tables. + +--echo # Use MyISAM engine for the most of the tables +--echo # used in this test in order to be able to +--echo # check that DDL statements on temporary tables +--echo # are compatible with FTRWL. +--disable_warnings +drop tables if exists t1_base, t2_base, t3_trans; +drop tables if exists tm_base, tm_base_temp; +drop database if exists mysqltest1; +--echo # We're going to test ALTER DATABASE UPGRADE +drop database if exists `#mysql50#mysqltest-2`; +drop procedure if exists p1; +drop function if exists f1; +drop view if exists v1; +drop procedure if exists p2; +drop function if exists f2_base; +drop function if exists f2_temp; +drop event if exists e1; +drop event if exists e2; +--enable_warnings +create table t1_base(i int) engine=myisam; +create table t2_base(j int) engine=myisam; +create table t3_trans(i int) engine=innodb; +create temporary table t1_temp(i int) engine=myisam; +create temporary table t2_temp(j int) engine=myisam; +create temporary table t3_temp_trans(i int) engine=innodb; +create database mysqltest1; +create database `#mysql50#mysqltest-2`; +create procedure p1() begin end; +create function f1() returns int return 0; +create view v1 as select 1 as i; +create procedure p2(i int) begin end; +delimiter |; +create function f2_base() returns int +begin + insert into t1_base values (1); + return 0; +end| +create function f2_temp() returns int +begin + insert into t1_temp values (1); + return 0; +end| +delimiter ;| +create event e1 on schedule every 1 minute do begin end; + +connect (con1,localhost,root,,); +connect (con2,localhost,root,,); +connect (con3,localhost,root,,); +connection default; + +--echo # +--echo # Test compatibility of FLUSH TABLES WITH READ LOCK +--echo # with various statements. +--echo # +--echo # These tests don't cover some classes of statements: +--echo # - Replication-related - CHANGE MASTER TO, START/STOP SLAVE and etc +--echo # (all compatible with FTWRL). +--echo # - Plugin-related - INSTALL/UNINSTALL (incompatible with FTWRL, +--echo # require plugin support). + +let $con_aux1=con1; +let $con_aux2=con2; +let $cleanup_stmt2= ; +let $skip_3rd_check= ; + +--echo # +--echo # 1) ALTER variants. +--echo # +--echo # 1.1) ALTER TABLE +--echo # +--echo # 1.1.a) For base table should be incompatible with FTWRL. +--echo # +let $statement= alter table t1_base add column c1 int; +let $cleanup_stmt1= alter table t1_base drop column c1; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 1.1.b) For a temporary table should be compatible with FTWRL. +--echo # +let $statement= alter table t1_temp add column c1 int; +let $cleanup_stmt= alter table t1_temp drop column c1; +--source include/check_ftwrl_compatible.inc + +--echo # +--echo # 1.2) ALTER DATABASE should be incompatible with FTWRL. +--echo # +let $statement= alter database mysqltest1 default character set utf8; +let $cleanup_stmt1= alter database mysqltest1 default character set latin1; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 1.3) ALTER DATABASE UPGRADE DATA DIRECTORY NAME should be +--echo # incompatible with FTWRL. +--echo # +let $statement= alter database `#mysql50#mysqltest-2` upgrade data directory name; +let $cleanup_stmt1= drop database `mysqltest-2`; +let $cleanup_stmt2= create database `#mysql50#mysqltest-2`; +--source include/check_ftwrl_incompatible.inc +let $cleanup_stmt2= ; + +--echo # +--echo # 1.4) ALTER PROCEDURE should be incompatible with FTWRL. +--echo # +let $statement= alter procedure p1 comment 'a'; +let $cleanup_stmt1= alter procedure p1 comment ''; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 1.5) ALTER FUNCTION should be incompatible with FTWRL. +--echo # +let $statement= alter function f1 comment 'a'; +let $cleanup_stmt1= alter function f1 comment ''; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 1.6) ALTER VIEW should be incompatible with FTWRL. +--echo # +let $statement= alter view v1 as select 2 as j; +let $cleanup_stmt1= alter view v1 as select 1 as i; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 1.7) ALTER EVENT should be incompatible with FTWRL. +--echo # +let $statement= alter event e1 comment 'test'; +let $cleanup_stmt1= alter event e1 comment ''; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 1.x) The rest of ALTER statements (ALTER TABLESPACE, +--echo # ALTER LOGFILE GROUP and ALTER SERVER) are too +--echo # special to be tested here. +--echo # + + +--echo # +--echo # 2) ANALYZE TABLE statement is compatible with FTWRL. +--echo # See Bug#43336 ANALYZE and OPTIMIZE do not honour +--echo # --read-only for a discussion why. +--echo # +let $statement= analyze table t1_base; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + +--echo # +--echo # 3) BEGIN, ROLLBACK and COMMIT statements. +--echo # BEGIN and ROLLBACK are compatible with FTWRL. +--echo # COMMIT is not. +--echo # +--echo # We need a special test for these statements as +--echo # FTWRL commits a transaction and because COMMIT +--echo # is handled in a special way. +flush tables with read lock; +begin; +--echo # ROLLBACK is allowed under FTWRL although there +--echo # no much sense in it. FTWRL commits any previous +--echo # changes and doesn't allows any DML after it. +--echo # So such a ROLLBACK is always a no-op. +rollback; +--echo # Although COMMIT is incompatible with FTWRL in +--echo # other senses it is still allowed under FTWRL. +--echo # This fact relied upon by some versions of +--echo # innobackup tool. +--echo # Similarly to ROLLBACK it is a no-op in this situation. +commit; +unlock tables; +--echo # Check that BEGIN/ROLLBACK are not blocked and +--echo # COMMIT is blocked by active FTWRL in another +--echo # connection. +--echo # +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +begin; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +unlock tables; +--echo # Switching to connection 'default'. +connection default; +--echo # Do some work so ROLLBACK is not a no-op. +insert into t3_trans values (1); +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +rollback; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +unlock tables; +--echo # Switching to connection 'default'. +connection default; +begin; +--echo # Do some work so COMMIT is not a no-op. +insert into t3_trans values (1); +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +--echo # Send: +--send commit +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +--echo # Wait until COMMIT is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for commit lock" and + info = "commit"; +--source include/wait_condition.inc +unlock tables; +--echo # Switching to connection 'default'. +connection default; +--echo # Reap COMMIT. +--reap +delete from t3_trans; +--echo # +--echo # Check that COMMIT blocks FTWRL in another connection. +begin; +insert into t3_trans values (1); +set debug_sync='RESET'; +set debug_sync='ha_commit_trans_after_acquire_commit_lock SIGNAL parked WAIT_FOR go'; +--send commit +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +set debug_sync='now WAIT_FOR parked'; +--send flush tables with read lock +--echo # Switching to connection '$con_aux2'. +connection $con_aux2; +--echo # Wait until FTWRL is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for commit lock" and + info = "flush tables with read lock"; +--source include/wait_condition.inc +set debug_sync='now SIGNAL go'; +--echo # Switching to connection 'default'. +connection default; +--echo # Reap COMMIT. +--reap +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +--echo # Reap FTWRL. +--reap +unlock tables; +--echo # Switching to connection 'default'. +connection default; +delete from t3_trans; +set debug_sync= "RESET"; +--echo # We don't run similar test for BEGIN and ROLLBACK as +--echo # they release metadata locks in non-standard place. + + +--echo # +--echo # 4) BINLOG statement should be incompatible with FTWRL. +--echo # +--echo # +--echo # Provide format description BINLOG statement first. +BINLOG ' +MfmqTA8BAAAAZwAAAGsAAAABAAQANS41LjctbTMtZGVidWctbG9nAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAx+apMEzgNAAgAEgAEBAQEEgAAVAAEGggAAAAICAgCAA== +'; +--echo # Now test compatibility for BINLOG statement which is +--echo # equivalent to INSERT INTO t1_base VALUES (1). +let $statement= BINLOG ' +MfmqTBMBAAAALgAAAN0AAAAAACgAAAAAAAEABHRlc3QAB3QxX2Jhc2UAAQMAAQ== +MfmqTBcBAAAAIgAAAP8AAAAAACgAAAAAAAEAAf/+AQAAAA== +'; +let $cleanup_stmt1= delete from t1_base where i = 1 limit 1; +--echo # Skip last part of compatibility testing as this statement +--echo # releases metadata locks in non-standard place. +let $skip_3rd_check= 1; +--source include/check_ftwrl_incompatible.inc +let $skip_3rd_check= ; + + +--echo # +--echo # 5) CALL statement. This statement uses resources in two +--echo # ways: through expressions used as parameters and through +--echo # sub-statements. This test covers only usage through +--echo # parameters as sub-statements do locking individually. +--echo # +--echo # 5.a) In simple cases a parameter expression should be +--echo # compatible with FTWRL. +let $statement= call p2((select count(*) from t1_base)); +let $cleanup_stmt1= ; +--echo # Skip last part of compatibility testing as this statement +--echo # releases metadata locks in non-standard place. +let $skip_3rd_check= 1; +--source include/check_ftwrl_compatible.inc +let $skip_3rd_check= ; + +--echo # +--echo # 5.b) In case when an expression uses function which updates +--echo # base tables CALL should be incompatible with FTWRL. +--echo # +let $statement= call p2(f2_base()); +let $cleanup_stmt1= ; +--echo # Skip last part of compatibility testing as this statement +--echo # releases metadata locks in non-standard place. +let $skip_3rd_check= 1; +--source include/check_ftwrl_incompatible.inc +let $skip_3rd_check= ; + +--echo # +--echo # 5.c) If function used as argument updates temporary tables +--echo # CALL statement should be compatible with FTWRL. +--echo # +let $statement= call p2(f2_temp()); +let $cleanup_stmt1= ; +--echo # Skip last part of compatibility testing as this statement +--echo # releases metadata locks in non-standard place. +let $skip_3rd_check= 1; +--source include/check_ftwrl_compatible.inc +let $skip_3rd_check= ; + + +--echo # +--echo # 6) CHECK TABLE statement is compatible with FTWRL. +--echo # +let $statement= check table t1_base; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + + +--echo # +--echo # 7) CHECKSUM TABLE statement is compatible with FTWRL. +--echo # +let $statement= checksum table t1_base; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + + +--echo # +--echo # 8) CREATE variants. +--echo # +--echo # 8.1) CREATE TABLE statement. +--echo # +--echo # 8.1.a) CREATE TABLE is incompatible with FTWRL when +--echo # base table is created. +let $statement= create table t3_base(i int); +let $cleanup_stmt1= drop table t3_base; +--source include/check_ftwrl_incompatible.inc + +--echo # 8.1.b) CREATE TABLE is compatible with FTWRL when +--echo # temporary table is created. +let $statement= create temporary table t3_temp(i int); +let $cleanup_stmt= drop temporary tables t3_temp; +--source include/check_ftwrl_compatible.inc + +--echo # 8.1.c) CREATE TABLE LIKE is incompatible with FTWRL when +--echo # base table is created. +let $statement= create table t3_base like t1_temp; +let $cleanup_stmt1= drop table t3_base; +--source include/check_ftwrl_incompatible.inc + +--echo # 8.1.d) CREATE TABLE LIKE is compatible with FTWRL when +--echo # temporary table is created. +let $statement= create temporary table t3_temp like t1_base; +let $cleanup_stmt= drop temporary table t3_temp; +--source include/check_ftwrl_compatible.inc + +--echo # 8.1.e) CREATE TABLE SELECT is incompatible with FTWRL when +--echo # base table is created. +let $statement= create table t3_base select 1 as i; +let $cleanup_stmt1= drop table t3_base; +--source include/check_ftwrl_incompatible.inc + +--echo # 8.1.f) CREATE TABLE SELECT is compatible with FTWRL when +--echo # temporary table is created. +let $statement= create temporary table t3_temp select 1 as i; +let $cleanup_stmt= drop temporary table t3_temp; +--source include/check_ftwrl_compatible.inc + +--echo # 8.2) CREATE INDEX statement. +--echo # +--echo # 8.2.a) CREATE INDEX is incompatible with FTWRL when +--echo # applied to base table. +let $statement= create index i on t1_base (i); +let $cleanup_stmt1= drop index i on t1_base; +--source include/check_ftwrl_incompatible.inc + +--echo # 8.2.b) CREATE INDEX is compatible with FTWRL when +--echo # applied to temporary table. +let $statement= create index i on t1_temp (i); +let $cleanup_stmt= drop index i on t1_temp; +--source include/check_ftwrl_compatible.inc + +--echo # +--echo # 8.3) CREATE DATABASE is incompatible with FTWRL. +--echo # +let $statement= create database mysqltest2; +let $cleanup_stmt1= drop database mysqltest2; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 8.4) CREATE VIEW is incompatible with FTWRL. +--echo # +let $statement= create view v2 as select 1 as j; +let $cleanup_stmt1= drop view v2; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 8.5) CREATE TRIGGER is incompatible with FTWRL. +--echo # +let $statement= create trigger t1_bi before insert on t1_base for each row begin end; +let $cleanup_stmt1= drop trigger t1_bi; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 8.6) CREATE FUNCTION is incompatible with FTWRL. +--echo # +let $statement= create function f2() returns int return 0; +let $cleanup_stmt1= drop function f2; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 8.7) CREATE PROCEDURE is incompatible with FTWRL. +--echo # +let $statement= create procedure p3() begin end; +let $cleanup_stmt1= drop procedure p3; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 8.8) CREATE EVENT should be incompatible with FTWRL. +--echo # +let $statement= create event e2 on schedule every 1 minute do begin end; +let $cleanup_stmt1= drop event e2; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 8.9) CREATE USER should be incompatible with FTWRL. +--echo # +let $statement= create user mysqltest_u1; +let $cleanup_stmt1= drop user mysqltest_u1; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 8.x) The rest of CREATE variants (CREATE LOGFILE GROUP, +--echo # CREATE TABLESPACE and CREATE SERVER) are too special +--echo # to test here. +--echo # + + +--echo # +--echo # 9) PREPARE, EXECUTE and DEALLOCATE PREPARE statements. +--echo # +--echo # 9.1) PREPARE statement is compatible with FTWRL as it +--echo # doesn't change any data. +--echo # +--echo # 9.1.a) Prepare of simple INSERT statement. +--echo # +let $statement= prepare stmt1 from 'insert into t1_base values (1)'; +let $cleanup_stmt= deallocate prepare stmt1; +--echo # Skip last part of compatibility testing as this statement +--echo # releases metadata locks in non-standard place. +let $skip_3rd_check= 1; +--source include/check_ftwrl_compatible.inc +let $skip_3rd_check= ; + +--echo # +--echo # 9.1.b) Prepare of multi-UPDATE. At some point such statements +--echo # tried to acquire thr_lock.c locks during prepare phase. +--echo # This no longer happens and thus it is compatible with +--echo # FTWRL. +let $statement= prepare stmt1 from 'update t1_base, t2_base set t1_base.i= 1 where t1_base.i = t2_base.j'; +let $cleanup_stmt= deallocate prepare stmt1; +--echo # Skip last part of compatibility testing as this statement +--echo # releases metadata locks in non-standard place. +let $skip_3rd_check= 1; +--source include/check_ftwrl_compatible.inc +let $skip_3rd_check= ; + +--echo # +--echo # 9.1.c) Prepare of multi-DELETE. Again PREPARE of such +--echo # statement should be compatible with FTWRL. +let $statement= prepare stmt1 from 'delete t1_base from t1_base, t2_base where t1_base.i = t2_base.j'; +let $cleanup_stmt= deallocate prepare stmt1; +--echo # Skip last part of compatibility testing as this statement +--echo # releases metadata locks in non-standard place. +let $skip_3rd_check= 1; +--source include/check_ftwrl_compatible.inc +let $skip_3rd_check= ; + +--echo # +--echo # 9.2) Compatibility of EXECUTE statement depends on statement +--echo # to be executed. +--echo # +--echo # 9.2.a) EXECUTE for statement which is itself compatible with +--echo # FTWRL should be compatible. +prepare stmt1 from 'select * from t1_base'; +let $statement= execute stmt1; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc +deallocate prepare stmt1; + +--echo # +--echo # 9.2.b) EXECUTE for statement which is incompatible with FTWRL +--echo # should be also incompatible. +--echo # +--echo # Check that EXECUTE is not allowed under FTWRL. +prepare stmt1 from 'insert into t1_base values (1)'; +flush tables with read lock; +--error ER_CANT_UPDATE_WITH_READLOCK +execute stmt1; +unlock tables; +--echo # Check that active FTWRL in another connection +--echo # blocks EXECUTE which changes data. +--echo # +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +--send execute stmt1 +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +--echo # Check that EXECUTE is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for global read lock" and + info = "insert into t1_base values (1)"; +--source include/wait_condition.inc +unlock tables; +--echo # Switching to connection 'default'. +connection default; +--echo # Reap EXECUTE. +--reap +set debug_sync='RESET'; +set debug_sync='execute_command_after_close_tables SIGNAL parked WAIT_FOR go'; +--send execute stmt1; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +set debug_sync='now WAIT_FOR parked'; +--send flush tables with read lock +--echo # Switching to connection '$con_aux2'. +connection $con_aux2; +--echo # Wait until FTWRL is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for global read lock" and + info = "flush tables with read lock"; +--source include/wait_condition.inc +set debug_sync='now SIGNAL go'; +--echo # Switching to connection 'default'. +connection default; +--echo # Reap EXECUTE. +--reap +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +--echo # Reap FTWRL. +--reap +unlock tables; +--echo # Switching to connection 'default'. +connection default; +set debug_sync= "RESET"; +delete from t1_base; +deallocate prepare stmt1; + +--echo # +--echo # 9.3) DEALLOCATE PREPARE is compatible with FTWRL. +--echo # +prepare stmt1 from 'insert into t1_base values (1)'; +let $statement= deallocate prepare stmt1; +let $cleanup_stmt= prepare stmt1 from 'insert into t1_base values (1)'; +--source include/check_ftwrl_compatible.inc +deallocate prepare stmt1; + + +--echo # +--echo # 10) DELETE variations. +--echo # +--echo # 10.1) Simple DELETE. +--echo # +--echo # 10.1.a) Simple DELETE on base table is incompatible with FTWRL. +let $statement= delete from t1_base; +let $cleanup_stmt1= ; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 10.1.b) Simple DELETE on temporary table is compatible with FTWRL. +let $statement= delete from t1_temp; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + +--echo # +--echo # 10.2) Multi DELETE. +--echo # +--echo # 10.2.a) Multi DELETE on base tables is incompatible with FTWRL. +let $statement= delete t1_base from t1_base, t2_base where t1_base.i = t2_base.j; +let $cleanup_stmt1= ; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 10.2.b) Multi DELETE on temporary tables is compatible with FTWRL. +let $statement= delete t1_temp from t1_temp, t2_temp where t1_temp.i = t2_temp.j; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + + +--echo # +--echo # 11) DESCRIBE should be compatible with FTWRL. +--echo # +let $statement= describe t1_base; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + + +--echo # +--echo # 12) Compatibility of DO statement with FTWRL depends on its +--echo # expression. +--echo # +--echo # 12.a) DO with expression which does not change base table +--echo # should be compatible with FTWRL. +let $statement= do (select count(*) from t1_base); +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + +--echo # +--echo # 12.b) DO which calls SF updating base table should be +--echo # incompatible with FTWRL. +let $statement= do f2_base(); +let $cleanup_stmt1= delete from t1_base limit 1; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 12.c) DO which calls SF updating temporary table should be +--echo # compatible with FTWRL. +let $statement= do f2_temp(); +let $cleanup_stmt= delete from t1_temp limit 1; +--source include/check_ftwrl_compatible.inc + + +--echo # +--echo # 13) DROP variants. +--echo # +--echo # 13.1) DROP TABLES. +--echo # +--echo # 13.1.a) DROP TABLES which affects base tables is incompatible +--echo # with FTWRL. +let $statement= drop table t2_base; +let $cleanup_stmt1= create table t2_base(j int); +--source include/check_ftwrl_incompatible.inc + +--echo # 13.1.b) DROP TABLES which affects only temporary tables +--echo # in theory can be compatible with FTWRL. +--echo # In practice it is not yet. +let $statement= drop table t2_temp; +let $cleanup_stmt1= create temporary table t2_temp(j int); +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 13.1.c) DROP TEMPORARY TABLES should be compatible with FTWRL. +let $statement= drop temporary table t2_temp; +let $cleanup_stmt= create temporary table t2_temp(j int); +--source include/check_ftwrl_compatible.inc + +--echo # +--echo # 13.2) DROP INDEX. +--echo # +--echo # 13.2.a) DROP INDEX on a base table is incompatible with FTWRL. +create index i on t1_base (i); +let $statement= drop index i on t1_base; +let $cleanup_stmt1= create index i on t1_base (i); +--source include/check_ftwrl_incompatible.inc +drop index i on t1_base; + +--echo # +--echo # 13.2.b) DROP INDEX on a temporary table is compatible with FTWRL. +create index i on t1_temp (i); +let $statement= drop index i on t1_temp; +let $cleanup_stmt= create index i on t1_temp (i); +--source include/check_ftwrl_compatible.inc +drop index i on t1_temp; + +--echo # +--echo # 13.3) DROP DATABASE is incompatible with FTWRL +--echo # +let $statement= drop database mysqltest1; +let $cleanup_stmt1= create database mysqltest1; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 13.4) DROP FUNCTION is incompatible with FTWRL. +--echo # +let $statement= drop function f1; +let $cleanup_stmt1= create function f1() returns int return 0; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 13.5) DROP PROCEDURE is incompatible with FTWRL. +--echo # +let $statement= drop procedure p1; +let $cleanup_stmt1= create procedure p1() begin end; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 13.6) DROP USER should be incompatible with FTWRL. +--echo # +create user mysqltest_u1; +let $statement= drop user mysqltest_u1; +let $cleanup_stmt1= create user mysqltest_u1; +--source include/check_ftwrl_incompatible.inc +drop user mysqltest_u1; + +--echo # +--echo # 13.7) DROP VIEW should be incompatible with FTWRL. +--echo # +let $statement= drop view v1; +let $cleanup_stmt1= create view v1 as select 1 as i; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 13.8) DROP EVENT should be incompatible with FTWRL. +--echo # +let $statement= drop event e1; +let $cleanup_stmt1= create event e1 on schedule every 1 minute do begin end; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 13.9) DROP TRIGGER is incompatible with FTWRL. +--echo # +create trigger t1_bi before insert on t1_base for each row begin end; +let $statement= drop trigger t1_bi; +let $cleanup_stmt1= create trigger t1_bi before insert on t1_base for each row begin end; +--source include/check_ftwrl_incompatible.inc +drop trigger t1_bi; + +--echo # +--echo # 13.x) The rest of DROP variants (DROP TABLESPACE, DROP LOGFILE +--echo # GROUP and DROP SERVER) are too special to test here. +--echo # + + +--echo # +--echo # 14) FLUSH variants. +--echo # +--echo # Test compatibility of _some_ important FLUSH variants with FTWRL. +--echo # +--echo # 14.1) FLUSH TABLES WITH READ LOCK is compatible with itself. +--echo # +--echo # Check that FTWRL statements can be run while FTWRL +--echo # is active in another connection. +--echo # +--echo # Switching to connection '$con_aux1'. +flush tables with read lock; +--echo # The second FTWRL in a row is allowed at the moment. +--echo # It does not make much sense as it does only flush. +flush tables with read lock; +unlock tables; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +flush tables with read lock; +unlock tables; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +unlock tables; +--echo # Switching to connection 'default'. +connection default; + +--echo # +--echo # 14.2) FLUSH TABLES <list> WITH READ LOCK is not blocked by +--echo # active FTWRL. But since the latter keeps tables open +--echo # FTWRL is blocked by FLUSH TABLES <list> WITH READ LOCK. +flush tables with read lock; +--echo # FT <list> WRL is allowed under FTWRL at the moment. +--echo # It does not make much sense though. +flush tables t1_base, t2_base with read lock; +unlock tables; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +flush tables t1_base, t2_base with read lock; +unlock tables; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +unlock tables; +--echo # Switching to connection 'default'. +connection default; +flush tables t1_base, t2_base with read lock; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +--send flush tables with read lock +--echo # Switching to connection '$con_aux2'. +connection $con_aux2; +--echo # Wait until FTWRL is blocked. +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; +unlock tables; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +--echo # Reap FTWRL. +--reap +unlock tables; +--echo # Switching to connection 'default'. +connection default; + + +--echo # +--echo # 14.3) FLUSH TABLES is compatible with FTWRL. +let $statement= flush tables; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + +--echo # +--echo # 14.4) FLUSH TABLES <list> is compatible with FTWRL. +let $statement= flush table t1_base, t2_base; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + +--echo # +--echo # 14.5) FLUSH PRIVILEGES is compatible with FTWRL. +let $statement= flush privileges; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + + +--echo # +--echo # 15) GRANT statement should be incompatible with FTWRL. +--echo # +let $statement= grant all privileges on t1_base to mysqltest_u1; +let $cleanup_stmt1= revoke all privileges on t1_base from mysqltest_u1; +--source include/check_ftwrl_incompatible.inc +drop user mysqltest_u1; + + +--echo # +--echo # 16) All HANDLER variants are half-compatible with FTWRL. +--echo # I.e. they are not blocked by active FTWRL. But since open +--echo # HANDLER means open table instance FTWRL is blocked while +--echo # HANDLER is not closed. +--echo # +--echo # Check that HANDLER statements succeed under FTWRL. +flush tables with read lock; +handler t1_base open; +handler t1_base read first; +handler t1_base close; +unlock tables; +--echo # Check that HANDLER statements can be run while FTWRL +--echo # is active in another connection. +--echo # +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +handler t1_base open; +handler t1_base read first; +handler t1_base close; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +unlock tables; +--echo # Switching to connection 'default'. +connection default; + + +--echo # +--echo # 17) HELP statement is compatible with FTWRL. +--echo # +let $statement= help no_such_topic; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + + +--echo # +--echo # 18) INSERT statement. +--echo # +--echo # 18.a) Ordinary INSERT into base table is incompatible with FTWRL. +let $statement= insert into t1_base values (1); +let $cleanup_stmt1= delete from t1_base limit 1; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 18.b) Ordinary INSERT into temp table is compatible with FTWRL. +let $statement= insert into t1_temp values (1); +let $cleanup_stmt= delete from t1_temp limit 1; +--source include/check_ftwrl_compatible.inc + +--echo # +--echo # 18.c) INSERT DELAYED is incompatible with FTWRL. +let $statement= insert delayed into t1_base values (1); +let $cleanup_stmt1= ; +--source include/check_ftwrl_incompatible.inc +delete from t1_base; + +--echo # +--echo # 18.d) INSERT SELECT into base table is incompatible with FTWRL. +let $statement= insert into t1_base select * from t1_temp; +let $cleanup_stmt1= ; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 18.e) INSERT SELECT into temp table is compatible with FTWRL. +let $statement= insert into t1_temp select * from t1_base; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + + +--echo # +--echo # 19) KILL statement is compatible with FTWRL. +--echo # +--echo # Check that KILL can be run under FTWRL. +flush tables with read lock; +set @id:= connection_id(); +--error ER_QUERY_INTERRUPTED +kill query @id; +unlock tables; +--echo # Check that KILL statements can be run while FTWRL +--echo # is active in another connection. +--echo # +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +--error ER_QUERY_INTERRUPTED +kill query @id; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +unlock tables; +--echo # Switching to connection 'default'. +connection default; +--echo # Finally check that KILL doesn't block FTWRL +set debug_sync='RESET'; +set debug_sync='execute_command_after_close_tables SIGNAL parked WAIT_FOR go'; +--send kill query @id +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +set debug_sync='now WAIT_FOR parked'; +flush tables with read lock; +unlock tables; +set debug_sync='now SIGNAL go'; +--echo # Switching to connection 'default'. +connection default; +--echo # Reap KILL. +--error ER_QUERY_INTERRUPTED +--reap +set debug_sync='RESET'; + + +--echo # +--echo # 20) LOAD DATA statement. +--echo # +--echo # 20.a) LOAD DATA into base table is incompatible with FTWRL. +let $statement= load data infile '../../std_data/rpl_loaddata.dat' into table t1_base (@dummy, i); +let $cleanup_stmt1= delete from t1_base; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 20.b) LOAD DATA into temporary table is compatible with FTWRL. +let $statement= load data infile '../../std_data/rpl_loaddata.dat' into table t1_temp (@dummy, i); +let $cleanup_stmt= delete from t1_temp; +--source include/check_ftwrl_compatible.inc + + +--echo # +--echo # 21) LOCK/UNLOCK TABLES statements. +--echo # +--echo # LOCK TABLES statement always (almost) blocks FTWRL as it +--echo # keeps tables open until UNLOCK TABLES. +--echo # Active FTWRL on the other hand blocks only those +--echo # LOCK TABLES which allow updating of base tables. +--echo # +--echo # 21.a) LOCK TABLES READ is allowed under FTWRL and +--echo # is not blocked by active FTWRL. +flush tables with read lock; +lock tables t1_base read; +unlock tables; +--echo # +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +lock tables t1_base read; +unlock tables; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +unlock tables; +--echo # Switching to connection 'default'. +connection default; + +--echo # +--echo # 21.b) LOCK TABLES WRITE on a base table is disallowed +--echo # under FTWRL and should be blocked by active FTWRL. +flush tables with read lock; +--error ER_CANT_UPDATE_WITH_READLOCK +lock tables t1_base write; +unlock tables; +--echo # +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +--send lock tables t1_base write +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +--echo # Check that LOCK TABLES WRITE is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for global read lock" and + info = "lock tables t1_base write"; +--source include/wait_condition.inc +unlock tables; +--echo # Switching to connection 'default'. +connection default; +--echo # Reap LOCK TABLES WRITE +--reap +unlock tables; + +--echo # +--echo # 21.c) LOCK TABLES WRITE on temporary table doesn't +--echo # make much sense but is allowed under FTWRL +--echo # and should not be blocked by active FTWRL. +flush tables with read lock; +lock tables t1_temp write; +unlock tables; +--echo # +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +lock tables t1_temp write; +unlock tables; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +unlock tables; +--echo # Switching to connection 'default'. +connection default; + + +--echo # +--echo # 22) OPTIMIZE TABLE statement. +--echo # +--echo # 22.a) OPTIMIZE TABLE of base table is incompatible with FTWRL. +flush tables with read lock; +--echo # OPTIMIZE statement returns errors as part of result-set. +optimize table t1_base; +unlock tables; +--echo # +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +--send optimize table t1_base +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +--echo # Check that OPTIMIZE TABLE is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for global read lock" and + info = "optimize table t1_base"; +--source include/wait_condition.inc +unlock tables; +--echo # Switching to connection 'default'. +connection default; +--echo # Reap OPTIMIZE TABLE +--reap +--echo # We don't check that active OPTIMIZE TABLE blocks +--echo # FTWRL as this one of statements releasing metadata +--echo # locks in non-standard place. + +--echo # +--echo # 22.b) OPTIMIZE TABLE of temporary table is compatible with FTWRL. +let $statement= optimize table t1_temp; +let $cleanup_stmt= ; +--echo # Skip last part of compatibility testing as this statement +--echo # releases metadata locks in non-standard place. +let $skip_3rd_check= 1; +--source include/check_ftwrl_compatible.inc +let $skip_3rd_check= ; + + +--echo # +--echo # 23) CACHE statement is compatible with FTWRL. +--echo # +let $statement= cache index t1_base in default; +let $cleanup_stmt= ; +--echo # Skip last part of compatibility testing as this statement +--echo # releases metadata locks in non-standard place. +let $skip_3rd_check= 1; +--source include/check_ftwrl_compatible.inc +let $skip_3rd_check= ; + + +--echo # +--echo # 24) LOAD INDEX statement is compatible with FTWRL. +--echo # +let $statement= load index into cache t1_base; +let $cleanup_stmt= ; +--echo # Skip last part of compatibility testing as this statement +--echo # releases metadata locks in non-standard place. +let $skip_3rd_check= 1; +--source include/check_ftwrl_compatible.inc +let $skip_3rd_check= ; + + +--echo # +--echo # 25) SAVEPOINT/RELEASE SAVEPOINT/ROLLBACK TO SAVEPOINT are +--echo # compatible with FTWRL. +--echo # +--echo # Since manipulations on savepoint have to be done +--echo # inside transaction and FTWRL commits transaction we +--echo # need a special test for these statements. +flush tables with read lock; +begin; +savepoint sv1; +rollback to savepoint sv1; +release savepoint sv1; +unlock tables; +commit; +--echo # Check that these statements are not blocked by +--echo # active FTWRL in another connection. +--echo # +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +begin; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +unlock tables; +--echo # Switching to connection 'default'. +connection default; +--echo # Do some changes to avoid SAVEPOINT and friends +--echo # being almost no-ops. +insert into t3_trans values (1); +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +savepoint sv1; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +unlock tables; +--echo # Switching to connection 'default'. +connection default; +insert into t3_trans values (2); +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +rollback to savepoint sv1; +release savepoint sv1; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +unlock tables; +--echo # Switching to connection 'default'. +connection default; +rollback; +--echo # Check that these statements don't block FTWRL in +--echo # another connection. +begin; +--echo # Do some changes to avoid SAVEPOINT and friends +--echo # being almost no-ops. +insert into t3_trans values (1); +set debug_sync='RESET'; +set debug_sync='execute_command_after_close_tables SIGNAL parked WAIT_FOR go'; +--send savepoint sv1 +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +set debug_sync='now WAIT_FOR parked'; +flush tables with read lock; +unlock tables; +set debug_sync='now SIGNAL go'; +--echo # Switching to connection 'default'. +connection default; +--echo # Reap SAVEPOINT +--reap +insert into t3_trans values (2); +set debug_sync='execute_command_after_close_tables SIGNAL parked WAIT_FOR go'; +--send rollback to savepoint sv1 +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +set debug_sync='now WAIT_FOR parked'; +flush tables with read lock; +unlock tables; +set debug_sync='now SIGNAL go'; +--echo # Switching to connection 'default'. +connection default; +--echo # Reap ROLLBACK TO SAVEPOINT +--reap +set debug_sync='execute_command_after_close_tables SIGNAL parked WAIT_FOR go'; +--send release savepoint sv1 +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +set debug_sync='now WAIT_FOR parked'; +flush tables with read lock; +unlock tables; +set debug_sync='now SIGNAL go'; +--echo # Switching to connection 'default'. +connection default; +--echo # Reap RELEASE SAVEPOINT +--reap +rollback; +set debug_sync= "RESET"; + + +--echo # +--echo # 26) RENAME variants. +--echo # +--echo # 26.1) RENAME TABLES is incompatible with FTWRL. +let $statement= rename table t1_base to t3_base; +let $cleanup_stmt1= rename table t3_base to t1_base; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 26.2) RENAME USER is incompatible with FTWRL. +create user mysqltest_u1; +let $statement= rename user mysqltest_u1 to mysqltest_u2; +let $cleanup_stmt1= rename user mysqltest_u2 to mysqltest_u1; +--source include/check_ftwrl_incompatible.inc +drop user mysqltest_u1; + + +--echo # +--echo # 27) REPAIR TABLE statement. +--echo # +--echo # 27.a) REPAIR TABLE of base table is incompatible with FTWRL. +flush tables with read lock; +--echo # REPAIR statement returns errors as part of result-set. +repair table t1_base; +unlock tables; +--echo # +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +--send repair table t1_base +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +--echo # Check that REPAIR TABLE is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for global read lock" and + info = "repair table t1_base"; +--source include/wait_condition.inc +unlock tables; +--echo # Switching to connection 'default'. +connection default; +--echo # Reap REPAIR TABLE +--reap +--echo # We don't check that active REPAIR TABLE blocks +--echo # FTWRL as this one of statements releasing metadata +--echo # locks in non-standard place. + +--echo # +--echo # 27.b) REPAIR TABLE of temporary table is compatible with FTWRL. +let $statement= repair table t1_temp; +let $cleanup_stmt= ; +--echo # Skip last part of compatibility testing as this statement +--echo # releases metadata locks in non-standard place. +let $skip_3rd_check= 1; +--source include/check_ftwrl_compatible.inc +let $skip_3rd_check= ; + + +--echo # +--echo # 28) REPLACE statement. +--echo # +--echo # 28.a) Ordinary REPLACE into base table is incompatible with FTWRL. +let $statement= replace into t1_base values (1); +let $cleanup_stmt1= delete from t1_base limit 1; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 28.b) Ordinary REPLACE into temp table is compatible with FTWRL. +let $statement= replace into t1_temp values (1); +let $cleanup_stmt= delete from t1_temp limit 1; +--source include/check_ftwrl_compatible.inc + +--echo # +--echo # 28.c) REPLACE SELECT into base table is incompatible with FTWRL. +let $statement= replace into t1_base select * from t1_temp; +let $cleanup_stmt1= ; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 28.d) REPLACE SELECT into temp table is compatible with FTWRL. +let $statement= replace into t1_temp select * from t1_base; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + + +--echo # +--echo # 29) REVOKE variants. +--echo # +--echo # 29.1) REVOKE privileges is incompatible with FTWRL. +grant all privileges on t1_base to mysqltest_u1; +let $statement= revoke all privileges on t1_base from mysqltest_u1; +let $cleanup_stmt1= grant all privileges on t1_base to mysqltest_u1; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 29.2) REVOKE ALL PRIVILEGES, GRANT OPTION is incompatible with FTWRL. +let $statement= revoke all privileges, grant option from mysqltest_u1; +let $cleanup_stmt1= grant all privileges on t1_base to mysqltest_u1; +--source include/check_ftwrl_incompatible.inc +drop user mysqltest_u1; + + +--echo # +--echo # 30) Compatibility of SELECT statement with FTWRL depends on +--echo # locking mode used and on functions being invoked by it. +--echo # +--echo # 30.a) Simple SELECT which does not change tables should be +--echo # compatible with FTWRL. +let $statement= select count(*) from t1_base; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + +--echo # 30.b) SELECT ... FOR UPDATE is incompatible with FTWRL. +let $statement= select count(*) from t1_base for update; +let $cleanup_stmt1= ; +--source include/check_ftwrl_incompatible.inc + +--echo # 30.c) SELECT ... LOCK IN SHARE MODE is compatible with FTWRL. +let $statement= select count(*) from t1_base lock in share mode; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + +--echo # +--echo # 30.d) SELECT which calls SF updating base table should be +--echo # incompatible with FTWRL. +let $statement= select f2_base(); +let $cleanup_stmt1= delete from t1_base limit 1; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 30.e) SELECT which calls SF updating temporary table should be +--echo # compatible with FTWRL. +let $statement= select f2_temp(); +let $cleanup_stmt= delete from t1_temp limit 1; +--source include/check_ftwrl_compatible.inc + + +--echo # +--echo # 31) Compatibility of SET statement with FTWRL depends on its +--echo # expression and on whether it is a special SET statement. +--echo # +--echo # 31.a) Ordinary SET with expression which does not +--echo # changes base table should be compatible with FTWRL. +let $statement= set @a:= (select count(*) from t1_base); +let $cleanup_stmt= ; +--echo # Skip last part of compatibility testing as our helper debug +--echo # sync-point doesn't work for SET statements. +let $skip_3rd_check= 1; +--source include/check_ftwrl_compatible.inc +let $skip_3rd_check= ; + +--echo # +--echo # 31.b) Ordinary SET which calls SF updating base table should +--echo # be incompatible with FTWRL. +let $statement= set @a:= f2_base(); +let $cleanup_stmt1= delete from t1_base limit 1; +--echo # Skip last part of compatibility testing as our helper debug +--echo # sync-point doesn't work for SET statements. +let $skip_3rd_check= 1; +--source include/check_ftwrl_incompatible.inc +let $skip_3rd_check= ; + +--echo # +--echo # 31.c) Ordinary SET which calls SF updating temporary table +--echo # should be compatible with FTWRL. +let $statement= set @a:= f2_temp(); +let $cleanup_stmt= delete from t1_temp limit 1; +--echo # Skip last part of compatibility testing as our helper debug +--echo # sync-point doesn't work for SET statements. +let $skip_3rd_check= 1; +--source include/check_ftwrl_compatible.inc +let $skip_3rd_check= ; + +--echo # +--echo # 31.d) Special SET variants have different compatibility with FTWRL. +--echo # +--echo # 31.d.I) SET PASSWORD is incompatible with FTWRL as it changes data. +create user mysqltest_u1; +let $statement= set password for 'mysqltest_u1' = password(''); +let $cleanup_stmt1= ; +--echo # Skip last part of compatibility testing as our helper debug +--echo # sync-point doesn't work for SET statements. +let $skip_3rd_check= 1; +--source include/check_ftwrl_incompatible.inc +let $skip_3rd_check= ; +drop user mysqltest_u1; +--echo # +--echo # 31.d.II) SET READ_ONLY is compatible with FTWRL (but has no +--echo # effect when executed under it). +let $statement= set global read_only= 1; +let $cleanup_stmt= set global read_only= 0; +--echo # Skip last part of compatibility testing as our helper debug +--echo # sync-point doesn't work for SET statements. +let $skip_3rd_check= 1; +--source include/check_ftwrl_compatible.inc +let $skip_3rd_check= ; +--echo # +--echo # 31.d.III) Situation with SET AUTOCOMMIT is complex. +--echo # Turning auto-commit off is always compatible with FTWRL. +--echo # Turning auto-commit on causes implicit commit and so +--echo # is incompatible with FTWRL if there are changes to be +--echo # committed. +flush tables with read lock; +set autocommit= 0; +--echo # Turning auto-commit on causes implicit commit so can +--echo # be incompatible with FTWRL if there is something to +--echo # commit. But since even in this case we allow commits +--echo # under active FTWRL such statement should always succeed. +insert into t3_temp_trans values (1); +set autocommit= 1; +unlock tables; +delete from t3_temp_trans; +--echo # Check that SET AUTOCOMMIT=0 is not blocked and +--echo # SET AUTOCOMMIT=1 is blocked by active FTWRL in +--echo # another connection. +--echo # +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +set autocommit= 0; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +unlock tables; +--echo # Switching to connection 'default'. +connection default; +--echo # Do some work so implicit commit in SET AUTOCOMMIT=1 +--echo # is not a no-op. +insert into t3_trans values (1); +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +--echo # Send: +--send set autocommit= 1 +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +--echo # Wait until SET AUTOCOMMIT=1 is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for commit lock" and + info = "set autocommit= 1"; +--source include/wait_condition.inc +unlock tables; +--echo # Switching to connection 'default'. +connection default; +--echo # Reap SET AUTOCOMMIT=1. +--reap +delete from t3_trans; +--echo # +--echo # Check that SET AUTOCOMMIT=1 blocks FTWRL in another connection. +set autocommit= 0; +insert into t3_trans values (1); +set debug_sync='RESET'; +set debug_sync='ha_commit_trans_after_acquire_commit_lock SIGNAL parked WAIT_FOR go'; +--send set autocommit= 1 +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +set debug_sync='now WAIT_FOR parked'; +--send flush tables with read lock +--echo # Switching to connection '$con_aux2'. +connection $con_aux2; +--echo # Wait until FTWRL is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for commit lock" and + info = "flush tables with read lock"; +--source include/wait_condition.inc +set debug_sync='now SIGNAL go'; +--echo # Switching to connection 'default'. +connection default; +--echo # Reap SET AUTOCOMMIT=1. +--reap +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +--echo # Reap FTWRL. +--reap +unlock tables; +--echo # Switching to connection 'default'. +connection default; +delete from t3_trans; +set debug_sync= "RESET"; + + +--echo # +--echo # 32) SHOW statements are compatible with FTWRL. +--echo # Let us test _some_ of them. +--echo # +--echo # 32.1) SHOW TABLES. +let $statement= show tables from test; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + +--echo # +--echo # 32.1) SHOW TABLES. +let $statement= show tables from test; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + +--echo # +--echo # 32.2) SHOW EVENTS. +let $statement= show events from test; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + +--echo # +--echo # 32.3) SHOW GRANTS. +create user mysqltest_u1; +let $statement= show grants for mysqltest_u1; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc +drop user mysqltest_u1; + +--echo # +--echo # 32.4) SHOW CREATE TABLE. +let $statement= show create table t1_base; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + +--echo # +--echo # 32.5) SHOW CREATE FUNCTION. +let $statement= show create function f1; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + + +--echo # +--echo # 33) SIGNAL statement is compatible with FTWRL. +--echo # +--echo # Note that we don't cover RESIGNAL as it requires +--echo # active handler context. +let $statement= signal sqlstate '01000'; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + + +--echo # +--echo # 34) TRUNCATE TABLE statement. +--echo # +--echo # 34.a) TRUNCATE of base table is incompatible with FTWRL. +let $statement= truncate table t1_base; +let $cleanup_stmt1= ; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 34.b) TRUNCATE of temporary table is compatible with FTWRL. +let $statement= truncate table t1_temp; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + + +--echo # +--echo # 35) UPDATE variants. +--echo # +--echo # 35.1) Simple UPDATE. +--echo # +--echo # 35.1.a) Simple UPDATE on base table is incompatible with FTWRL. +let $statement= update t1_base set i= 1 where i = 0; +let $cleanup_stmt1= ; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 35.1.b) Simple UPDATE on temporary table is compatible with FTWRL. +let $statement= update t1_temp set i= 1 where i = 0; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + +--echo # +--echo # 35.2) Multi UPDATE. +--echo # +--echo # 35.2.a) Multi UPDATE on base tables is incompatible with FTWRL. +let $statement= update t1_base, t2_base set t1_base.i= 1 where t1_base.i = t2_base.j; +let $cleanup_stmt1= ; +--source include/check_ftwrl_incompatible.inc + +--echo # +--echo # 35.2.b) Multi UPDATE on temporary tables is compatible with FTWRL. +let $statement= update t1_temp, t2_temp set t1_temp.i= 1 where t1_temp.i = t2_temp.j; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + + +--echo # +--echo # 36) USE statement is compatible with FTWRL. +--echo # +let $statement= use mysqltest1; +let $cleanup_stmt= use test; +--source include/check_ftwrl_compatible.inc + + +--echo # +--echo # 37) XA statements. +--echo # +--echo # XA statements are similar to BEGIN/COMMIT/ROLLBACK. +--echo # +--echo # XA BEGIN, END, PREPARE, ROLLBACK and RECOVER are compatible +--echo # with FTWRL. XA COMMIT is not. +flush tables with read lock; +--echo # Although all below statements are allowed under FTWRL they +--echo # are almost no-ops as FTWRL does commit and does not allows +--echo # any non-temporary DML under it. +xa start 'test1'; +xa end 'test1'; +xa prepare 'test1'; +xa rollback 'test1'; +xa start 'test1'; +xa end 'test1'; +xa prepare 'test1'; +xa commit 'test1'; +--disable_result_log +xa recover; +--enable_result_log +unlock tables; +--echo # Check that XA non-COMMIT statements are not and COMMIT is +--echo # blocked by active FTWRL in another connection +--echo # +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +xa start 'test1'; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +unlock tables; +--echo # Switching to connection 'default'. +connection default; +insert into t3_trans values (1); +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +xa end 'test1'; +xa prepare 'test1'; +xa rollback 'test1'; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +unlock tables; +--echo # Switching to connection 'default'. +connection default; +xa start 'test1'; +insert into t3_trans values (1); +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +connection default; +xa end 'test1'; +xa prepare 'test1'; +--echo # Send: +--send xa commit 'test1'; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +--echo # Wait until XA COMMIT is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for commit lock" and + info = "xa commit 'test1'"; +--source include/wait_condition.inc +unlock tables; +--echo # Switching to connection 'default'. +connection default; +--echo # Reap XA COMMIT. +--reap +delete from t3_trans; +--echo # +--echo # Check that XA COMMIT blocks FTWRL in another connection. +xa start 'test1'; +insert into t3_trans values (1); +xa end 'test1'; +xa prepare 'test1'; +set debug_sync='RESET'; +set debug_sync='trans_xa_commit_after_acquire_commit_lock SIGNAL parked WAIT_FOR go'; +--send xa commit 'test1' +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +set debug_sync='now WAIT_FOR parked'; +--send flush tables with read lock +--echo # Switching to connection '$con_aux2'. +connection $con_aux2; +--echo # Wait until FTWRL is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for commit lock" and + info = "flush tables with read lock"; +--source include/wait_condition.inc +set debug_sync='now SIGNAL go'; +--echo # Switching to connection 'default'. +connection default; +--echo # Reap XA COMMIT. +--reap +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +--echo # Reap FTWRL. +--reap +unlock tables; +--echo # Switching to connection 'default'. +connection default; +delete from t3_trans; +set debug_sync= "RESET"; + + +--echo # +--echo # 38) Test effect of auto-commit mode for DML on transactional +--echo # temporary tables. +--echo # +--echo # 38.1) When auto-commit is on each such a statement ends with commit +--echo # of changes to temporary tables. But since transactions doing +--echo # such changes are considered read only [sic!/QQ] this commit +--echo # is compatible with FTWRL. +--echo # +--echo # Let us demostrate this fact for some common DML statements. +let $statement= delete from t3_temp_trans; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + +let $statement= insert into t3_temp_trans values (1); +let $cleanup_stmt= delete from t3_temp_trans limit 1; +--source include/check_ftwrl_compatible.inc + +let $statement= update t3_temp_trans, t2_temp set t3_temp_trans.i= 1 where t3_temp_trans.i = t2_temp.j; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + +--echo # +--echo # 38.2) When auto-commit is off DML on transaction temporary tables +--echo # is compatible with FTWRL. +--echo # +set autocommit= 0; +let $statement= delete from t3_temp_trans; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc + +let $statement= insert into t3_temp_trans values (1); +let $cleanup_stmt= delete from t3_temp_trans limit 1; +--source include/check_ftwrl_compatible.inc + +let $statement= update t3_temp_trans, t2_temp set t3_temp_trans.i= 1 where t3_temp_trans.i = t2_temp.j; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc +set autocommit= 1; + + +--echo # +--echo # 39) Test effect of DDL on transactional tables. +--echo # +--echo # 39.1) Due to implicit commit at the end of statement some of DDL +--echo # statements which are compatible with FTWRL in non-transactional +--echo # case are not compatible in case of transactional tables. +--echo # +--echo # 39.1.a) ANALYZE TABLE for transactional table is incompatible with +--echo # FTWRL. +flush tables with read lock; +--echo # Implicit commits are allowed under FTWRL. +analyze table t3_trans; +unlock tables; +--echo # +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +--send analyze table t3_trans +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +--echo # Check that ANALYZE TABLE is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for commit lock" and + info = "analyze table t3_trans"; +--source include/wait_condition.inc +unlock tables; +--echo # Switching to connection 'default'. +connection default; +--echo # Reap ANALYZE TABLE +--reap + +--echo # +--echo # 39.1.b) CHECK TABLE for transactional table is compatible with FTWRL. +--echo # Although it does implicit commit at the end of statement it +--echo # is considered to be read-only operation. +let $statement= check table t3_trans; +let $cleanup_stmt= ; +--echo # Skip last part of compatibility testing as this statement +--echo # releases metadata locks in non-standard place. +let $skip_3rd_check= 1; +--source include/check_ftwrl_compatible.inc +let $skip_3rd_check= ; + +--echo # +--echo # 39.2) Situation with DDL on temporary transactional tables is +--echo # complex. +--echo # +--echo # 39.2.a) Some statements compatible with FTWRL since they don't +--echo # do implicit commit. +--echo # +--echo # For example, CREATE TEMPORARY TABLE: +let $statement= create temporary table t4_temp_trans(i int) engine=innodb; +let $cleanup_stmt= drop temporary tables t4_temp_trans; +--source include/check_ftwrl_compatible.inc +--echo # +--echo # Or DROP TEMPORARY TABLE: +let $statement= drop temporary tables t3_temp_trans; +let $cleanup_stmt= create temporary table t3_temp_trans(i int) engine=innodb; +--source include/check_ftwrl_compatible.inc +--echo # +--echo # 39.2.b) Some statements do implicit commit but are considered +--echo # read-only and so are compatible with FTWRL. +--echo # +--echo # For example, REPAIR TABLE: +let $statement= repair table t3_temp_trans; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc +--echo # +--echo # And ANALYZE TABLE: +let $statement= analyze table t3_temp_trans; +let $cleanup_stmt= ; +--source include/check_ftwrl_compatible.inc +--echo # +--echo # 39.2.c) Some statements do implicit commit and not +--echo # considered read-only. As result they are +--echo # not compatible with FTWRL. +--echo # +flush tables with read lock; +--echo # Implicit commits are allowed under FTWRL. +alter table t3_temp_trans add column c1 int; +unlock tables; +--echo # +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +--send alter table t3_temp_trans drop column c1 +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +--echo # Check that ALTER TABLE is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for commit lock" and + info = "alter table t3_temp_trans drop column c1"; +--source include/wait_condition.inc +unlock tables; +--echo # Switching to connection 'default'. +connection default; +--echo # Reap ALTER TABLE +--reap + + +--echo # +--echo # 40) Test effect of implicit commit for DDL which is otherwise +--echo # compatible with FTWRL. Implicit commit at the start of DDL +--echo # statement can make it incompatible with FTWRL if there are +--echo # some changes to be commited even in case when DDL statement +--echo # itself is compatible with FTWRL. +--echo # +--echo # For example CHECK TABLE for base non-transactional tables and +--echo # ALTER TABLE for temporary non-transactional tables are affected. +begin; +insert into t3_trans values (1); +--echo # +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +--send check table t1_base +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +--echo # Check that CHECK TABLE is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for commit lock" and + info = "check table t1_base"; +--source include/wait_condition.inc +unlock tables; +--echo # Switching to connection 'default'. +connection default; +--echo # Reap CHECK TABLE +--reap +begin; +delete from t3_trans; +--echo # +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +--send alter table t1_temp add column c1 int +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +--echo # Check that ALTER TABLE is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for commit lock" and + info = "alter table t1_temp add column c1 int"; +--source include/wait_condition.inc +unlock tables; +--echo # Switching to connection 'default'. +connection default; +--echo # Reap ALTER TABLE +--reap +alter table t1_temp drop column c1; + + +--echo # +--echo # Check that FLUSH TABLES WITH READ LOCK is blocked by individual +--echo # statements and is not blocked in the presence of transaction which +--echo # has done some changes earlier but is idle now (or does only reads). +--echo # This allows to use this statement even on systems which has long +--echo # running transactions. +--echo # +begin; +insert into t1_base values (1); +insert into t3_trans values (1); +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +--echo # The below FTWRL should not be blocked by transaction in 'default'. +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +--echo # Transaction still is able to read even with FTWRL active in another +--echo # connection. +select * from t1_base; +select * from t2_base; +select * from t3_trans; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +unlock tables; +--echo # Switching to connection 'default'. +connection default; +commit; +delete from t1_base; +delete from t3_trans; + + +--echo # +--echo # Check that impending FTWRL blocks new DML statements and +--echo # so can't be starved by a constant flow of DML. +--echo # (a.k.a. test for bug #54673 "It takes too long to get +--echo # readlock for 'FLUSH TABLES WITH READ LOCK'"). +--echo # +set debug_sync='RESET'; +set debug_sync='execute_command_after_close_tables SIGNAL parked WAIT_FOR go'; +--send insert into t1_base values (1) +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +set debug_sync='now WAIT_FOR parked'; +--send flush tables with read lock +--echo # Switching to connection '$con_aux2'. +connection $con_aux2; +--echo # Wait until FTWRL is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for global read lock" and + info = "flush tables with read lock"; +--source include/wait_condition.inc +--echo # Try to run another INSERT and see that it is blocked. +--send insert into t2_base values (1); +--echo # Switching to connection 'con3'. +connection con3; +--echo # Wait until new INSERT is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for global read lock" and + info = "insert into t2_base values (1)"; +--echo # Unblock INSERT in the first connection. +set debug_sync='now SIGNAL go'; +--echo # Switching to connection 'default'. +connection default; +--echo # Reap first INSERT. +--reap +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +--echo # Reap FTWRL. +--reap +unlock tables; +--echo # Switching to connection '$con_aux2'. +connection $con_aux2; +--echo # Reap second INSERT. +--reap +--echo # Switching to connection 'default'. +connection default; +set debug_sync= "RESET"; +delete from t1_base; +delete from t2_base; + +--echo +--echo # Check that COMMIT thas is issued after +--echo # FLUSH TABLES WITH READ LOCK is not blocked by +--echo # FLUSH TABLES WITH READ LOCK from another connection. +--echo # This scenario is used in innobackup.pl. The COMMIT goes +--echo # through because the transaction started by FTWRL does +--echo # not modify any tables, and the commit blocker lock is +--echo # only taken when there were such modifications. +--echo +flush tables with read lock; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +--echo # The below FTWRL should not be blocked by transaction in 'default'. +flush tables with read lock; +--echo # Switching to connection 'default'. +connection default; +select * from t1_base; +select * from t3_trans; +commit; +--echo # Switching to connection '$con_aux1'. +connection $con_aux1; +select * from t1_base; +select * from t3_trans; +commit; +unlock tables; +--echo # Switching to connection 'default'. +connection default; +unlock tables; + + +--echo # +--echo # Check how FLUSH TABLE WITH READ LOCK is handled for MERGE tables. +--echo # As usual there are tricky cases related to this type of tables. +--echo # +--echo # +--echo # 1) Most typical case - base MERGE table with base underlying tables. +--echo # +--echo # 1.a) DML statements which change data should be incompatible with FTWRL. +create table tm_base (i int) engine=merge union=(t1_base) insert_method=last; +let $statement= insert into tm_base values (1); +let $cleanup_stmt1= delete from tm_base; +--source include/check_ftwrl_incompatible.inc +--echo # +--echo # 1.b) DDL statement on such table should be incompatible with FTWRL as well. +let $statement= alter table tm_base insert_method=first; +let $cleanup_stmt1= alter table tm_base insert_method=last; +--source include/check_ftwrl_incompatible.inc +drop table tm_base; + +--echo # +--echo # 2) Temporary MERGE table with base underlying tables. +--echo # +--echo # 2.a) DML statements which change data should be incompatible with FTWRL +--echo # as they affect base tables. +create temporary table tm_temp_base (i int) engine=merge union=(t1_base) insert_method=last; +let $statement= insert into tm_temp_base values (1); +let $cleanup_stmt1= delete from tm_temp_base; +--source include/check_ftwrl_incompatible.inc +--echo # +--echo # 2.b) Some of DDL statements on such table can be compatible with FTWRL +--echo # as they don't affect base tables. +let $statement= drop temporary tables tm_temp_base; +let $cleanup_stmt= create temporary table tm_temp_base (i int) engine=merge union=(t1_base) insert_method=last; +--source include/check_ftwrl_compatible.inc +--echo # +--echo # 2.c) ALTER statement is incompatible with FTWRL. Even though it does +--echo # not change data in base table it still acquires strong metadata +--echo # locks on them. +let $statement= alter table tm_temp_base insert_method=first; +let $cleanup_stmt1= alter table tm_temp_base insert_method=last; +--source include/check_ftwrl_incompatible.inc +drop table tm_temp_base; + +--echo # +--echo # 3) Temporary MERGE table with temporary underlying tables. +--echo # +--echo # 3.a) DML statements should be compatible with FTWRL as +--echo # no base table is going to be affected. +create temporary table tm_temp_temp (i int) engine=merge union=(t1_temp) insert_method=last; +let $statement= insert into tm_temp_temp values (1); +let $cleanup_stmt= delete from tm_temp_temp; +--source include/check_ftwrl_compatible.inc +--echo # +--echo # 3.b) DDL statements should be compatible with FTWRL as well +--echo # as no base table is going to be affected too. +let $statement= alter table tm_temp_temp union=(t1_temp) insert_method=first; +let $cleanup_stmt= alter table tm_temp_temp union=(t1_temp) insert_method=last; +--source include/check_ftwrl_compatible.inc +drop table tm_temp_temp; + +--echo # +--echo # 4) For the sake of completeness let us check that base MERGE tables +--echo # with temporary underlying tables are not functional. +create table tm_base_temp (i int) engine=merge union=(t1_temp) insert_method=last; +--error ER_WRONG_MRG_TABLE +select * from tm_base_temp; +drop table tm_base_temp; + + +--echo # +--echo # Clean-up. +--echo # +drop event e1; +drop function f2_temp; +drop function f2_base; +drop procedure p2; +drop view v1; +drop function f1; +drop procedure p1; +drop database `#mysql50#mysqltest-2`; +drop database mysqltest1; +drop temporary tables t1_temp, t2_temp; +drop tables t1_base, t2_base, t3_trans; +disconnect con1; +disconnect con2; +disconnect con3; + +# Check that all connections opened by test cases in this file are really +# gone so execution of other tests won't be affected by their presence. +--source include/wait_until_count_sessions.inc diff --git a/mysql-test/t/flush_read_lock_kill-master.opt b/mysql-test/t/flush_read_lock_kill-master.opt deleted file mode 100644 index 61e2b242351..00000000000 --- a/mysql-test/t/flush_read_lock_kill-master.opt +++ /dev/null @@ -1 +0,0 @@ ---loose-debug=+d,make_global_read_lock_block_commit_loop diff --git a/mysql-test/t/flush_read_lock_kill.test b/mysql-test/t/flush_read_lock_kill.test index 2d359383949..a672fa5dfc5 100644 --- a/mysql-test/t/flush_read_lock_kill.test +++ b/mysql-test/t/flush_read_lock_kill.test @@ -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()); + +--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'; -# 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 # 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 diff --git a/mysql-test/t/lock_multi.test b/mysql-test/t/lock_multi.test index 6bdb235903d..5bab5e647ab 100644 --- a/mysql-test/t/lock_multi.test +++ b/mysql-test/t/lock_multi.test @@ -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; diff --git a/mysql-test/t/mdl_sync.test b/mysql-test/t/mdl_sync.test index 5c4fc20b428..197cad536e4 100644 --- a/mysql-test/t/mdl_sync.test +++ b/mysql-test/t/mdl_sync.test @@ -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; diff --git a/mysql-test/t/trigger_notembedded.test b/mysql-test/t/trigger_notembedded.test index 89857063ebd..536b00308ab 100644 --- a/mysql-test/t/trigger_notembedded.test +++ b/mysql-test/t/trigger_notembedded.test @@ -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; diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc index 52c509621ac..bdc79dcbcfd 100644 --- a/sql/event_data_objects.cc +++ b/sql/event_data_objects.cc @@ -290,7 +290,6 @@ Event_basic::load_time_zone(THD *thd, const LEX_STRING tz_name) */ Event_queue_element::Event_queue_element(): - status_changed(FALSE), last_executed_changed(FALSE), on_completion(Event_parse_data::ON_COMPLETION_DROP), status(Event_parse_data::ENABLED), expression(0), dropped(FALSE), execution_count(0) @@ -539,7 +538,6 @@ Event_queue_element::load_from_row(THD *thd, TABLE *table) TIME_NO_ZERO_DATE); last_executed= my_tz_OFFSET0->TIME_to_gmt_sec(&time,¬_used); } - last_executed_changed= FALSE; if ((ptr= get_field(&mem_root, table->field[ET_FIELD_STATUS])) == NullS) DBUG_RETURN(TRUE); @@ -935,7 +933,6 @@ Event_queue_element::compute_next_execution_time() DBUG_PRINT("info",("One-time event will be dropped: %d.", dropped)); status= Event_parse_data::DISABLED; - status_changed= TRUE; } goto ret; } @@ -955,7 +952,6 @@ Event_queue_element::compute_next_execution_time() dropped= TRUE; DBUG_PRINT("info", ("Dropped: %d", dropped)); status= Event_parse_data::DISABLED; - status_changed= TRUE; goto ret; } @@ -1018,7 +1014,6 @@ Event_queue_element::compute_next_execution_time() if (on_completion == Event_parse_data::ON_COMPLETION_DROP) dropped= TRUE; status= Event_parse_data::DISABLED; - status_changed= TRUE; } else { @@ -1108,7 +1103,6 @@ Event_queue_element::compute_next_execution_time() execute_at= 0; execute_at_null= TRUE; status= Event_parse_data::DISABLED; - status_changed= TRUE; if (on_completion == Event_parse_data::ON_COMPLETION_DROP) dropped= TRUE; } @@ -1144,50 +1138,11 @@ void Event_queue_element::mark_last_executed(THD *thd) { last_executed= (my_time_t) thd->query_start(); - last_executed_changed= TRUE; execution_count++; } -/* - Saves status and last_executed_at to the disk if changed. - - SYNOPSIS - Event_queue_element::update_timing_fields() - thd - thread context - - RETURN VALUE - FALSE OK - TRUE Error while opening mysql.event for writing or during - write on disk -*/ - -bool -Event_queue_element::update_timing_fields(THD *thd) -{ - Event_db_repository *db_repository= Events::get_db_repository(); - int ret; - - DBUG_ENTER("Event_queue_element::update_timing_fields"); - - DBUG_PRINT("enter", ("name: %*s", (int) name.length, name.str)); - - /* No need to update if nothing has changed */ - if (!(status_changed || last_executed_changed)) - DBUG_RETURN(0); - - ret= db_repository->update_timing_fields_for_event(thd, - dbname, name, - last_executed_changed, - last_executed, - status_changed, - (ulonglong) status); - last_executed_changed= status_changed= FALSE; - DBUG_RETURN(ret); -} - - static void append_datetime(String *buf, Time_zone *time_zone, my_time_t secs, diff --git a/sql/event_data_objects.h b/sql/event_data_objects.h index 9d17213bcb8..46740812d31 100644 --- a/sql/event_data_objects.h +++ b/sql/event_data_objects.h @@ -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); }; diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc index db508e4ea41..053558aa0c3 100644 --- a/sql/event_db_repository.cc +++ b/sql/event_db_repository.cc @@ -622,6 +622,12 @@ Event_db_repository::create_event(THD *thd, Event_parse_data *parse_data, TABLE *table= NULL; sp_head *sp= thd->lex->sphead; ulong saved_mode= thd->variables.sql_mode; + /* + Take a savepoint to release only the lock on mysql.event + table at the end but keep the global read lock and + possible other locks taken by the caller. + */ + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); DBUG_ENTER("Event_db_repository::create_event"); @@ -699,8 +705,8 @@ Event_db_repository::create_event(THD *thd, Event_parse_data *parse_data, ret= 0; end: - if (table) - close_mysql_tables(thd); + close_thread_tables(thd); + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); thd->variables.sql_mode= saved_mode; DBUG_RETURN(test(ret)); @@ -734,6 +740,12 @@ Event_db_repository::update_event(THD *thd, Event_parse_data *parse_data, TABLE *table= NULL; sp_head *sp= thd->lex->sphead; ulong saved_mode= thd->variables.sql_mode; + /* + Take a savepoint to release only the lock on mysql.event + table at the end but keep the global read lock and + possible other locks taken by the caller. + */ + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); int ret= 1; DBUG_ENTER("Event_db_repository::update_event"); @@ -811,8 +823,8 @@ Event_db_repository::update_event(THD *thd, Event_parse_data *parse_data, ret= 0; end: - if (table) - close_mysql_tables(thd); + close_thread_tables(thd); + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); thd->variables.sql_mode= saved_mode; DBUG_RETURN(test(ret)); @@ -838,6 +850,12 @@ Event_db_repository::drop_event(THD *thd, LEX_STRING db, LEX_STRING name, bool drop_if_exists) { TABLE *table= NULL; + /* + Take a savepoint to release only the lock on mysql.event + table at the end but keep the global read lock and + possible other locks taken by the caller. + */ + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); int ret= 1; DBUG_ENTER("Event_db_repository::drop_event"); @@ -866,8 +884,8 @@ Event_db_repository::drop_event(THD *thd, LEX_STRING db, LEX_STRING name, ret= 0; end: - if (table) - close_mysql_tables(thd); + close_thread_tables(thd); + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); DBUG_RETURN(test(ret)); } @@ -940,7 +958,7 @@ Event_db_repository::drop_schema_events(THD *thd, LEX_STRING schema) TABLE *table= NULL; READ_RECORD read_record_info; enum enum_events_table_field field= ET_FIELD_DB; - MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint(); + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); DBUG_ENTER("Event_db_repository::drop_schema_events"); DBUG_PRINT("enter", ("field=%d schema=%s", field, schema.str)); @@ -1042,15 +1060,14 @@ Event_db_repository:: update_timing_fields_for_event(THD *thd, LEX_STRING event_db_name, LEX_STRING event_name, - bool update_last_executed, my_time_t last_executed, - bool update_status, ulonglong status) { TABLE *table= NULL; Field **fields; int ret= 1; bool save_binlog_row_based; + MYSQL_TIME time; DBUG_ENTER("Event_db_repository::update_timing_fields_for_event"); @@ -1075,20 +1092,12 @@ update_timing_fields_for_event(THD *thd, /* Don't update create on row update. */ table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; - if (update_last_executed) - { - MYSQL_TIME time; - my_tz_OFFSET0->gmt_sec_to_TIME(&time, last_executed); + my_tz_OFFSET0->gmt_sec_to_TIME(&time, last_executed); + fields[ET_FIELD_LAST_EXECUTED]->set_notnull(); + fields[ET_FIELD_LAST_EXECUTED]->store_time(&time, MYSQL_TIMESTAMP_DATETIME); - fields[ET_FIELD_LAST_EXECUTED]->set_notnull(); - fields[ET_FIELD_LAST_EXECUTED]->store_time(&time, - MYSQL_TIMESTAMP_DATETIME); - } - if (update_status) - { - fields[ET_FIELD_STATUS]->set_notnull(); - fields[ET_FIELD_STATUS]->store(status, TRUE); - } + fields[ET_FIELD_STATUS]->set_notnull(); + fields[ET_FIELD_STATUS]->store(status, TRUE); if ((ret= table->file->ha_update_row(table->record[1], table->record[0]))) { diff --git a/sql/event_db_repository.h b/sql/event_db_repository.h index ea7f3bbac0e..58484d17f06 100644 --- a/sql/event_db_repository.h +++ b/sql/event_db_repository.h @@ -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 diff --git a/sql/event_queue.cc b/sql/event_queue.cc index 458a7c1defd..92b8a2c9071 100644 --- a/sql/event_queue.cc +++ b/sql/event_queue.cc @@ -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); } diff --git a/sql/events.cc b/sql/events.cc index e7e47801586..23a0dc9eb52 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -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); } diff --git a/sql/events.h b/sql/events.h index f3ebc6da4ad..a337b29049a 100644 --- a/sql/events.h +++ b/sql/events.h @@ -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(); diff --git a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc index 6b07cf76d79..20745db9ce9 100644 --- a/sql/ha_ndbcluster.cc +++ b/sql/ha_ndbcluster.cc @@ -7382,38 +7382,35 @@ int ndbcluster_find_files(handlerton *hton, THD *thd, } /* + Delete old files. + ndbcluster_find_files() may be called from I_S code and ndbcluster_binlog thread in situations when some tables are already open. This means that code below will try to obtain exclusive metadata lock on some table - while holding shared meta-data lock on other tables. This might lead to - a deadlock, and therefore is disallowed by assertions of the metadata - locking subsystem. This is violation of metadata - locking protocol which has to be closed ASAP. + while holding shared meta-data lock on other tables. This might lead to a + deadlock but such a deadlock should be detected by MDL deadlock detector. + XXX: the scenario described above is not covered with any test. */ - if (!global_read_lock) - { - // Delete old files - List_iterator_fast<char> it3(delete_list); - while ((file_name_str= it3++)) - { - DBUG_PRINT("info", ("Remove table %s/%s", db, file_name_str)); - // Delete the table and all related files - TABLE_LIST table_list; - table_list.init_one_table(db, strlen(db), file_name_str, - strlen(file_name_str), file_name_str, - TL_WRITE); - table_list.mdl_request.set_type(MDL_EXCLUSIVE); - (void)mysql_rm_table_part2(thd, &table_list, - FALSE, /* if_exists */ - FALSE, /* drop_temporary */ - FALSE, /* drop_view */ - TRUE /* dont_log_query*/); - trans_commit_implicit(thd); /* Safety, should be unnecessary. */ - thd->mdl_context.release_transactional_locks(); - /* Clear error message that is returned when table is deleted */ - thd->clear_error(); - } + List_iterator_fast<char> it3(delete_list); + while ((file_name_str= it3++)) + { + DBUG_PRINT("info", ("Remove table %s/%s", db, file_name_str)); + /* Delete the table and all related files. */ + TABLE_LIST table_list; + table_list.init_one_table(db, strlen(db), file_name_str, + strlen(file_name_str), file_name_str, + TL_WRITE); + table_list.mdl_request.set_type(MDL_EXCLUSIVE); + (void)mysql_rm_table_part2(thd, &table_list, + FALSE, /* if_exists */ + FALSE, /* drop_temporary */ + FALSE, /* drop_view */ + TRUE /* dont_log_query*/); + trans_commit_implicit(thd); /* Safety, should be unnecessary. */ + thd->mdl_context.release_transactional_locks(); + /* Clear error message that is returned when table is deleted */ + thd->clear_error(); } /* Lock mutex before creating .FRM files. */ diff --git a/sql/handler.cc b/sql/handler.cc index eb060002f48..22bb3069a9f 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -29,8 +29,6 @@ #include "sql_cache.h" // query_cache, query_cache_* #include "key.h" // key_copy, key_unpack, key_cmp_if_same, key_cmp #include "sql_table.h" // build_table_filename -#include "lock.h" // wait_if_global_read_lock, - // start_waiting_global_read_lock #include "sql_parse.h" // check_stack_overrun #include "sql_acl.h" // SUPER_ACL #include "sql_base.h" // free_io_cache @@ -41,6 +39,7 @@ #include "transaction.h" #include <errno.h> #include "probes_mysql.h" +#include "debug_sync.h" // DEBUG_SYNC #ifdef WITH_PARTITION_STORAGE_ENGINE #include "ha_partition.h" @@ -1155,6 +1154,7 @@ int ha_commit_trans(THD *thd, bool all) { uint rw_ha_count; bool rw_trans; + MDL_request mdl_request; DBUG_EXECUTE_IF("crash_commit_before", DBUG_SUICIDE();); @@ -1166,11 +1166,27 @@ int ha_commit_trans(THD *thd, bool all) /* rw_trans is TRUE when we in a transaction changing data */ rw_trans= is_real_trans && (rw_ha_count > 0); - if (rw_trans && - thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, FALSE)) + if (rw_trans) { - ha_rollback_trans(thd, all); - DBUG_RETURN(1); + /* + Acquire a metadata lock which will ensure that COMMIT is blocked + by an active FLUSH TABLES WITH READ LOCK (and vice versa: + COMMIT in progress blocks FTWRL). + + We allow the owner of FTWRL to COMMIT; we assume that it knows + what it does. + */ + mdl_request.init(MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE, + MDL_EXPLICIT); + + if (thd->mdl_context.acquire_lock(&mdl_request, + thd->variables.lock_wait_timeout)) + { + ha_rollback_trans(thd, all); + DBUG_RETURN(1); + } + + DEBUG_SYNC(thd, "ha_commit_trans_after_acquire_commit_lock"); } if (rw_trans && @@ -1225,8 +1241,16 @@ int ha_commit_trans(THD *thd, bool all) DBUG_EXECUTE_IF("crash_commit_after", DBUG_SUICIDE();); RUN_HOOK(transaction, after_commit, (thd, FALSE)); end: - if (rw_trans) - thd->global_read_lock.start_waiting_global_read_lock(thd); + if (rw_trans && mdl_request.ticket) + { + /* + We do not always immediately release transactional locks + after ha_commit_trans() (see uses of ha_enable_transaction()), + thus we release the commit blocker lock as soon as it's + not needed. + */ + thd->mdl_context.release_lock(mdl_request.ticket); + } } /* Free resources and perform other cleanup even for 'empty' transactions. */ else if (is_real_trans) diff --git a/sql/lock.cc b/sql/lock.cc index 84cc24b3f61..2e141e1c9fb 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -777,8 +777,11 @@ bool lock_schema_name(THD *thd, const char *db) return TRUE; } - global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE); - mdl_request.init(MDL_key::SCHEMA, db, "", MDL_EXCLUSIVE); + if (thd->global_read_lock.can_acquire_protection()) + return TRUE; + global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE, + MDL_STATEMENT); + mdl_request.init(MDL_key::SCHEMA, db, "", MDL_EXCLUSIVE, MDL_TRANSACTION); mdl_requests.push_front(&mdl_request); mdl_requests.push_front(&global_request); @@ -793,13 +796,13 @@ bool lock_schema_name(THD *thd, const char *db) /** - Obtain an exclusive metadata lock on the stored routine name. + Obtain an exclusive metadata lock on an object name. @param thd Thread handle. - @param is_function Stored routine type (only functions or procedures - are name-locked. - @param db The schema the routine belongs to. - @param name Routine name. + @param mdl_type Object type (currently functions, procedures + and events can be name-locked). + @param db The schema the object belongs to. + @param name Object name in the schema. This function assumes that no metadata locks were acquired before calling it. Additionally, it cannot be called while @@ -815,12 +818,9 @@ bool lock_schema_name(THD *thd, const char *db) or this connection was killed. */ -bool lock_routine_name(THD *thd, bool is_function, +bool lock_object_name(THD *thd, MDL_key::enum_mdl_namespace mdl_type, const char *db, const char *name) { - MDL_key::enum_mdl_namespace mdl_type= (is_function ? - MDL_key::FUNCTION : - MDL_key::PROCEDURE); MDL_request_list mdl_requests; MDL_request global_request; MDL_request schema_request; @@ -836,9 +836,13 @@ bool lock_routine_name(THD *thd, bool is_function, DBUG_ASSERT(name); DEBUG_SYNC(thd, "before_wait_locked_pname"); - global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE); - schema_request.init(MDL_key::SCHEMA, db, "", MDL_INTENTION_EXCLUSIVE); - mdl_request.init(mdl_type, db, name, MDL_EXCLUSIVE); + if (thd->global_read_lock.can_acquire_protection()) + return TRUE; + global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE, + MDL_STATEMENT); + schema_request.init(MDL_key::SCHEMA, db, "", MDL_INTENTION_EXCLUSIVE, + MDL_TRANSACTION); + mdl_request.init(mdl_type, db, name, MDL_EXCLUSIVE, MDL_TRANSACTION); mdl_requests.push_front(&mdl_request); mdl_requests.push_front(&schema_request); @@ -888,45 +892,24 @@ static void print_lock_error(int error, const char *table) /**************************************************************************** Handling of global read locks + Global read lock is implemented using metadata lock infrastructure. + Taking the global read lock is TWO steps (2nd step is optional; without it, COMMIT of existing transactions will be allowed): lock_global_read_lock() THEN make_global_read_lock_block_commit(). - The global locks are handled through the global variables: - global_read_lock - count of threads which have the global read lock (i.e. have completed at - least the first step above) - global_read_lock_blocks_commit - count of threads which have the global read lock and block - commits (i.e. are in or have completed the second step above) - waiting_for_read_lock - count of threads which want to take a global read lock but cannot - protect_against_global_read_lock - count of threads which have set protection against global read lock. - - access to them is protected with a mutex LOCK_global_read_lock - - (XXX: one should never take LOCK_open if LOCK_global_read_lock is - taken, otherwise a deadlock may occur. Other mutexes could be a - problem too - grep the code for global_read_lock if you want to use - any other mutex here) Also one must not hold LOCK_open when calling - wait_if_global_read_lock(). When the thread with the global read lock - tries to close its tables, it needs to take LOCK_open in - close_thread_table(). - How blocking of threads by global read lock is achieved: that's - advisory. Any piece of code which should be blocked by global read lock must - be designed like this: - - call to wait_if_global_read_lock(). When this returns 0, no global read - lock is owned; if argument abort_on_refresh was 0, none can be obtained. - - job - - if abort_on_refresh was 0, call to start_waiting_global_read_lock() to - allow other threads to get the global read lock. I.e. removal of the - protection. - (Note: it's a bit like an implementation of rwlock). - - [ I am sorry to mention some SQL syntaxes below I know I shouldn't but found - no better descriptive way ] + semi-automatic. We assume that any statement which should be blocked + by global read lock will either open and acquires write-lock on tables + or acquires metadata locks on objects it is going to modify. For any + such statement global IX metadata lock is automatically acquired for + its duration (in case of LOCK TABLES until end of LOCK TABLES mode). + And lock_global_read_lock() simply acquires global S metadata lock + and thus prohibits execution of statements which modify data (unless + they modify only temporary tables). If deadlock happens it is detected + by MDL subsystem and resolved in the standard fashion (by backing-off + metadata locks acquired so far and restarting open tables process + if possible). Why does FLUSH TABLES WITH READ LOCK need to block COMMIT: because it's used to read a non-moving SHOW MASTER STATUS, and a COMMIT writes to the binary @@ -960,11 +943,6 @@ static void print_lock_error(int error, const char *table) ****************************************************************************/ -volatile uint global_read_lock=0; -volatile uint global_read_lock_blocks_commit=0; -static volatile uint protect_against_global_read_lock=0; -static volatile uint waiting_for_read_lock=0; - /** Take global read lock, wait if there is protection against lock. @@ -985,84 +963,17 @@ bool Global_read_lock::lock_global_read_lock(THD *thd) if (!m_state) { MDL_request mdl_request; - const char *old_message; - const char *new_message= "Waiting to get readlock"; - (void) mysql_mutex_lock(&LOCK_global_read_lock); - - old_message= thd->enter_cond(&COND_global_read_lock, - &LOCK_global_read_lock, new_message); - DBUG_PRINT("info", - ("waiting_for: %d protect_against: %d", - waiting_for_read_lock, protect_against_global_read_lock)); - - waiting_for_read_lock++; - -#if defined(ENABLED_DEBUG_SYNC) - /* - The below sync point fires if we have to wait for - protect_against_global_read_lock. - - WARNING: Beware to use WAIT_FOR with this sync point. We hold - LOCK_global_read_lock here. - - The sync point is after enter_cond() so that proc_info is - available immediately after the sync point sends a SIGNAL. This - can make tests more reliable. - - The sync point is before the loop so that it is executed only once. - */ - if (protect_against_global_read_lock && !thd->killed) - { - DEBUG_SYNC(thd, "wait_lock_global_read_lock"); - } -#endif /* defined(ENABLED_DEBUG_SYNC) */ - - while (protect_against_global_read_lock && !thd->killed) - mysql_cond_wait(&COND_global_read_lock, &LOCK_global_read_lock); - waiting_for_read_lock--; - if (thd->killed) - { - thd->exit_cond(old_message); - DBUG_RETURN(1); - } - m_state= GRL_ACQUIRED; - global_read_lock++; - thd->exit_cond(old_message); // this unlocks LOCK_global_read_lock - /* - When we perform FLUSH TABLES or ALTER TABLE under LOCK TABLES, - tables being reopened are protected only by meta-data locks at - some point. To avoid sneaking in with our global read lock at - this moment we have to take global shared meta data lock. - - TODO: We should change this code to acquire global shared metadata - lock before acquiring global read lock. But in order to do - this we have to get rid of all those places in which - wait_if_global_read_lock() is called before acquiring - metadata locks first. Also long-term we should get rid of - redundancy between metadata locks, global read lock and DDL - blocker (see WL#4399 and WL#4400). - */ DBUG_ASSERT(! thd->mdl_context.is_lock_owner(MDL_key::GLOBAL, "", "", MDL_SHARED)); - mdl_request.init(MDL_key::GLOBAL, "", "", MDL_SHARED); + mdl_request.init(MDL_key::GLOBAL, "", "", MDL_SHARED, MDL_EXPLICIT); if (thd->mdl_context.acquire_lock(&mdl_request, thd->variables.lock_wait_timeout)) - { - /* Our thread was killed -- return back to initial state. */ - mysql_mutex_lock(&LOCK_global_read_lock); - if (!(--global_read_lock)) - { - DBUG_PRINT("signal", ("Broadcasting COND_global_read_lock")); - mysql_cond_broadcast(&COND_global_read_lock); - } - mysql_mutex_unlock(&LOCK_global_read_lock); - m_state= GRL_NONE; DBUG_RETURN(1); - } - thd->mdl_context.move_ticket_after_trans_sentinel(mdl_request.ticket); + m_mdl_global_shared_lock= mdl_request.ticket; + m_state= GRL_ACQUIRED; } /* We DON'T set global_read_lock_blocks_commit now, it will be set after @@ -1088,166 +999,22 @@ bool Global_read_lock::lock_global_read_lock(THD *thd) void Global_read_lock::unlock_global_read_lock(THD *thd) { - uint tmp; DBUG_ENTER("unlock_global_read_lock"); - DBUG_PRINT("info", - ("global_read_lock: %u global_read_lock_blocks_commit: %u", - global_read_lock, global_read_lock_blocks_commit)); DBUG_ASSERT(m_mdl_global_shared_lock && m_state); - 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) + if (m_mdl_blocks_commits_lock) { - DBUG_PRINT("signal", ("Broadcasting COND_global_read_lock")); - mysql_cond_broadcast(&COND_global_read_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; m_state= GRL_NONE; DBUG_VOID_RETURN; } -/** - Wait if the global read lock is set, and optionally seek protection against - global read lock. - - See also "Handling of global read locks" above. - - @param thd Reference to thread. - @param abort_on_refresh If True, abort waiting if a refresh occurs, - do NOT seek protection against GRL. - If False, wait until the GRL is released and seek - protection against GRL. - @param is_not_commit If False, called from a commit operation, - wait only if commit blocking is also enabled. - - @retval False Success, protection against global read lock is set - (if !abort_on_refresh) - @retval True Failure, wait was aborted or thread was killed. -*/ - -#define must_wait (global_read_lock && \ - (is_not_commit || \ - global_read_lock_blocks_commit)) - -bool Global_read_lock:: -wait_if_global_read_lock(THD *thd, bool abort_on_refresh, - bool is_not_commit) -{ - const char *UNINIT_VAR(old_message); - bool result= 0, need_exit_cond; - DBUG_ENTER("wait_if_global_read_lock"); - - /* - If we already have protection against global read lock, - just increment the counter. - */ - if (unlikely(m_protection_count > 0)) - { - if (!abort_on_refresh) - m_protection_count++; - DBUG_RETURN(FALSE); - } - /* - Assert that we do not own LOCK_open. If we would own it, other - threads could not close their tables. This would make a pretty - deadlock. - */ - mysql_mutex_assert_not_owner(&LOCK_open); - - mysql_mutex_lock(&LOCK_global_read_lock); - if ((need_exit_cond= must_wait)) - { - if (m_state) // This thread had the read locks - { - if (is_not_commit) - my_message(ER_CANT_UPDATE_WITH_READLOCK, - ER(ER_CANT_UPDATE_WITH_READLOCK), MYF(0)); - mysql_mutex_unlock(&LOCK_global_read_lock); - /* - We allow FLUSHer to COMMIT; we assume FLUSHer knows what it does. - This allowance is needed to not break existing versions of innobackup - which do a BEGIN; INSERT; FLUSH TABLES WITH READ LOCK; COMMIT. - */ - DBUG_RETURN(is_not_commit); - } - old_message=thd->enter_cond(&COND_global_read_lock, &LOCK_global_read_lock, - "Waiting for release of readlock"); - while (must_wait && ! thd->killed && - (!abort_on_refresh || !thd->open_tables || - thd->open_tables->s->version == refresh_version)) - { - DBUG_PRINT("signal", ("Waiting for COND_global_read_lock")); - mysql_cond_wait(&COND_global_read_lock, &LOCK_global_read_lock); - DBUG_PRINT("signal", ("Got COND_global_read_lock")); - } - if (thd->killed) - result=1; - } - if (!abort_on_refresh && !result) - { - m_protection_count++; - protect_against_global_read_lock++; - DBUG_PRINT("sql_lock", ("protect_against_global_read_lock incr: %u", - protect_against_global_read_lock)); - } - /* - The following is only true in case of a global read locks (which is rare) - and if old_message is set - */ - if (unlikely(need_exit_cond)) - thd->exit_cond(old_message); // this unlocks LOCK_global_read_lock - else - mysql_mutex_unlock(&LOCK_global_read_lock); - DBUG_RETURN(result); -} - - -/** - Release protection against global read lock and restart - global read lock waiters. - - Should only be called if we have protection against global read lock. - - See also "Handling of global read locks" above. - - @param thd Reference to thread. -*/ - -void Global_read_lock::start_waiting_global_read_lock(THD *thd) -{ - bool tmp; - DBUG_ENTER("start_waiting_global_read_lock"); - /* - Ignore request if we do not have protection against global read lock. - (Note that this is a violation of the interface contract, hence the assert). - */ - DBUG_ASSERT(m_protection_count > 0); - if (unlikely(m_protection_count == 0)) - DBUG_VOID_RETURN; - /* Decrement local read lock protection counter, return if we still have it */ - if (unlikely(--m_protection_count > 0)) - DBUG_VOID_RETURN; - if (unlikely(m_state)) - DBUG_VOID_RETURN; - mysql_mutex_lock(&LOCK_global_read_lock); - DBUG_ASSERT(protect_against_global_read_lock); - tmp= (!--protect_against_global_read_lock && - (waiting_for_read_lock || global_read_lock_blocks_commit)); - mysql_mutex_unlock(&LOCK_global_read_lock); - if (tmp) - mysql_cond_broadcast(&COND_global_read_lock); - DBUG_VOID_RETURN; -} - /** Make global read lock also block commits. @@ -1266,8 +1033,7 @@ void Global_read_lock::start_waiting_global_read_lock(THD *thd) bool Global_read_lock::make_global_read_lock_block_commit(THD *thd) { - bool error; - const char *old_message; + MDL_request mdl_request; DBUG_ENTER("make_global_read_lock_block_commit"); /* If we didn't succeed lock_global_read_lock(), or if we already suceeded @@ -1275,42 +1041,32 @@ bool Global_read_lock::make_global_read_lock_block_commit(THD *thd) */ if (m_state != GRL_ACQUIRED) DBUG_RETURN(0); - mysql_mutex_lock(&LOCK_global_read_lock); - /* increment this BEFORE waiting on cond (otherwise race cond) */ - global_read_lock_blocks_commit++; - /* For testing we set up some blocking, to see if we can be killed */ - DBUG_EXECUTE_IF("make_global_read_lock_block_commit_loop", - protect_against_global_read_lock++;); - old_message= thd->enter_cond(&COND_global_read_lock, &LOCK_global_read_lock, - "Waiting for all running commits to finish"); - while (protect_against_global_read_lock && !thd->killed) - mysql_cond_wait(&COND_global_read_lock, &LOCK_global_read_lock); - DBUG_EXECUTE_IF("make_global_read_lock_block_commit_loop", - protect_against_global_read_lock--;); - if ((error= test(thd->killed))) - global_read_lock_blocks_commit--; // undo what we did - else - m_state= GRL_ACQUIRED_AND_BLOCKS_COMMIT; - thd->exit_cond(old_message); // this unlocks LOCK_global_read_lock - DBUG_RETURN(error); + + mdl_request.init(MDL_key::COMMIT, "", "", MDL_SHARED, MDL_EXPLICIT); + + if (thd->mdl_context.acquire_lock(&mdl_request, + thd->variables.lock_wait_timeout)) + DBUG_RETURN(TRUE); + + m_mdl_blocks_commits_lock= mdl_request.ticket; + m_state= GRL_ACQUIRED_AND_BLOCKS_COMMIT; + + DBUG_RETURN(FALSE); } /** - Broadcast COND_global_read_lock. - - 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. + Set explicit duration for metadata locks which are used to implement GRL. + + @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); } /** diff --git a/sql/lock.h b/sql/lock.h index c097c8d269e..6f779595af8 100644 --- a/sql/lock.h +++ b/sql/lock.h @@ -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 */ diff --git a/sql/log_event.cc b/sql/log_event.cc index b4641af07c5..2feb69493a8 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -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;); diff --git a/sql/mdl.cc b/sql/mdl.cc index d53ddcee0c8..3aed54ce12d 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -68,8 +68,6 @@ static void init_mdl_psi_keys(void) } #endif /* HAVE_PSI_INTERFACE */ -void notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket); - /** Thread state names to be used in case when we have to wait on resource @@ -78,12 +76,14 @@ void notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket); const char *MDL_key::m_namespace_to_wait_state_name[NAMESPACE_END]= { - "Waiting for global metadata lock", + "Waiting for global read lock", "Waiting for schema metadata lock", "Waiting for table metadata lock", "Waiting for stored function metadata lock", "Waiting for stored procedure metadata lock", - NULL + "Waiting for trigger metadata lock", + "Waiting for event metadata lock", + "Waiting for commit lock" }; static bool mdl_initialized= 0; @@ -100,7 +100,6 @@ class MDL_map public: void init(); void destroy(); - MDL_lock *find(const MDL_key *key); MDL_lock *find_or_insert(const MDL_key *key); void remove(MDL_lock *lock); private: @@ -110,6 +109,10 @@ private: HASH m_locks; /* Protects access to m_locks hash. */ mysql_mutex_t m_mutex; + /** Pre-allocated MDL_lock object for GLOBAL namespace. */ + MDL_lock *m_global_lock; + /** Pre-allocated MDL_lock object for COMMIT namespace. */ + MDL_lock *m_commit_lock; }; @@ -354,18 +357,6 @@ public: inline static MDL_lock *create(const MDL_key *key); - void notify_shared_locks(MDL_context *ctx) - { - Ticket_iterator it(m_granted); - MDL_ticket *conflicting_ticket; - - while ((conflicting_ticket= it++)) - { - if (conflicting_ticket->get_ctx() != ctx) - notify_shared_lock(ctx->get_thd(), conflicting_ticket); - } - } - void reschedule_waiters(); void remove_ticket(Ticket_list MDL_lock::*queue, MDL_ticket *ticket); @@ -373,6 +364,9 @@ public: bool visit_subgraph(MDL_ticket *waiting_ticket, MDL_wait_for_graph_visitor *gvisitor); + virtual bool needs_notification(const MDL_ticket *ticket) const = 0; + virtual void notify_conflicting_locks(MDL_context *ctx) = 0; + /** List of granted tickets for this lock. */ Ticket_list m_granted; /** Tickets for contexts waiting to acquire a lock. */ @@ -442,6 +436,11 @@ public: { return m_waiting_incompatible; } + virtual bool needs_notification(const MDL_ticket *ticket) const + { + return (ticket->get_type() == MDL_SHARED); + } + virtual void notify_conflicting_locks(MDL_context *ctx); private: static const bitmap_t m_granted_incompatible[MDL_TYPE_END]; @@ -469,6 +468,11 @@ public: { return m_waiting_incompatible; } + virtual bool needs_notification(const MDL_ticket *ticket) const + { + return ticket->is_upgradable_or_exclusive(); + } + virtual void notify_conflicting_locks(MDL_context *ctx); private: static const bitmap_t m_granted_incompatible[MDL_TYPE_END]; @@ -536,9 +540,14 @@ void mdl_destroy() void MDL_map::init() { + MDL_key global_lock_key(MDL_key::GLOBAL, "", ""); + MDL_key commit_lock_key(MDL_key::COMMIT, "", ""); + mysql_mutex_init(key_MDL_map_mutex, &m_mutex, NULL); my_hash_init(&m_locks, &my_charset_bin, 16 /* FIXME */, 0, 0, mdl_locks_key, 0, 0); + m_global_lock= MDL_lock::create(&global_lock_key); + m_commit_lock= MDL_lock::create(&commit_lock_key); } @@ -552,6 +561,8 @@ void MDL_map::destroy() DBUG_ASSERT(!m_locks.records); mysql_mutex_destroy(&m_mutex); my_hash_free(&m_locks); + MDL_lock::destroy(m_global_lock); + MDL_lock::destroy(m_commit_lock); } @@ -569,43 +580,28 @@ MDL_lock* MDL_map::find_or_insert(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()))) + if (mdl_key->mdl_namespace() == MDL_key::GLOBAL || + mdl_key->mdl_namespace() == MDL_key::COMMIT) { - lock= MDL_lock::create(mdl_key); - if (!lock || my_hash_insert(&m_locks, (uchar*)lock)) - { - mysql_mutex_unlock(&m_mutex); - MDL_lock::destroy(lock); - return NULL; - } - } - - if (move_from_hash_to_lock_mutex(lock)) - goto retry; + /* + 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. - return lock; -} + 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; -/** - Find MDL_lock object corresponding to the key. + mysql_prlock_wrlock(&lock->m_rwlock); - @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. -*/ + return lock; + } -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()); @@ -616,8 +612,13 @@ retry: mdl_key->ptr(), mdl_key->length()))) { - mysql_mutex_unlock(&m_mutex); - return NULL; + lock= MDL_lock::create(mdl_key); + if (!lock || my_hash_insert(&m_locks, (uchar*)lock)) + { + mysql_mutex_unlock(&m_mutex); + MDL_lock::destroy(lock); + return NULL; + } } if (move_from_hash_to_lock_mutex(lock)) @@ -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,49 +804,17 @@ 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. @note Also chooses an MDL_lock descendant appropriate for object namespace. @@ -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); - - *is_transactional= TRUE; + int i; - while ((ticket= it++)) + for (i= 0; i < MDL_DURATION_END; i++) { - if (ticket == m_trans_sentinel) - *is_transactional= FALSE; + enum_mdl_duration duration= (enum_mdl_duration)((mdl_request->duration+i) % + MDL_DURATION_END); + Ticket_iterator it(m_tickets[duration]); - if (mdl_request->key.is_equal(&ticket->m_lock->key) && - ticket->has_stronger_or_equal_type(mdl_request->type)) - break; + while ((ticket= it++)) + { + if (mdl_request->key.is_equal(&ticket->m_lock->key) && + ticket->has_stronger_or_equal_type(mdl_request->type)) + { + *result_duration= duration; + return ticket; + } + } } - - return ticket; + return NULL; } @@ -1549,7 +1543,7 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request, MDL_lock *lock; MDL_key *key= &mdl_request->key; MDL_ticket *ticket; - bool is_transactional; + enum_mdl_duration found_duration; DBUG_ASSERT(mdl_request->type != MDL_EXCLUSIVE || is_lock_owner(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE)); @@ -1563,7 +1557,7 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request, Check whether the context already holds a shared lock on the object, and if so, grant the request. */ - if ((ticket= find_ticket(mdl_request, &is_transactional))) + if ((ticket= find_ticket(mdl_request, &found_duration))) { DBUG_ASSERT(ticket->m_lock); DBUG_ASSERT(ticket->has_stronger_or_equal_type(mdl_request->type)); @@ -1585,7 +1579,9 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request, a different alias. */ mdl_request->ticket= ticket; - if (!is_transactional && clone_ticket(mdl_request)) + if ((found_duration != mdl_request->duration || + mdl_request->duration == MDL_EXPLICIT) && + clone_ticket(mdl_request)) { /* Clone failed. */ mdl_request->ticket= NULL; @@ -1594,7 +1590,11 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request, return FALSE; } - if (!(ticket= MDL_ticket::create(this, mdl_request->type))) + if (!(ticket= MDL_ticket::create(this, mdl_request->type +#ifndef DBUG_OFF + , mdl_request->duration +#endif + ))) return TRUE; /* The below call implicitly locks MDL_lock::m_rwlock on success. */ @@ -1612,7 +1612,7 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request, mysql_prlock_unlock(&lock->m_rwlock); - m_tickets.push_front(ticket); + m_tickets[mdl_request->duration].push_front(ticket); mdl_request->ticket= ticket; } @@ -1647,7 +1647,11 @@ MDL_context::clone_ticket(MDL_request *mdl_request) we effectively downgrade the cloned lock to the level of the request. */ - if (!(ticket= MDL_ticket::create(this, mdl_request->type))) + if (!(ticket= MDL_ticket::create(this, mdl_request->type +#ifndef DBUG_OFF + , mdl_request->duration +#endif + ))) return TRUE; /* clone() is not supposed to be used to get a stronger lock. */ @@ -1660,36 +1664,74 @@ MDL_context::clone_ticket(MDL_request *mdl_request) ticket->m_lock->m_granted.add_ticket(ticket); mysql_prlock_unlock(&ticket->m_lock->m_rwlock); - m_tickets.push_front(ticket); + m_tickets[mdl_request->duration].push_front(ticket); return FALSE; } /** - Notify a thread holding a shared metadata lock which - conflicts with a pending exclusive lock. + Notify threads holding a shared metadata locks on object which + conflict with a pending X, SNW or SNRW lock. - @param thd Current thread context - @param conflicting_ticket Conflicting metadata lock + @param ctx MDL_context for current thread. */ -void notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket) +void MDL_object_lock::notify_conflicting_locks(MDL_context *ctx) { - /* Only try to abort locks on which we back off. */ - if (conflicting_ticket->get_type() < MDL_SHARED_NO_WRITE) + Ticket_iterator it(m_granted); + MDL_ticket *conflicting_ticket; + + while ((conflicting_ticket= it++)) { - MDL_context *conflicting_ctx= conflicting_ticket->get_ctx(); - THD *conflicting_thd= conflicting_ctx->get_thd(); - DBUG_ASSERT(thd != conflicting_thd); /* Self-deadlock */ + /* Only try to abort locks on which we back off. */ + if (conflicting_ticket->get_ctx() != ctx && + conflicting_ticket->get_type() < MDL_SHARED_NO_WRITE) - /* - If thread which holds conflicting lock is waiting on table-level - lock or some other non-MDL resource we might need to wake it up - by calling code outside of MDL. - */ - mysql_notify_thread_having_shared_lock(thd, conflicting_thd, - conflicting_ctx->get_needs_thr_lock_abort()); + { + MDL_context *conflicting_ctx= conflicting_ticket->get_ctx(); + + /* + If thread which holds conflicting lock is waiting on table-level + lock or some other non-MDL resource we might need to wake it up + by calling code outside of MDL. + */ + mysql_notify_thread_having_shared_lock(ctx->get_thd(), + conflicting_ctx->get_thd(), + conflicting_ctx->get_needs_thr_lock_abort()); + } + } +} + + +/** + Notify threads holding scoped IX locks which conflict with a pending S lock. + + @param ctx MDL_context for current thread. +*/ + +void MDL_scoped_lock::notify_conflicting_locks(MDL_context *ctx) +{ + Ticket_iterator it(m_granted); + MDL_ticket *conflicting_ticket; + + while ((conflicting_ticket= it++)) + { + if (conflicting_ticket->get_ctx() != ctx && + conflicting_ticket->get_type() == MDL_INTENTION_EXCLUSIVE) + + { + MDL_context *conflicting_ctx= conflicting_ticket->get_ctx(); + + /* + Thread which holds global IX lock can be a handler thread for + insert delayed. We need to kill such threads in order to get + global shared lock. We do this my calling code outside of MDL. + */ + mysql_notify_thread_having_shared_lock(ctx->get_thd(), + conflicting_ctx->get_thd(), + conflicting_ctx->get_needs_thr_lock_abort()); + } } } @@ -1747,8 +1789,8 @@ MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout) */ m_wait.reset_status(); - if (ticket->is_upgradable_or_exclusive()) - lock->notify_shared_locks(this); + if (lock->needs_notification(ticket)) + lock->notify_conflicting_locks(this); mysql_prlock_unlock(&lock->m_rwlock); @@ -1759,7 +1801,7 @@ MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout) find_deadlock(); - if (ticket->is_upgradable_or_exclusive()) + if (lock->needs_notification(ticket)) { struct timespec abs_shortwait; set_timespec(abs_shortwait, 1); @@ -1775,7 +1817,7 @@ MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout) break; mysql_prlock_wrlock(&lock->m_rwlock); - lock->notify_shared_locks(this); + lock->notify_conflicting_locks(this); mysql_prlock_unlock(&lock->m_rwlock); set_timespec(abs_shortwait, 1); } @@ -1818,7 +1860,7 @@ MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout) */ DBUG_ASSERT(wait_status == MDL_wait::GRANTED); - m_tickets.push_front(ticket); + m_tickets[mdl_request->duration].push_front(ticket); mdl_request->ticket= ticket; @@ -1859,7 +1901,7 @@ bool MDL_context::acquire_locks(MDL_request_list *mdl_requests, { MDL_request_list::Iterator it(*mdl_requests); MDL_request **sort_buf, **p_req; - MDL_ticket *mdl_svp= mdl_savepoint(); + MDL_savepoint mdl_svp= mdl_savepoint(); ssize_t req_count= static_cast<ssize_t>(mdl_requests->elements()); if (req_count == 0) @@ -1928,7 +1970,7 @@ MDL_context::upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket, ulong lock_wait_timeout) { MDL_request mdl_xlock_request; - MDL_ticket *mdl_svp= mdl_savepoint(); + MDL_savepoint mdl_svp= mdl_savepoint(); bool is_new_ticket; DBUG_ENTER("MDL_ticket::upgrade_shared_lock_to_exclusive"); @@ -1945,7 +1987,8 @@ MDL_context::upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket, DBUG_ASSERT(mdl_ticket->m_type == MDL_SHARED_NO_WRITE || mdl_ticket->m_type == MDL_SHARED_NO_READ_WRITE); - mdl_xlock_request.init(&mdl_ticket->m_lock->key, MDL_EXCLUSIVE); + mdl_xlock_request.init(&mdl_ticket->m_lock->key, MDL_EXCLUSIVE, + MDL_TRANSACTION); if (acquire_lock(&mdl_xlock_request, lock_wait_timeout)) DBUG_RETURN(TRUE); @@ -1969,7 +2012,7 @@ MDL_context::upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket, if (is_new_ticket) { - m_tickets.remove(mdl_xlock_request.ticket); + m_tickets[MDL_TRANSACTION].remove(mdl_xlock_request.ticket); MDL_ticket::destroy(mdl_xlock_request.ticket); } @@ -2230,10 +2273,12 @@ void MDL_context::find_deadlock() /** Release lock. - @param ticket Ticket for lock to be released. + @param duration Lock duration. + @param ticket Ticket for lock to be released. + */ -void MDL_context::release_lock(MDL_ticket *ticket) +void MDL_context::release_lock(enum_mdl_duration duration, MDL_ticket *ticket) { MDL_lock *lock= ticket->m_lock; DBUG_ENTER("MDL_context::release_lock"); @@ -2243,12 +2288,9 @@ 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; @@ -2256,50 +2298,56 @@ void MDL_context::release_lock(MDL_ticket *ticket) /** + Release lock with explicit duration. + + @param ticket Ticket for lock to be released. + +*/ + +void MDL_context::release_lock(MDL_ticket *ticket) +{ + DBUG_ASSERT(ticket->m_duration == MDL_EXPLICIT); + + release_lock(MDL_EXPLICIT, ticket); +} + + +/** Release all locks associated with the context. If the sentinel is not NULL, do not release locks stored in the list after and including the sentinel. - Transactional locks are added to the beginning of the list, i.e. - stored in reverse temporal order. This allows to employ this - function to: + Statement and transactional locks are added to the beginning of + the corresponding lists, i.e. stored in reverse temporal order. + This allows to employ this function to: - back off in case of a lock conflict. - - release all locks in the end of a transaction + - release all locks in the end of a statment or transaction - rollback to a savepoint. - - The sentinel semantics is used to support LOCK TABLES - mode and HANDLER statements: locks taken by these statements - survive COMMIT, ROLLBACK, ROLLBACK TO SAVEPOINT. */ -void MDL_context::release_locks_stored_before(MDL_ticket *sentinel) +void MDL_context::release_locks_stored_before(enum_mdl_duration duration, + MDL_ticket *sentinel) { MDL_ticket *ticket; - Ticket_iterator it(m_tickets); + Ticket_iterator it(m_tickets[duration]); DBUG_ENTER("MDL_context::release_locks_stored_before"); - if (m_tickets.is_empty()) + if (m_tickets[duration].is_empty()) DBUG_VOID_RETURN; while ((ticket= it++) && ticket != sentinel) { DBUG_PRINT("info", ("found lock to release ticket=%p", ticket)); - release_lock(ticket); + release_lock(duration, ticket); } - /* - If all locks were released, then the sentinel was not present - in the list. It must never happen because the sentinel was - bogus, i.e. pointed to a ticket that no longer exists. - */ - DBUG_ASSERT(! m_tickets.is_empty() || sentinel == NULL); DBUG_VOID_RETURN; } /** - Release all locks in the context which correspond to the same name/ - object as this lock request. + Release all explicit locks in the context which correspond to the + same name/object as this lock request. @param ticket One of the locks for the name/object for which all locks should be released. @@ -2312,17 +2360,13 @@ void MDL_context::release_all_locks_for_name(MDL_ticket *name) /* Remove matching lock tickets from the context. */ MDL_ticket *ticket; - Ticket_iterator it_ticket(m_tickets); + Ticket_iterator it_ticket(m_tickets[MDL_EXPLICIT]); while ((ticket= it_ticket++)) { DBUG_ASSERT(ticket->m_lock); - /* - We rarely have more than one ticket in this loop, - let's not bother saving on pthread_cond_broadcast(). - */ if (ticket->m_lock == lock) - release_lock(ticket); + release_lock(MDL_EXPLICIT, ticket); } } @@ -2377,9 +2421,10 @@ MDL_context::is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace, enum_mdl_type mdl_type) { MDL_request mdl_request; - bool is_transactional_unused; - mdl_request.init(mdl_namespace, db, name, mdl_type); - MDL_ticket *ticket= find_ticket(&mdl_request, &is_transactional_unused); + enum_mdl_duration not_unused; + /* We don't care about exact duration of lock here. */ + mdl_request.init(mdl_namespace, db, name, mdl_type, MDL_TRANSACTION); + MDL_ticket *ticket= find_ticket(&mdl_request, ¬_unused); DBUG_ASSERT(ticket == NULL || ticket->m_lock); @@ -2408,18 +2453,15 @@ bool MDL_ticket::has_pending_conflicting_lock() const @note It's safe to iterate and unlock any locks after taken after this savepoint because other statements that take other special locks cause a implicit commit (ie LOCK TABLES). - - @param mdl_savepont The last acquired MDL lock when the - savepoint was set. */ -void MDL_context::rollback_to_savepoint(MDL_ticket *mdl_savepoint) +void MDL_context::rollback_to_savepoint(const MDL_savepoint &mdl_savepoint) { DBUG_ENTER("MDL_context::rollback_to_savepoint"); /* If savepoint is NULL, it is from the start of the transaction. */ - release_locks_stored_before(mdl_savepoint ? - mdl_savepoint : m_trans_sentinel); + release_locks_stored_before(MDL_STATEMENT, mdl_savepoint.m_stmt_ticket); + release_locks_stored_before(MDL_TRANSACTION, mdl_savepoint.m_trans_ticket); DBUG_VOID_RETURN; } @@ -2437,65 +2479,150 @@ void MDL_context::rollback_to_savepoint(MDL_ticket *mdl_savepoint) void MDL_context::release_transactional_locks() { DBUG_ENTER("MDL_context::release_transactional_locks"); - release_locks_stored_before(m_trans_sentinel); + release_locks_stored_before(MDL_STATEMENT, NULL); + release_locks_stored_before(MDL_TRANSACTION, NULL); + DBUG_VOID_RETURN; +} + + +void MDL_context::release_statement_locks() +{ + DBUG_ENTER("MDL_context::release_transactional_locks"); + release_locks_stored_before(MDL_STATEMENT, NULL); DBUG_VOID_RETURN; } /** Does this savepoint have this lock? - - @retval TRUE The ticket is older than the savepoint and - is not LT, HA or GLR ticket. Thus it belongs - to the savepoint. - @retval FALSE The ticket is newer than the savepoint - or is an LT, HA or GLR ticket. + + @retval TRUE The ticket is older than the savepoint or + is an LT, HA or GLR ticket. Thus it belongs + to the savepoint or has explicit duration. + @retval FALSE The ticket is newer than the savepoint. + and is not an LT, HA or GLR ticket. */ -bool MDL_context::has_lock(MDL_ticket *mdl_savepoint, +bool MDL_context::has_lock(const MDL_savepoint &mdl_savepoint, MDL_ticket *mdl_ticket) { MDL_ticket *ticket; /* Start from the beginning, most likely mdl_ticket's been just acquired. */ - MDL_context::Ticket_iterator it(m_tickets); - bool found_savepoint= FALSE; + MDL_context::Ticket_iterator s_it(m_tickets[MDL_STATEMENT]); + MDL_context::Ticket_iterator t_it(m_tickets[MDL_TRANSACTION]); - while ((ticket= it++) && ticket != m_trans_sentinel) + while ((ticket= s_it++) && ticket != mdl_savepoint.m_stmt_ticket) { - /* - First met the savepoint. The ticket must be - somewhere after it. - */ - if (ticket == mdl_savepoint) - found_savepoint= TRUE; - /* - Met the ticket. If we haven't yet met the savepoint, - the ticket is newer than the savepoint. - */ if (ticket == mdl_ticket) - return found_savepoint; + return FALSE; } - /* Reached m_trans_sentinel. The ticket must be LT, HA or GRL ticket. */ - return FALSE; + + while ((ticket= t_it++) && ticket != mdl_savepoint.m_trans_ticket) + { + if (ticket == mdl_ticket) + return FALSE; + } + return TRUE; +} + + +/** + 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::set_lock_duration(MDL_ticket *mdl_ticket, + enum_mdl_duration duration) +{ + 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 } /** - 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. + Set explicit duration for all locks in the context. */ -void MDL_context::move_ticket_after_trans_sentinel(MDL_ticket *mdl_ticket) +void MDL_context::set_explicit_duration_for_all_locks() { - m_tickets.remove(mdl_ticket); - if (m_trans_sentinel == NULL) + 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++) { - m_trans_sentinel= mdl_ticket; - m_tickets.push_back(mdl_ticket); + Ticket_iterator it_ticket(m_tickets[i]); + + while ((ticket= it_ticket++)) + { + m_tickets[i].remove(ticket); + m_tickets[MDL_EXPLICIT].push_front(ticket); + } } - else - m_tickets.insert_after(m_trans_sentinel, mdl_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 } diff --git a/sql/mdl.h b/sql/mdl.h index 7938d833eac..ff30746e739 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -150,6 +150,15 @@ enum enum_mdl_type { MDL_TYPE_END}; +/** Duration of metadata lock. */ + +enum enum_mdl_duration { MDL_STATEMENT= 0, + MDL_TRANSACTION, + MDL_EXPLICIT, + /* This should be the last ! */ + MDL_DURATION_END }; + + /** Maximal length of key for metadata locking subsystem. */ #define MAX_MDLKEY_LENGTH (1 + NAME_LEN + 1 + NAME_LEN + 1) @@ -167,13 +176,16 @@ class MDL_key { public: /** - Object namespaces + Object namespaces. + Sic: when adding a new member to this enum make sure to + update m_namespace_to_wait_state_name array in mdl.cc! Different types of objects exist in different namespaces - TABLE is for tables and views. - FUNCTION is for stored functions. - PROCEDURE is for stored procedures. - TRIGGER is for triggers. + - EVENT is for event scheduler events Note that although there isn't metadata locking on triggers, it's necessary to have a separate namespace for them since MDL_key is also used outside of the MDL subsystem. @@ -184,6 +196,8 @@ public: FUNCTION, PROCEDURE, TRIGGER, + EVENT, + COMMIT, /* This should be the last ! */ NAMESPACE_END }; @@ -305,6 +319,8 @@ class MDL_request public: /** Type of metadata lock. */ enum enum_mdl_type type; + /** Duration for requested lock. */ + enum enum_mdl_duration duration; /** Pointers for participating in the list of lock requests for this context. @@ -327,17 +343,16 @@ public: void init(MDL_key::enum_mdl_namespace namespace_arg, const char *db_arg, const char *name_arg, - enum_mdl_type mdl_type_arg); - void init(const MDL_key *key_arg, enum_mdl_type mdl_type_arg); + enum_mdl_type mdl_type_arg, + enum_mdl_duration mdl_duration_arg); + void init(const MDL_key *key_arg, enum_mdl_type mdl_type_arg, + enum_mdl_duration mdl_duration_arg); /** Set type of lock request. Can be only applied to pending locks. */ inline void set_type(enum_mdl_type type_arg) { DBUG_ASSERT(ticket == NULL); type= type_arg; } - static MDL_request *create(MDL_key::enum_mdl_namespace mdl_namespace, - const char *db, const char *name, - enum_mdl_type mdl_type, MEM_ROOT *root); /* This is to work around the ugliness of TABLE_LIST @@ -363,6 +378,7 @@ public: MDL_request(const MDL_request *rhs) :type(rhs->type), + duration(rhs->duration), ticket(NULL), key(&rhs->key) {} @@ -484,17 +500,35 @@ public: private: friend class MDL_context; - MDL_ticket(MDL_context *ctx_arg, enum_mdl_type type_arg) + MDL_ticket(MDL_context *ctx_arg, enum_mdl_type type_arg +#ifndef DBUG_OFF + , enum_mdl_duration duration_arg +#endif + ) : m_type(type_arg), +#ifndef DBUG_OFF + m_duration(duration_arg), +#endif m_ctx(ctx_arg), m_lock(NULL) {} - static MDL_ticket *create(MDL_context *ctx_arg, enum_mdl_type type_arg); + static MDL_ticket *create(MDL_context *ctx_arg, enum_mdl_type type_arg +#ifndef DBUG_OFF + , enum_mdl_duration duration_arg +#endif + ); static void destroy(MDL_ticket *ticket); private: /** Type of metadata lock. Externally accessible. */ enum enum_mdl_type m_type; +#ifndef DBUG_OFF + /** + Duration of lock represented by this ticket. + Context private. Debug-only. + */ + enum_mdl_duration m_duration; +#endif /** Context of the owner of the metadata lock ticket. Externally accessible. */ @@ -512,6 +546,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(); - } - - MDL_ticket *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 !(m_tickets[MDL_STATEMENT].is_empty() && + m_tickets[MDL_TRANSACTION].is_empty() && + m_tickets[MDL_EXPLICIT].is_empty()); } - void set_trans_sentinel() + MDL_savepoint mdl_savepoint() { - m_trans_sentinel= m_tickets.front(); + return MDL_savepoint(m_tickets[MDL_STATEMENT].front(), + m_tickets[MDL_TRANSACTION].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. - - 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. - - 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, + Lists of all MDL tickets acquired by this connection. + + 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. + + 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. - */ - Ticket_list m_tickets; - /** - Separates transactional and non-transactional locks - in m_tickets list, @sa m_tickets. + involved schemas and global intention exclusive lock. */ - 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); diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 21754b23940..9e4026f3a1c 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -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}, diff --git a/sql/mysqld.h b/sql/mysqld.h index dc9f94c0d03..5e65d0d2a8d 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -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; diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index af9b452acd8..a35e7bb1612 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -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 diff --git a/sql/sp.cc b/sql/sp.cc index 7385a6ffcae..4dea6843ef1 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -27,7 +27,7 @@ #include "sql_acl.h" // SUPER_ACL #include "sp_head.h" #include "sp_cache.h" -#include "lock.h" // lock_routine_name +#include "lock.h" // lock_object_name #include <my_user.h> @@ -440,7 +440,7 @@ static TABLE *open_proc_table_for_update(THD *thd) { TABLE_LIST table_list; TABLE *table; - MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint(); + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); DBUG_ENTER("open_proc_table_for_update"); table_list.init_one_table("mysql", 5, "proc", 4, "proc", TL_WRITE); @@ -925,6 +925,8 @@ sp_create_routine(THD *thd, int type, sp_head *sp) TABLE *table; char definer[USER_HOST_BUFF_SIZE]; ulong saved_mode= thd->variables.sql_mode; + MDL_key::enum_mdl_namespace mdl_type= type == TYPE_ENUM_FUNCTION ? + MDL_key::FUNCTION : MDL_key::PROCEDURE; CHARSET_INFO *db_cs= get_default_db_collation(thd, sp->m_db.str); @@ -944,8 +946,7 @@ sp_create_routine(THD *thd, int type, sp_head *sp) type == TYPE_ENUM_FUNCTION); /* Grab an exclusive MDL lock. */ - if (lock_routine_name(thd, type == TYPE_ENUM_FUNCTION, - sp->m_db.str, sp->m_name.str)) + if (lock_object_name(thd, mdl_type, sp->m_db.str, sp->m_name.str)) DBUG_RETURN(SP_OPEN_TABLE_FAILED); /* Reset sql_mode during data dictionary operations. */ @@ -1193,6 +1194,8 @@ sp_drop_routine(THD *thd, int type, sp_name *name) TABLE *table; int ret; bool save_binlog_row_based; + MDL_key::enum_mdl_namespace mdl_type= type == TYPE_ENUM_FUNCTION ? + MDL_key::FUNCTION : MDL_key::PROCEDURE; DBUG_ENTER("sp_drop_routine"); DBUG_PRINT("enter", ("type: %d name: %.*s", type, (int) name->m_name.length, name->m_name.str)); @@ -1201,8 +1204,7 @@ sp_drop_routine(THD *thd, int type, sp_name *name) type == TYPE_ENUM_FUNCTION); /* Grab an exclusive MDL lock. */ - if (lock_routine_name(thd, type == TYPE_ENUM_FUNCTION, - name->m_db.str, name->m_name.str)) + if (lock_object_name(thd, mdl_type, name->m_db.str, name->m_name.str)) DBUG_RETURN(SP_DELETE_ROW_FAILED); if (!(table= open_proc_table_for_update(thd))) @@ -1273,6 +1275,8 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics) TABLE *table; int ret; bool save_binlog_row_based; + MDL_key::enum_mdl_namespace mdl_type= type == TYPE_ENUM_FUNCTION ? + MDL_key::FUNCTION : MDL_key::PROCEDURE; DBUG_ENTER("sp_update_routine"); DBUG_PRINT("enter", ("type: %d name: %.*s", type, (int) name->m_name.length, name->m_name.str)); @@ -1281,8 +1285,7 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics) type == TYPE_ENUM_FUNCTION); /* Grab an exclusive MDL lock. */ - if (lock_routine_name(thd, type == TYPE_ENUM_FUNCTION, - name->m_db.str, name->m_name.str)) + if (lock_object_name(thd, mdl_type, name->m_db.str, name->m_name.str)) DBUG_RETURN(SP_OPEN_TABLE_FAILED); if (!(table= open_proc_table_for_update(thd))) @@ -1370,7 +1373,7 @@ sp_drop_db_routines(THD *thd, char *db) TABLE *table; int ret; uint key_len; - MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint(); + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); DBUG_ENTER("sp_drop_db_routines"); DBUG_PRINT("enter", ("db: %s", db)); @@ -1697,7 +1700,7 @@ bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, (Sroutine_hash_entry *)arena->alloc(sizeof(Sroutine_hash_entry)); if (!rn) // OOM. Error will be reported using fatal_error(). return FALSE; - rn->mdl_request.init(key, MDL_SHARED); + rn->mdl_request.init(key, MDL_SHARED, MDL_TRANSACTION); if (my_hash_insert(&prelocking_ctx->sroutines, (uchar *)rn)) return FALSE; prelocking_ctx->sroutines_list.link_in_list(rn, &rn->next); diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 0d33bd5c599..5089e7e0340 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -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; diff --git a/sql/sql_admin.cc b/sql/sql_admin.cc index 21a05a5baca..f648d219fac 100644 --- a/sql/sql_admin.cc +++ b/sql/sql_admin.cc @@ -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, diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 8caae3ee406..60c32a1a376 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -21,7 +21,7 @@ #include "sql_priv.h" #include "unireg.h" #include "debug_sync.h" -#include "lock.h" // broadcast_refresh, mysql_lock_remove, +#include "lock.h" // mysql_lock_remove, // mysql_unlock_tables, // mysql_lock_have_duplicate #include "sql_show.h" // append_identifier @@ -1285,20 +1285,12 @@ static void mark_used_tables_as_free_for_reuse(THD *thd, TABLE *table) static void close_open_tables(THD *thd) { - bool found_old_table= 0; - mysql_mutex_assert_not_owner(&LOCK_open); DBUG_PRINT("info", ("thd->open_tables: 0x%lx", (long) thd->open_tables)); while (thd->open_tables) - found_old_table|= close_thread_table(thd, &thd->open_tables); - - if (found_old_table) - { - /* Tell threads waiting for refresh that something has happened */ - broadcast_refresh(); - } + (void) close_thread_table(thd, &thd->open_tables); } @@ -1364,11 +1356,6 @@ close_all_tables_for_name(THD *thd, TABLE_SHARE *share, /* Remove the table share from the cache. */ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, db, table_name, FALSE); - /* - There could be a FLUSH thread waiting - on the table to go away. Wake it up. - */ - broadcast_refresh(); } @@ -2463,7 +2450,8 @@ open_table_get_mdl_lock(THD *thd, Open_table_context *ot_ctx, mdl_request_shared.init(&mdl_request->key, (flags & MYSQL_OPEN_FORCE_SHARED_MDL) ? - MDL_SHARED : MDL_SHARED_HIGH_PRIO); + MDL_SHARED : MDL_SHARED_HIGH_PRIO, + MDL_TRANSACTION); mdl_request= &mdl_request_shared; } @@ -2628,32 +2616,6 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, 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 are always thread-local and "shadow" possible base tables with the @@ -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; diff --git a/sql/sql_base.h b/sql/sql_base.h index 00f335ab209..35fa04b3674 100644 --- a/sql/sql_base.h +++ b/sql/sql_base.h @@ -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; }; diff --git a/sql/sql_class.cc b/sql/sql_class.cc index c848d686299..1823a0416ff 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -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) diff --git a/sql/sql_class.h b/sql/sql_class.h index 8c444f34364..8bf72f1cdb2 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -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(); @@ -3503,20 +3520,10 @@ 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 */ diff --git a/sql/sql_db.cc b/sql/sql_db.cc index 517cb9139e9..4df8748429c 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -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; diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index a5c126a8521..b5cd3ac9e9a 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -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; } diff --git a/sql/sql_handler.h b/sql/sql_handler.h index c5da3c4d468..2eea552d7c9 100644 --- a/sql/sql_handler.h +++ b/sql/sql_handler.h @@ -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 */ diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 1f8da3fab5c..860a5b8941a 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -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; } diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index d91489b4a7a..7a8a86d3ca4 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -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 diff --git a/sql/sql_lex.h b/sql/sql_lex.h index c20c2f0bca2..191562bd3e8 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -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() diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index c09a4abb2c8..e95745634e8 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -18,12 +18,9 @@ #include "sql_priv.h" #include "unireg.h" // REQUIRED: for other includes #include "sql_parse.h" // sql_kill, *_precheck, *_prepare -#include "lock.h" // wait_if_global_read_lock, - // unlock_global_read_lock, - // try_transactional_lock, +#include "lock.h" // try_transactional_lock, // check_transactional_lock, // set_handler_table_locks, - // start_waiting_global_read_lock, // lock_global_read_lock, // make_global_read_lock_block_commit #include "sql_base.h" // find_temporary_tablesx @@ -260,21 +257,20 @@ void init_update_queries(void) the code, in particular in the Query_log_event's constructor. */ sql_command_flags[SQLCOM_CREATE_TABLE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL | + CF_AUTO_COMMIT_TRANS | CF_CAN_GENERATE_ROW_EVENTS; sql_command_flags[SQLCOM_CREATE_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_ALTER_TABLE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND | - CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL; + CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_TRUNCATE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND | - CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL; + CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DROP_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_LOAD]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_PROTECT_AGAINST_GRL | CF_CAN_GENERATE_ROW_EVENTS; - sql_command_flags[SQLCOM_CREATE_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL; - sql_command_flags[SQLCOM_DROP_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL; - sql_command_flags[SQLCOM_ALTER_DB_UPGRADE]= CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL; - sql_command_flags[SQLCOM_ALTER_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL; + sql_command_flags[SQLCOM_CREATE_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_DB_UPGRADE]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_RENAME_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DROP_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_CREATE_VIEW]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | @@ -285,26 +281,18 @@ void init_update_queries(void) sql_command_flags[SQLCOM_CREATE_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_ALTER_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DROP_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_CREATE_TRIGGER]= CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_DROP_TRIGGER]= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_UPDATE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_PROTECT_AGAINST_GRL | CF_CAN_GENERATE_ROW_EVENTS; sql_command_flags[SQLCOM_UPDATE_MULTI]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_PROTECT_AGAINST_GRL | CF_CAN_GENERATE_ROW_EVENTS; sql_command_flags[SQLCOM_INSERT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_PROTECT_AGAINST_GRL | CF_CAN_GENERATE_ROW_EVENTS; sql_command_flags[SQLCOM_INSERT_SELECT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_PROTECT_AGAINST_GRL | CF_CAN_GENERATE_ROW_EVENTS; sql_command_flags[SQLCOM_DELETE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_PROTECT_AGAINST_GRL | CF_CAN_GENERATE_ROW_EVENTS; sql_command_flags[SQLCOM_DELETE_MULTI]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_PROTECT_AGAINST_GRL | CF_CAN_GENERATE_ROW_EVENTS; sql_command_flags[SQLCOM_REPLACE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | CF_CAN_GENERATE_ROW_EVENTS; @@ -366,20 +354,19 @@ void init_update_queries(void) CF_REEXECUTION_FRAGILE); - sql_command_flags[SQLCOM_CREATE_USER]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; - sql_command_flags[SQLCOM_RENAME_USER]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; - sql_command_flags[SQLCOM_DROP_USER]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; + sql_command_flags[SQLCOM_CREATE_USER]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_RENAME_USER]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_DROP_USER]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_GRANT]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_REVOKE]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_REVOKE_ALL]= CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_OPTIMIZE]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_CREATE_FUNCTION]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_CREATE_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_CREATE_SPFUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_DROP_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_DROP_FUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_ALTER_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS; - sql_command_flags[SQLCOM_ALTER_FUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_PROCEDURE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_SPFUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_PROCEDURE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_FUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_PROCEDURE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_FUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_INSTALL_PLUGIN]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_UNINSTALL_PLUGIN]= CF_CHANGES_DATA; @@ -1111,7 +1098,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, SHOW statements should not add the used tables to the list of tables used in a transaction. */ - MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint(); + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); status_var_increment(thd->status_var.com_stat[SQLCOM_SHOW_FIELDS]); if (thd->copy_db_to(&db.str, &db.length)) @@ -1754,16 +1741,6 @@ bool sp_process_definer(THD *thd) /** Execute command saved in thd and lex->sql_command. - Before every operation that can request a write lock for a table - wait if a global read lock exists. However do not wait if this - thread has locked tables already. No new locks can be requested - until the other locks are released. The thread that requests the - global read lock waits for write locked tables to become unlocked. - - Note that wait_if_global_read_lock() sets a protection against a new - global read lock when it succeeds. This needs to be released by - start_waiting_global_read_lock() after the operation. - @param thd Thread handle @todo @@ -1797,7 +1774,6 @@ mysql_execute_command(THD *thd) /* have table map for update for multi-update statement (BUG#37051) */ bool have_table_map_for_update= FALSE; #endif - /* Saved variable value */ DBUG_ENTER("mysql_execute_command"); #ifdef WITH_PARTITION_STORAGE_ENGINE thd->work_part_info= 0; @@ -1989,17 +1965,6 @@ mysql_execute_command(THD *thd) thd->mdl_context.release_transactional_locks(); } - /* - Check if this command needs protection against the global read lock - to avoid deadlock. See CF_PROTECT_AGAINST_GRL. - start_waiting_global_read_lock() is called at the end of - mysql_execute_command(). - */ - if (((sql_command_flags[lex->sql_command] & CF_PROTECT_AGAINST_GRL) != 0) && - !thd->locked_tables_mode) - if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) - goto error; - #ifndef DBUG_OFF if (lex->sql_command != SQLCOM_SET_OPTION) DEBUG_SYNC(thd,"before_execute_sql_command"); @@ -2074,10 +2039,6 @@ mysql_execute_command(THD *thd) if (res) break; - if (!thd->locked_tables_mode && lex->protect_against_global_read_lock && - thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) - break; - res= execute_sqlcom_select(thd, all_tables); break; } @@ -2336,20 +2297,6 @@ case SQLCOM_PREPARE: create_info.default_table_charset= create_info.table_charset; create_info.table_charset= 0; } - /* - The create-select command will open and read-lock the select table - and then create, open and write-lock the new table. If a global - read lock steps in, we get a deadlock. The write lock waits for - the global read lock, while the global read lock waits for the - select table to be closed. So we wait until the global readlock is - gone before starting both steps. Note that - wait_if_global_read_lock() sets a protection against a new global - read lock when it succeeds. This needs to be released by - start_waiting_global_read_lock(). We protect the normal CREATE - TABLE in the same way. That way we avoid that a new table is - created during a global read lock. - Protection against grl is covered by the CF_PROTECT_AGAINST_GRL flag. - */ #ifdef WITH_PARTITION_STORAGE_ENGINE { @@ -3143,9 +3090,6 @@ end_with_restore_list: if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE)) goto error; - if (lex->protect_against_global_read_lock && - thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) - goto error; thd->variables.option_bits|= OPTION_TABLE_LOCK; thd->in_lock_tables=1; @@ -3801,13 +3745,22 @@ end_with_restore_list: Security_context *backup= NULL; LEX_USER *definer= thd->lex->definer; /* - We're going to issue an implicit GRANT statement. - It takes metadata locks and updates system tables. - Make sure that sp_create_routine() did not leave any - locks in the MDL context, so there is no risk to - deadlock. + We're going to issue an implicit GRANT statement so we close all + open tables. We have to keep metadata locks as this ensures that + this statement is atomic against concurent FLUSH TABLES WITH READ + LOCK. Deadlocks which can arise due to fact that this implicit + statement takes metadata locks should be detected by a deadlock + detector in MDL subsystem and reported as errors. + + No need to commit/rollback statement transaction, it's not started. + + TODO: Long-term we should either ensure that implicit GRANT statement + is written into binary log as a separate statement or make both + creation of routine and implicit GRANT parts of one fully atomic + statement. */ - close_mysql_tables(thd); + DBUG_ASSERT(thd->transaction.stmt.is_empty()); + close_thread_tables(thd); /* Check if the definer exists on slave, then use definer privilege to insert routine privileges to mysql.procs_priv. @@ -4067,13 +4020,22 @@ create_sp_error: #ifndef NO_EMBEDDED_ACCESS_CHECKS /* - We're going to issue an implicit REVOKE statement. - It takes metadata locks and updates system tables. - Make sure that sp_create_routine() did not leave any - locks in the MDL context, so there is no risk to - deadlock. + We're going to issue an implicit REVOKE statement so we close all + open tables. We have to keep metadata locks as this ensures that + this statement is atomic against concurent FLUSH TABLES WITH READ + LOCK. Deadlocks which can arise due to fact that this implicit + statement takes metadata locks should be detected by a deadlock + detector in MDL subsystem and reported as errors. + + No need to commit/rollback statement transaction, it's not started. + + TODO: Long-term we should either ensure that implicit REVOKE statement + is written into binary log as a separate statement or make both + dropping of routine and implicit REVOKE parts of one fully atomic + statement. */ - close_mysql_tables(thd); + DBUG_ASSERT(thd->transaction.stmt.is_empty()); + close_thread_tables(thd); if (sp_result != SP_KEY_NOT_FOUND && sp_automatic_privileges && !opt_noacl && @@ -4362,14 +4324,6 @@ error: res= TRUE; finish: - if (thd->global_read_lock.has_protection()) - { - /* - Release the protection against the global read lock and wake - everyone, who might want to set a global read lock. - */ - thd->global_read_lock.start_waiting_global_read_lock(thd); - } DBUG_ASSERT(!thd->in_active_multi_stmt_transaction() || thd->in_multi_stmt_transaction_mode()); @@ -4405,6 +4359,11 @@ finish: close_thread_tables(thd); thd_proc_info(thd, 0); +#ifndef DBUG_OFF + if (lex->sql_command != SQLCOM_SET_OPTION && ! thd->in_sub_stmt) + DEBUG_SYNC(thd, "execute_command_after_close_tables"); +#endif + if (stmt_causes_implicit_commit(thd, CF_IMPLICIT_COMMIT_END)) { /* No transaction control allowed in sub-statements. */ @@ -4430,6 +4389,10 @@ finish: */ thd->mdl_context.release_transactional_locks(); } + else if (! thd->in_sub_stmt) + { + thd->mdl_context.release_statement_locks(); + } DBUG_RETURN(res || thd->is_error()); } @@ -5899,7 +5862,8 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->next_name_resolution_table= NULL; /* Link table in global list (all used tables) */ lex->add_to_query_tables(ptr); - ptr->mdl_request.init(MDL_key::TABLE, ptr->db, ptr->table_name, mdl_type); + ptr->mdl_request.init(MDL_key::TABLE, ptr->db, ptr->table_name, mdl_type, + MDL_TRANSACTION); DBUG_RETURN(ptr); } diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index fcbf2c48896..2b51305d099 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -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 diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc index 8f990eae001..6a7b0b0b3ad 100644 --- a/sql/sql_rename.cc +++ b/sql/sql_rename.cc @@ -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); } diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 19a73c85a9b..b8c66562b1a 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -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. diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 1c6ca4a48d9..ed0acc66e49 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -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); } diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index a9b52eee9fc..c2e3cd9944a 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -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); diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 96b1ac67b49..8a9feb1b3f8 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -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); /* diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 5fdf7b8d850..54b5eb43ab1 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -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; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 66d74a398fb..9aa938437b1 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -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; } ; diff --git a/sql/table.cc b/sql/table.cc index f55095d6e82..8cd2e9e9bab 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -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); } diff --git a/sql/table.h b/sql/table.h index c8e1ad8e658..6aba4f21db2 100644 --- a/sql/table.h +++ b/sql/table.h @@ -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); } /* diff --git a/sql/transaction.cc b/sql/transaction.cc index d3e3ba142b9..b331fea89fe 100644 --- a/sql/transaction.cc +++ b/sql/transaction.cc @@ -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 |