diff options
author | Sergey Vojtovich <svoj@mariadb.org> | 2015-04-01 14:07:02 +0400 |
---|---|---|
committer | Sergey Vojtovich <svoj@mariadb.org> | 2015-04-01 14:07:02 +0400 |
commit | 334e7c9bccbc6892efbb35f5930f80ccf1d770a8 (patch) | |
tree | 7ddc38e5c776a4f82c9a9731f8d6d3a79103eef0 | |
parent | cbc5157feb9801310e458f7ed10983ad478c881e (diff) | |
download | mariadb-git-bb-mdev7895.tar.gz |
MDEV-7895 - HANDLER READ doesn't upgrade metadata lock from S to SRbb-mdev7895
Change code for HANDLER READ statements to upgrade S metadata lock to
SR metadata lock for the duration of read. This allows us properly
isolate HANDLER READ from LOCK TABLES WRITE and makes metadata locking
for these statements consistent with locking for other DML.
HANDLER-related tests had to be adjusted to take into account that
HANDLER READ will wait for and acquire SR lock.
-rw-r--r-- | mysql-test/suite/handler/aria.result | 58 | ||||
-rw-r--r-- | mysql-test/suite/handler/handler.inc | 56 | ||||
-rw-r--r-- | mysql-test/suite/handler/heap.result | 58 | ||||
-rw-r--r-- | mysql-test/suite/handler/innodb.result | 58 | ||||
-rw-r--r-- | mysql-test/suite/handler/interface.result | 10 | ||||
-rw-r--r-- | mysql-test/suite/handler/interface.test | 8 | ||||
-rw-r--r-- | mysql-test/suite/handler/myisam.result | 58 | ||||
-rw-r--r-- | sql/lock.cc | 8 | ||||
-rw-r--r-- | sql/sql_class.h | 35 | ||||
-rw-r--r-- | sql/sql_handler.cc | 102 |
10 files changed, 222 insertions, 229 deletions
diff --git a/mysql-test/suite/handler/aria.result b/mysql-test/suite/handler/aria.result index e0b98bd36a0..e0751e322a2 100644 --- a/mysql-test/suite/handler/aria.result +++ b/mysql-test/suite/handler/aria.result @@ -977,12 +977,10 @@ drop table t1 ; # --> connection con2 # Waitng for 'drop table t1' to get blocked... # --> connection default +# Attempt to upgrade metadata locks to SR from S will lead to +# deadlock which will result in table being automatically closed. handler t1 read a prev; -a b -5 NULL -handler t1 read a prev; -a b -4 NULL +ERROR 42S02: Table 'test.t1' doesn't exist handler t1 close; # --> connection con1 # Reaping 'drop table t1'... @@ -1026,20 +1024,16 @@ a # thus we can reopen it in the handler handler t1 open; # We can commit the transaction, it doesn't close the handler -# and doesn't let DROP to proceed. +# and doesn't let DROP to proceed immediately. commit; +# Waiting for 'drop table t1' to get blocked... +# --> connection default +# OTOH the first attempt to read from HANDLER will lead to metadata +# locks deadlock and thus to HANDLER being automatically closed. handler t1 read a prev; -a -5 -handler t1 read a prev; -a -4 -handler t1 read a prev; -a -3 +ERROR 42S02: Table 'test.t1' doesn't exist handler t1 close; # --> connection con1 -# Now drop can proceed # Reaping 'drop table t1'... # --> connection default # @@ -1156,15 +1150,13 @@ rollback to savepoint sv; # Reaping 'drop table t2'... # Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler # lock. +# --> connection con3 +# Check if 'drop table t1' still blocked... # --> connection default +# Demonstrate that the drop will go through as soon as we close +# or will try to access HANDLER handler t1 read a next; -a -3 -handler t1 read a next; -a -4 -# Demonstrate that the drop will go through as soon as we close the -# HANDLER +ERROR 42S02: Table 'test.t1' doesn't exist handler t1 close; # connection con1 # Reaping 'drop table t1'... @@ -1215,15 +1207,13 @@ rollback to savepoint sv; # Reaping 'drop table t2'... # Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler # lock. +# --> connection con3 +# Check if 'drop table t1' is still blocked... # --> connection default +# Demonstrate that the drop will go through as soon as we access or +# close the HANDLER handler t1 read a next; -a -3 -handler t1 read a next; -a -4 -# Demonstrate that the drop will go through as soon as we close the -# HANDLER +ERROR 42S02: Table 'test.t1' doesn't exist handler t1 close; # connection con1 # Reaping 'drop table t1'... @@ -1261,14 +1251,12 @@ drop table t3; # Let DROP TABLE statement sync in. # --> connection con2 # Waiting for 'drop table t3' to get blocked... -# Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler -# lock. +# The fact that DROP TABLE is blocked means that ROLLBACK TO SAVEPOINT +# didn't release the handler lock. # --> connection default +# Drop will go through as soon as we access or close the HANDLER handler t3 read a next; -a -2 -# Demonstrate that the drop will go through as soon as we close the -# HANDLER +ERROR 42S02: Table 'test.t3' doesn't exist handler t3 close; # connection con1 # Reaping 'drop table t3'... diff --git a/mysql-test/suite/handler/handler.inc b/mysql-test/suite/handler/handler.inc index c71dc53e5ac..c8ab0210361 100644 --- a/mysql-test/suite/handler/handler.inc +++ b/mysql-test/suite/handler/handler.inc @@ -796,7 +796,9 @@ let $wait_condition=select count(*)=1 from information_schema.processlist --source include/wait_condition.inc --echo # --> connection default connection default; -handler t1 read a prev; +--echo # Attempt to upgrade metadata locks to SR from S will lead to +--echo # deadlock which will result in table being automatically closed. +--error ER_NO_SUCH_TABLE handler t1 read a prev; handler t1 close; --echo # --> connection con1 @@ -835,15 +837,23 @@ select * from t1; --echo # thus we can reopen it in the handler handler t1 open; --echo # We can commit the transaction, it doesn't close the handler ---echo # and doesn't let DROP to proceed. +--echo # and doesn't let DROP to proceed immediately. commit; -handler t1 read a prev; -handler t1 read a prev; +connection con2; +--echo # Waiting for 'drop table t1' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist + where state='Waiting for table metadata lock' and + info='drop table t1'; +--source include/wait_condition.inc +--echo # --> connection default +connection default; +--echo # OTOH the first attempt to read from HANDLER will lead to metadata +--echo # locks deadlock and thus to HANDLER being automatically closed. +--error ER_NO_SUCH_TABLE handler t1 read a prev; handler t1 close; --echo # --> connection con1 connection con1; ---echo # Now drop can proceed --echo # Reaping 'drop table t1'... --reap --echo # --> connection default @@ -984,12 +994,19 @@ connection con2; --reap --echo # Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler --echo # lock. +--echo # --> connection con3 +connection con3; +--echo # Check if 'drop table t1' still blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist + where state='Waiting for table metadata lock' and + info='drop table t1'; +--source include/wait_condition.inc --echo # --> connection default connection default; +--echo # Demonstrate that the drop will go through as soon as we close +--echo # or will try to access HANDLER +--error ER_NO_SUCH_TABLE handler t1 read a next; -handler t1 read a next; ---echo # Demonstrate that the drop will go through as soon as we close the ---echo # HANDLER handler t1 close; --echo # connection con1 connection con1; @@ -1053,12 +1070,19 @@ connection con2; --reap --echo # Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler --echo # lock. +--echo # --> connection con3 +connection con3; +--echo # Check if 'drop table t1' is still blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist + where state='Waiting for table metadata lock' and + info='drop table t1'; +--source include/wait_condition.inc --echo # --> connection default connection default; +--echo # Demonstrate that the drop will go through as soon as we access or +--echo # close the HANDLER +--error ER_NO_SUCH_TABLE handler t1 read a next; -handler t1 read a next; ---echo # Demonstrate that the drop will go through as soon as we close the ---echo # HANDLER handler t1 close; --echo # connection con1 connection con1; @@ -1100,13 +1124,13 @@ let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table metadata lock' and info='drop table t3'; --source include/wait_condition.inc ---echo # Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler ---echo # lock. +--echo # The fact that DROP TABLE is blocked means that ROLLBACK TO SAVEPOINT +--echo # didn't release the handler lock. --echo # --> connection default connection default; +--echo # Drop will go through as soon as we access or close the HANDLER +--error ER_NO_SUCH_TABLE handler t3 read a next; ---echo # Demonstrate that the drop will go through as soon as we close the ---echo # HANDLER handler t3 close; --echo # connection con1 connection con1; @@ -1214,7 +1238,7 @@ connection con1; --echo # Waiting for 'handler t1 read a next' to get blocked... let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Waiting for table level lock" and + where state = "Waiting for table metadata lock" and info = "handler t1 read a next"; --source include/wait_condition.inc diff --git a/mysql-test/suite/handler/heap.result b/mysql-test/suite/handler/heap.result index 527986edb5c..15522c5a71e 100644 --- a/mysql-test/suite/handler/heap.result +++ b/mysql-test/suite/handler/heap.result @@ -977,12 +977,10 @@ drop table t1 ; # --> connection con2 # Waitng for 'drop table t1' to get blocked... # --> connection default +# Attempt to upgrade metadata locks to SR from S will lead to +# deadlock which will result in table being automatically closed. handler t1 read a prev; -a b -5 NULL -handler t1 read a prev; -a b -4 NULL +ERROR 42S02: Table 'test.t1' doesn't exist handler t1 close; # --> connection con1 # Reaping 'drop table t1'... @@ -1026,20 +1024,16 @@ a # thus we can reopen it in the handler handler t1 open; # We can commit the transaction, it doesn't close the handler -# and doesn't let DROP to proceed. +# and doesn't let DROP to proceed immediately. commit; +# Waiting for 'drop table t1' to get blocked... +# --> connection default +# OTOH the first attempt to read from HANDLER will lead to metadata +# locks deadlock and thus to HANDLER being automatically closed. handler t1 read a prev; -a -5 -handler t1 read a prev; -a -4 -handler t1 read a prev; -a -3 +ERROR 42S02: Table 'test.t1' doesn't exist handler t1 close; # --> connection con1 -# Now drop can proceed # Reaping 'drop table t1'... # --> connection default # @@ -1156,15 +1150,13 @@ rollback to savepoint sv; # Reaping 'drop table t2'... # Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler # lock. +# --> connection con3 +# Check if 'drop table t1' still blocked... # --> connection default +# Demonstrate that the drop will go through as soon as we close +# or will try to access HANDLER handler t1 read a next; -a -3 -handler t1 read a next; -a -4 -# Demonstrate that the drop will go through as soon as we close the -# HANDLER +ERROR 42S02: Table 'test.t1' doesn't exist handler t1 close; # connection con1 # Reaping 'drop table t1'... @@ -1215,15 +1207,13 @@ rollback to savepoint sv; # Reaping 'drop table t2'... # Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler # lock. +# --> connection con3 +# Check if 'drop table t1' is still blocked... # --> connection default +# Demonstrate that the drop will go through as soon as we access or +# close the HANDLER handler t1 read a next; -a -3 -handler t1 read a next; -a -4 -# Demonstrate that the drop will go through as soon as we close the -# HANDLER +ERROR 42S02: Table 'test.t1' doesn't exist handler t1 close; # connection con1 # Reaping 'drop table t1'... @@ -1261,14 +1251,12 @@ drop table t3; # Let DROP TABLE statement sync in. # --> connection con2 # Waiting for 'drop table t3' to get blocked... -# Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler -# lock. +# The fact that DROP TABLE is blocked means that ROLLBACK TO SAVEPOINT +# didn't release the handler lock. # --> connection default +# Drop will go through as soon as we access or close the HANDLER handler t3 read a next; -a -2 -# Demonstrate that the drop will go through as soon as we close the -# HANDLER +ERROR 42S02: Table 'test.t3' doesn't exist handler t3 close; # connection con1 # Reaping 'drop table t3'... diff --git a/mysql-test/suite/handler/innodb.result b/mysql-test/suite/handler/innodb.result index 78660b0ef9c..053d9cf2e4b 100644 --- a/mysql-test/suite/handler/innodb.result +++ b/mysql-test/suite/handler/innodb.result @@ -981,12 +981,10 @@ drop table t1 ; # --> connection con2 # Waitng for 'drop table t1' to get blocked... # --> connection default +# Attempt to upgrade metadata locks to SR from S will lead to +# deadlock which will result in table being automatically closed. handler t1 read a prev; -a b -5 NULL -handler t1 read a prev; -a b -4 NULL +ERROR 42S02: Table 'test.t1' doesn't exist handler t1 close; # --> connection con1 # Reaping 'drop table t1'... @@ -1030,20 +1028,16 @@ a # thus we can reopen it in the handler handler t1 open; # We can commit the transaction, it doesn't close the handler -# and doesn't let DROP to proceed. +# and doesn't let DROP to proceed immediately. commit; +# Waiting for 'drop table t1' to get blocked... +# --> connection default +# OTOH the first attempt to read from HANDLER will lead to metadata +# locks deadlock and thus to HANDLER being automatically closed. handler t1 read a prev; -a -5 -handler t1 read a prev; -a -4 -handler t1 read a prev; -a -3 +ERROR 42S02: Table 'test.t1' doesn't exist handler t1 close; # --> connection con1 -# Now drop can proceed # Reaping 'drop table t1'... # --> connection default # @@ -1160,15 +1154,13 @@ rollback to savepoint sv; # Reaping 'drop table t2'... # Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler # lock. +# --> connection con3 +# Check if 'drop table t1' still blocked... # --> connection default +# Demonstrate that the drop will go through as soon as we close +# or will try to access HANDLER handler t1 read a next; -a -3 -handler t1 read a next; -a -4 -# Demonstrate that the drop will go through as soon as we close the -# HANDLER +ERROR 42S02: Table 'test.t1' doesn't exist handler t1 close; # connection con1 # Reaping 'drop table t1'... @@ -1219,15 +1211,13 @@ rollback to savepoint sv; # Reaping 'drop table t2'... # Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler # lock. +# --> connection con3 +# Check if 'drop table t1' is still blocked... # --> connection default +# Demonstrate that the drop will go through as soon as we access or +# close the HANDLER handler t1 read a next; -a -3 -handler t1 read a next; -a -4 -# Demonstrate that the drop will go through as soon as we close the -# HANDLER +ERROR 42S02: Table 'test.t1' doesn't exist handler t1 close; # connection con1 # Reaping 'drop table t1'... @@ -1265,14 +1255,12 @@ drop table t3; # Let DROP TABLE statement sync in. # --> connection con2 # Waiting for 'drop table t3' to get blocked... -# Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler -# lock. +# The fact that DROP TABLE is blocked means that ROLLBACK TO SAVEPOINT +# didn't release the handler lock. # --> connection default +# Drop will go through as soon as we access or close the HANDLER handler t3 read a next; -a -2 -# Demonstrate that the drop will go through as soon as we close the -# HANDLER +ERROR 42S02: Table 'test.t3' doesn't exist handler t3 close; # connection con1 # Reaping 'drop table t3'... diff --git a/mysql-test/suite/handler/interface.result b/mysql-test/suite/handler/interface.result index 89dec29f412..ec613d9f57a 100644 --- a/mysql-test/suite/handler/interface.result +++ b/mysql-test/suite/handler/interface.result @@ -273,19 +273,13 @@ ERROR HY000: Storage engine CSV of the table `test`.`t1` doesn't have this optio handler t1 close; unlock tables; drop table t1; -# Now test case which was reported originally but which no longer -# triggers execution path which has caused the problem. +# Now test case which was reported originally. create table t1 (a int not null); insert into t1 values (1); handler t1 open; alter table t1 engine=csv; -# Since S metadata lock was already acquired at HANDLER OPEN time -# and TL_READ lock requested by HANDLER READ is compatible with -# ALTER's TL_WRITE_ALLOW_READ the below statement should succeed -# without waiting. The old version of table should be used in it. handler t1 read next; -a -1 +ERROR HY000: Storage engine CSV of the table `test`.`t1` doesn't have this option handler t1 close; drop table t1; USE information_schema; diff --git a/mysql-test/suite/handler/interface.test b/mysql-test/suite/handler/interface.test index 2ef617c3ce7..4deaecd6ea8 100644 --- a/mysql-test/suite/handler/interface.test +++ b/mysql-test/suite/handler/interface.test @@ -333,8 +333,7 @@ connection con1; --reap unlock tables; drop table t1; ---echo # Now test case which was reported originally but which no longer ---echo # triggers execution path which has caused the problem. +--echo # Now test case which was reported originally. connection default; create table t1 (a int not null); insert into t1 values (1); @@ -348,10 +347,7 @@ let $wait_condition= info = "alter table t1 engine=csv"; --source include/wait_condition.inc connection default; ---echo # Since S metadata lock was already acquired at HANDLER OPEN time ---echo # and TL_READ lock requested by HANDLER READ is compatible with ---echo # ALTER's TL_WRITE_ALLOW_READ the below statement should succeed ---echo # without waiting. The old version of table should be used in it. +--error ER_ILLEGAL_HA handler t1 read next; handler t1 close; connection con1; diff --git a/mysql-test/suite/handler/myisam.result b/mysql-test/suite/handler/myisam.result index 9081722d866..05b3e787a6d 100644 --- a/mysql-test/suite/handler/myisam.result +++ b/mysql-test/suite/handler/myisam.result @@ -977,12 +977,10 @@ drop table t1 ; # --> connection con2 # Waitng for 'drop table t1' to get blocked... # --> connection default +# Attempt to upgrade metadata locks to SR from S will lead to +# deadlock which will result in table being automatically closed. handler t1 read a prev; -a b -5 NULL -handler t1 read a prev; -a b -4 NULL +ERROR 42S02: Table 'test.t1' doesn't exist handler t1 close; # --> connection con1 # Reaping 'drop table t1'... @@ -1026,20 +1024,16 @@ a # thus we can reopen it in the handler handler t1 open; # We can commit the transaction, it doesn't close the handler -# and doesn't let DROP to proceed. +# and doesn't let DROP to proceed immediately. commit; +# Waiting for 'drop table t1' to get blocked... +# --> connection default +# OTOH the first attempt to read from HANDLER will lead to metadata +# locks deadlock and thus to HANDLER being automatically closed. handler t1 read a prev; -a -5 -handler t1 read a prev; -a -4 -handler t1 read a prev; -a -3 +ERROR 42S02: Table 'test.t1' doesn't exist handler t1 close; # --> connection con1 -# Now drop can proceed # Reaping 'drop table t1'... # --> connection default # @@ -1156,15 +1150,13 @@ rollback to savepoint sv; # Reaping 'drop table t2'... # Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler # lock. +# --> connection con3 +# Check if 'drop table t1' still blocked... # --> connection default +# Demonstrate that the drop will go through as soon as we close +# or will try to access HANDLER handler t1 read a next; -a -3 -handler t1 read a next; -a -4 -# Demonstrate that the drop will go through as soon as we close the -# HANDLER +ERROR 42S02: Table 'test.t1' doesn't exist handler t1 close; # connection con1 # Reaping 'drop table t1'... @@ -1215,15 +1207,13 @@ rollback to savepoint sv; # Reaping 'drop table t2'... # Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler # lock. +# --> connection con3 +# Check if 'drop table t1' is still blocked... # --> connection default +# Demonstrate that the drop will go through as soon as we access or +# close the HANDLER handler t1 read a next; -a -3 -handler t1 read a next; -a -4 -# Demonstrate that the drop will go through as soon as we close the -# HANDLER +ERROR 42S02: Table 'test.t1' doesn't exist handler t1 close; # connection con1 # Reaping 'drop table t1'... @@ -1261,14 +1251,12 @@ drop table t3; # Let DROP TABLE statement sync in. # --> connection con2 # Waiting for 'drop table t3' to get blocked... -# Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler -# lock. +# The fact that DROP TABLE is blocked means that ROLLBACK TO SAVEPOINT +# didn't release the handler lock. # --> connection default +# Drop will go through as soon as we access or close the HANDLER handler t3 read a next; -a -2 -# Demonstrate that the drop will go through as soon as we close the -# HANDLER +ERROR 42S02: Table 'test.t3' doesn't exist handler t3 close; # connection con1 # Reaping 'drop table t3'... diff --git a/sql/lock.cc b/sql/lock.cc index c241e635e6b..ad0aedbee40 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -165,18 +165,12 @@ lock_tables_check(THD *thd, TABLE **tables, uint count, uint flags) write we must own metadata lock of MDL_SHARED_WRITE or stronger type. For table to be locked for read we must own metadata lock of MDL_SHARED_READ or stronger type). - The only exception are HANDLER statements which are allowed to - lock table for read while having only MDL_SHARED lock on it. */ DBUG_ASSERT(t->s->tmp_table || thd->mdl_context.is_lock_owner(MDL_key::TABLE, t->s->db.str, t->s->table_name.str, t->reginfo.lock_type >= TL_WRITE_ALLOW_WRITE ? - MDL_SHARED_WRITE : MDL_SHARED_READ) || - (t->open_by_handler && - thd->mdl_context.is_lock_owner(MDL_key::TABLE, - t->s->db.str, t->s->table_name.str, - MDL_SHARED))); + MDL_SHARED_WRITE : MDL_SHARED_READ)); /* Prevent modifications to base tables if READ_ONLY is activated. diff --git a/sql/sql_class.h b/sql/sql_class.h index 73637f8d8eb..6b64f710176 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1535,6 +1535,41 @@ private: /** + Internal error handler to process an error from MDL_context::upgrade_lock() + and mysql_lock_tables(). Used by implementations of HANDLER READ and + LOCK TABLES LOCAL. +*/ + +class MDL_deadlock_and_lock_abort_error_handler: public Internal_error_handler +{ +public: + /** + Handle an error from MDL_context::upgrade_lock() and mysql_lock_tables(). + Ignore ER_LOCK_ABORTED and ER_LOCK_DEADLOCK errors. + */ + virtual + bool handle_condition(THD *thd, + uint sql_errno, + const char *sqlstate, + Sql_condition::enum_warning_level level, + const char* msg, + Sql_condition **cond_hdl) + { + *cond_hdl= NULL; + if (sql_errno == ER_LOCK_ABORTED || sql_errno == ER_LOCK_DEADLOCK) + m_need_reopen= true; + + return m_need_reopen; + } + + bool need_reopen() const { return m_need_reopen; }; + void init() { m_need_reopen= false; }; +private: + bool m_need_reopen; +}; + + +/** Tables that were locked with LOCK TABLES statement. Encapsulates a list of TABLE_LIST instances for tables diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 7dcc6fa0e95..e117aa593b5 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -488,56 +488,6 @@ bool mysql_ha_close(THD *thd, TABLE_LIST *tables) /** - A helper class to process an error from mysql_lock_tables(). - HANDLER READ statement's attempt to lock the subject table - may get aborted if there is a pending DDL. In that case - we close the table, reopen it, and try to read again. - This is implicit and obscure, since HANDLER position - is lost in the process, but it's the legacy server - behaviour we should preserve. -*/ - -class Sql_handler_lock_error_handler: public Internal_error_handler -{ -public: - virtual - bool handle_condition(THD *thd, - uint sql_errno, - const char *sqlstate, - Sql_condition::enum_warning_level level, - const char* msg, - Sql_condition **cond_hdl); - - bool need_reopen() const { return m_need_reopen; }; - void init() { m_need_reopen= FALSE; }; -private: - bool m_need_reopen; -}; - - -/** - Handle an error from mysql_lock_tables(). - Ignore ER_LOCK_ABORTED errors. -*/ - -bool -Sql_handler_lock_error_handler:: -handle_condition(THD *thd, - uint sql_errno, - const char *sqlstate, - Sql_condition::enum_warning_level level, - const char* msg, - Sql_condition **cond_hdl) -{ - *cond_hdl= NULL; - if (sql_errno == ER_LOCK_ABORTED) - m_need_reopen= TRUE; - - return m_need_reopen; -} - - -/** Finds an open HANDLER table. @params name Name of handler to open @@ -733,7 +683,8 @@ bool mysql_ha_read(THD *thd, TABLE_LIST *tables, int error, keyno; uint num_rows; uchar *UNINIT_VAR(key); - Sql_handler_lock_error_handler sql_handler_lock_error; + MDL_deadlock_and_lock_abort_error_handler sql_handler_lock_error; + MDL_savepoint mdl_savepoint; DBUG_ENTER("mysql_ha_read"); DBUG_PRINT("enter",("'%s'.'%s' as '%s'", tables->db, tables->table_name, tables->alias)); @@ -752,6 +703,48 @@ retry: tables->table= table; // This is used by fix_fields table->pos_in_table_list= tables; + sql_handler_lock_error.init(); + + /* + For non-temporary tables we need to acquire SR lock in order to ensure + that HANDLER READ is blocked by LOCK TABLES WRITE in other connections + for storage engines which don't use THR_LOCK locks (e.g. InnoDB). + + To simplify clean-up code we take MDL_savepoint even for temporary tables. + */ + mdl_savepoint= thd->mdl_context.mdl_savepoint(); + + if (table->s->tmp_table == NO_TMP_TABLE) + { + MDL_request read_request; + + read_request.init(&handler->mdl_request.key, MDL_SHARED_READ, + MDL_TRANSACTION); + + thd->push_internal_handler(&sql_handler_lock_error); + + error= thd->mdl_context.acquire_lock(&read_request, + thd->variables.lock_wait_timeout); + thd->pop_internal_handler(); + + if (sql_handler_lock_error.need_reopen()) + { + /* + HANDLER READ statement's attempt to upgrade lock on the subject table + may get aborted if there is a pending DDL. In that case we close the + table, reopen it, and try to read again. + This is implicit and obscure, since HANDLER position is lost in the + process, but it's the legacy server behaviour we should preserve. + */ + DBUG_ASSERT(error && !thd->is_error()); + mysql_ha_close_table(handler); + goto retry; + } + + if (error) + goto err0; + } + if (handler->lock->lock_count > 0) { int lock_error; @@ -768,7 +761,7 @@ retry: */ thd->set_open_tables(table); - sql_handler_lock_error.init(); + /* Re-use Sql_handler_lock_error instance which was initialized earlier. */ thd->push_internal_handler(&sql_handler_lock_error); lock_error= mysql_lock_tables(thd, handler->lock, @@ -797,6 +790,7 @@ retry: */ DBUG_ASSERT(! thd->transaction_rollback_request); trans_rollback_stmt(thd); + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); mysql_ha_close_table(handler); if (thd->stmt_arena->is_stmt_execute()) { @@ -812,7 +806,10 @@ retry: } if (lock_error) + { + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); goto err0; // mysql_lock_tables() printed error message already + } } if (mysql_ha_fix_cond_and_key(handler, mode, keyname, key_expr, cond, 0)) @@ -957,6 +954,7 @@ ok: */ trans_commit_stmt(thd); mysql_unlock_tables(thd, handler->lock, 0); + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); my_eof(thd); DBUG_PRINT("exit",("OK")); DBUG_RETURN(FALSE); |