diff options
-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); |