diff --git a/mysql-test/r/lock.result b/mysql-test/r/lock.result index a60c5c8383f..676a8c41bb6 100644 --- a/mysql-test/r/lock.result +++ b/mysql-test/r/lock.result @@ -201,3 +201,48 @@ ERROR HY000: Table 't2' was locked with a READ lock and can't be updated UNLOCK TABLES; DROP TABLE t1,t2; End of 5.1 tests. +# +# Ensure that FLUSH TABLES doesn't substitute a base locked table +# with a temporary one. +# +drop table if exists t1, t2; +create table t1 (a int); +create table t2 (a int); +lock table t1 write, t2 write; +create temporary table t1 (a int); +flush table t1; +drop temporary table t1; +select * from t1; +a +unlock tables; +drop table t1, t2; +# +# Ensure that REPAIR .. USE_FRM works under LOCK TABLES. +# +drop table if exists t1, t2; +create table t1 (a int); +create table t2 (a int); +lock table t1 write, t2 write; +repair table t1 use_frm; +Table Op Msg_type Msg_text +test.t1 repair status OK +repair table t1 use_frm; +Table Op Msg_type Msg_text +test.t1 repair status OK +select * from t1; +a +select * from t2; +a +repair table t2 use_frm; +Table Op Msg_type Msg_text +test.t2 repair status OK +repair table t2 use_frm; +Table Op Msg_type Msg_text +test.t2 repair status OK +select * from t1; +a +unlock tables; +drop table t1, t2; +# +# End of 6.0 tests. +# diff --git a/mysql-test/r/trigger-compat.result b/mysql-test/r/trigger-compat.result index 14ef4f54f27..14949c227bd 100644 --- a/mysql-test/r/trigger-compat.result +++ b/mysql-test/r/trigger-compat.result @@ -34,8 +34,6 @@ TRIGGER_CATALOG TRIGGER_SCHEMA TRIGGER_NAME EVENT_MANIPULATION EVENT_OBJECT_CATA def mysqltest_db1 wl2818_trg1 INSERT def mysqltest_db1 t1 0 NULL INSERT INTO t2 VALUES(CURRENT_USER()) ROW BEFORE NULL NULL OLD NEW NULL latin1 latin1_swedish_ci latin1_swedish_ci def mysqltest_db1 wl2818_trg2 INSERT def mysqltest_db1 t1 0 NULL INSERT INTO t2 VALUES(CURRENT_USER()) ROW AFTER NULL NULL OLD NEW NULL mysqltest_dfn@localhost latin1 latin1_swedish_ci latin1_swedish_ci DROP TRIGGER wl2818_trg1; -Warnings: -Warning 1454 No definer attribute for trigger 'mysqltest_db1'.'wl2818_trg1'. The trigger will be activated under the authorization of the invoker, which may have insufficient privileges. Please recreate the trigger. DROP TRIGGER wl2818_trg2; use mysqltest_db1; DROP TABLE t1; diff --git a/mysql-test/suite/rpl/r/rpl_trigger.result b/mysql-test/suite/rpl/r/rpl_trigger.result index 3d7757613a7..01d886c4709 100644 --- a/mysql-test/suite/rpl/r/rpl_trigger.result +++ b/mysql-test/suite/rpl/r/rpl_trigger.result @@ -893,8 +893,6 @@ s @ root@localhost DROP TRIGGER trg1; -Warnings: -Warning 1454 No definer attribute for trigger 'test'.'trg1'. The trigger will be activated under the authorization of the invoker, which may have insufficient privileges. Please recreate the trigger. DROP TABLE t1; DROP TABLE t2; STOP SLAVE; diff --git a/mysql-test/t/lock.test b/mysql-test/t/lock.test index 856ae020492..7effaaeb20d 100644 --- a/mysql-test/t/lock.test +++ b/mysql-test/t/lock.test @@ -252,3 +252,46 @@ UNLOCK TABLES; DROP TABLE t1,t2; --echo End of 5.1 tests. + +--echo # +--echo # Ensure that FLUSH TABLES doesn't substitute a base locked table +--echo # with a temporary one. +--echo # + +--disable_warnings +drop table if exists t1, t2; +--enable_warnings +create table t1 (a int); +create table t2 (a int); +lock table t1 write, t2 write; +create temporary table t1 (a int); +flush table t1; +drop temporary table t1; +select * from t1; +unlock tables; +drop table t1, t2; + +--echo # +--echo # Ensure that REPAIR .. USE_FRM works under LOCK TABLES. +--echo # + +--disable_warnings +drop table if exists t1, t2; +--enable_warnings +create table t1 (a int); +create table t2 (a int); +lock table t1 write, t2 write; +repair table t1 use_frm; +repair table t1 use_frm; +select * from t1; +select * from t2; +repair table t2 use_frm; +repair table t2 use_frm; +select * from t1; +unlock tables; +drop table t1, t2; + + +--echo # +--echo # End of 6.0 tests. +--echo # diff --git a/sql/field.h b/sql/field.h index fb6ca34e88e..3dd18b4ffaa 100644 --- a/sql/field.h +++ b/sql/field.h @@ -478,7 +478,6 @@ public: } /* Hash value */ virtual void hash(ulong *nr, ulong *nr2); - friend bool reopen_table(THD *,struct st_table *,bool); friend int cre_myisam(char * name, register TABLE *form, uint options, ulonglong auto_increment_value); friend class Copy_field; diff --git a/sql/lock.cc b/sql/lock.cc index 33c9edcea48..814eebde337 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -512,28 +512,15 @@ void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock) /** Try to find the table in the list of locked tables. In case of success, unlock the table and remove it from this list. - - @note This function has a legacy side effect: the table is - unlocked even if it is not found in the locked list. - It's not clear if this side effect is intentional or still - desirable. It might lead to unmatched calls to - unlock_external(). Moreover, a discrepancy can be left - unnoticed by the storage engine, because in - unlock_external() we call handler::external_lock(F_UNLCK) only - if table->current_lock is not F_UNLCK. + If a table has more than one lock instance, removes them all. @param thd thread context @param locked list of locked tables @param table the table to unlock - @param always_unlock specify explicitly if the legacy side - effect is desired. */ -void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table, - bool always_unlock) +void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table) { - if (always_unlock == TRUE) - mysql_unlock_some_tables(thd, &table, /* table count */ 1); if (locked) { reg1 uint i; @@ -547,9 +534,8 @@ void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table, DBUG_ASSERT(table->lock_position == i); - /* Unlock if not yet unlocked */ - if (always_unlock == FALSE) - mysql_unlock_some_tables(thd, &table, /* table count */ 1); + /* Unlock the table. */ + mysql_unlock_some_tables(thd, &table, /* table count */ 1); /* Decrement table_count in advance, making below expressions easier */ old_tables= --locked->table_count; diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index b650af8017d..12d7e46e821 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1225,20 +1225,13 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT* mem, bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias, char *cache_key, uint cache_key_length, MEM_ROOT *mem_root, uint flags); -bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list); TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name); TABLE *find_write_locked_table(TABLE *list, const char *db, const char *table_name); void detach_merge_children(TABLE *table, bool clear_refs); bool fix_merge_after_open(TABLE_LIST *old_child_list, TABLE_LIST **old_last, TABLE_LIST *new_child_list, TABLE_LIST **new_last); -bool reopen_table(TABLE *table); -bool reopen_tables(THD *thd, bool get_locks); thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table); -void close_data_files_and_leave_as_placeholders(THD *thd, const char *db, - const char *table_name); -void close_handle_and_leave_table_as_placeholder(TABLE *table); -void unlock_locked_tables(THD *thd); void execute_init_command(THD *thd, sys_var_str *init_command_var, rw_lock_t *var_mutex); extern Field *not_found_field; @@ -1388,12 +1381,12 @@ void add_join_on(TABLE_LIST *b,Item *expr); void add_join_natural(TABLE_LIST *a,TABLE_LIST *b,List *using_fields, SELECT_LEX *lex); bool add_proc_to_list(THD *thd, Item *item); -bool close_cached_table(THD *thd, TABLE *table); bool wait_while_table_is_used(THD *thd, TABLE *table, enum ha_extra_function function); -void unlink_open_table(THD *thd, TABLE *find, bool unlock); void drop_open_table(THD *thd, TABLE *table, const char *db_name, const char *table_name); +void close_all_tables_for_name(THD *thd, TABLE_SHARE *share, + bool remove_from_locked_tables); void update_non_unique_table_error(TABLE_LIST *update, const char *operation, TABLE_LIST *duplicate); @@ -1515,7 +1508,6 @@ void close_temporary_table(THD *thd, TABLE *table, bool free_share, void close_temporary(TABLE *table, bool free_share, bool delete_table); bool rename_temporary_table(THD* thd, TABLE *table, const char *new_db, const char *table_name); -void remove_db_from_cache(const char *db); void flush_tables(); bool is_equal(const LEX_STRING *a, const LEX_STRING *b); char *make_default_log_name(char *buff,const char* log_ext); @@ -1561,7 +1553,7 @@ void create_subpartition_name(char *out, const char *in1, typedef struct st_lock_param_type { - TABLE_LIST table_list; + TABLE_LIST *table_list; ulonglong copied; ulonglong deleted; THD *thd; @@ -1572,7 +1564,6 @@ typedef struct st_lock_param_type const char *db; const char *table_name; uchar *pack_frm_data; - enum thr_lock_type old_lock_type; uint key_count; uint db_options; size_t pack_frm_len; @@ -2044,12 +2035,31 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count, #define MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY 0x0008 #define MYSQL_LOCK_PERF_SCHEMA 0x0010 #define MYSQL_OPEN_TAKE_UPGRADABLE_MDL 0x0020 +/** + Do not try to acquire a metadata lock on the table: we + already have one. +*/ +#define MYSQL_OPEN_HAS_MDL_LOCK 0x0040 +/** + If in locked tables mode, ignore the locked tables and get + a new instance of the table. +*/ +#define MYSQL_OPEN_GET_NEW_TABLE 0x0080 +/** Don't look up the table in the list of temporary tables. */ +#define MYSQL_OPEN_SKIP_TEMPORARY 0x0100 + +/** Please refer to the internals manual. */ +#define MYSQL_OPEN_REOPEN (MYSQL_LOCK_IGNORE_FLUSH |\ + MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK |\ + MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY |\ + MYSQL_OPEN_GET_NEW_TABLE |\ + MYSQL_OPEN_SKIP_TEMPORARY |\ + MYSQL_OPEN_HAS_MDL_LOCK) void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock); void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock); void mysql_unlock_some_tables(THD *thd, TABLE **table,uint count); -void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table, - bool always_unlock); +void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table); void mysql_lock_abort(THD *thd, TABLE *table, bool upgrade_lock); void mysql_lock_downgrade_write(THD *thd, TABLE *table, thr_lock_type new_lock_type); diff --git a/sql/sp_head.cc b/sql/sp_head.cc index d905ddcda31..1d953e773b3 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -3985,8 +3985,8 @@ sp_head::add_used_tables_to_table_list(THD *thd, table->belong_to_view= belong_to_view; table->trg_event_map= stab->trg_event_map; table->mdl_lock_data= mdl_alloc_lock(0, table->db, table->table_name, - thd->mdl_el_root ? - thd->mdl_el_root : + thd->locked_tables_root ? + thd->locked_tables_root : thd->mem_root); /* Everyting else should be zeroed */ @@ -4030,8 +4030,9 @@ sp_add_to_query_tables(THD *thd, LEX *lex, table->select_lex= lex->current_select; table->cacheable_table= 1; table->mdl_lock_data= mdl_alloc_lock(0, table->db, table->table_name, - thd->mdl_el_root ? thd->mdl_el_root : - thd->mem_root); + thd->locked_tables_root ? + thd->locked_tables_root : thd->mem_root); + lex->add_to_query_tables(table); return table; } diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index b01e7b1049d..aa2c697f221 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -676,7 +676,8 @@ my_bool acl_reload(THD *thd) my_bool return_val= 1; DBUG_ENTER("acl_reload"); - unlock_locked_tables(thd); // Can't have locked tables here + /* Can't have locked tables here. */ + thd->locked_tables_list.unlock_locked_tables(thd); /* To avoid deadlocks we should obtain table locks before diff --git a/sql/sql_base.cc b/sql/sql_base.cc index f7ac1df8b32..61c071d3430 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -116,9 +116,6 @@ static bool table_def_inited= 0; static bool check_and_update_table_version(THD *thd, TABLE_LIST *tables, TABLE_SHARE *table_share); -static bool reopen_table_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list, - const char *alias, char *cache_key, - uint cache_key_length); static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry); static bool auto_repair_table(THD *thd, TABLE_LIST *table_list); static void free_cache_entry(TABLE *entry); @@ -405,20 +402,6 @@ static void table_def_unuse_table(TABLE *table) } -/** - Bind used TABLE instance to another table share. - - @note Will go away once we refactor code responsible - for reopening tables under lock tables. -*/ - -static void table_def_change_share(TABLE *table, TABLE_SHARE *new_share) -{ - table->s->used_tables.remove(table); - new_share->used_tables.push_front(table); -} - - /* Get TABLE_SHARE for a table. @@ -726,65 +709,6 @@ static void reference_table_share(TABLE_SHARE *share) } -/** - Close file handle, but leave the table in THD::open_tables list - to allow its future reopening. - - @param table Table handler - - @note THD::killed will be set if we run out of memory - - @note If closing a MERGE child, the calling function has to - take care for closing the parent too, if necessary. - - @todo Get rid of this function once we refactor LOCK TABLES - to keep around TABLE_LIST elements used for opening - of tables until UNLOCK TABLES. -*/ - -void close_handle_and_leave_table_as_placeholder(TABLE *table) -{ - TABLE_SHARE *share, *old_share= table->s; - char *key_buff; - MEM_ROOT *mem_root= &table->mem_root; - DBUG_ENTER("close_handle_and_leave_table_as_lock"); - - DBUG_ASSERT(table->db_stat); - - /* - Make a local copy of the table share and free the current one. - This has to be done to ensure that the table share is removed from - the table defintion cache as soon as the last instance is removed - */ - if (multi_alloc_root(mem_root, - &share, sizeof(*share), - &key_buff, old_share->table_cache_key.length, - NULL)) - { - bzero((char*) share, sizeof(*share)); - share->set_table_cache_key(key_buff, old_share->table_cache_key.str, - old_share->table_cache_key.length); - share->tmp_table= INTERNAL_TMP_TABLE; // for intern_close_table() - } - - /* - When closing a MERGE parent or child table, detach the children first. - Do not clear child table references to allow for reopen. - */ - if (table->child_l || table->parent) - detach_merge_children(table, FALSE); - table->file->close(); - table->db_stat= 0; // Mark file closed - table_def_change_share(table, share); - release_table_share(table->s); - table->s= share; - table->file->change_table_ptr(table, table->s); - - DBUG_VOID_RETURN; -} - - - /* Create a list for all open tables matching SQL expression @@ -1030,64 +954,31 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, if (thd->locked_tables_mode) { /* - If we are under LOCK TABLES we need to reopen tables without + If we are under LOCK TABLES, we need to reopen the tables without opening a door for any concurrent threads to sneak in and get lock on our tables. To achieve this we use exclusive metadata locks. */ - if (!tables) + TABLE_LIST *tables_to_reopen= (tables ? tables : + thd->locked_tables_list.locked_tables()); + + for (TABLE_LIST *table_list= tables_to_reopen; table_list; + table_list= table_list->next_global) { - for (TABLE *tab= thd->open_tables; tab; tab= tab->next) + /* A check that the table was locked for write is done by the caller. */ + TABLE *table= find_locked_table(thd->open_tables, table_list->db, + table_list->table_name); + + /* May return NULL if this table has already been closed via an alias. */ + if (! table) + continue; + + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) { - /* - Checking TABLE::db_stat is essential in case when we have - several instances of the table open and locked. - */ - if (tab->db_stat) - { - char dbname[NAME_LEN+1]; - char tname[NAME_LEN+1]; - /* - Since close_data_files_and_leave_as_placeholders() frees share's - memroot we need to make copies of database and table names. - */ - strmov(dbname, tab->s->db.str); - strmov(tname, tab->s->table_name.str); - if (wait_while_table_is_used(thd, tab, HA_EXTRA_FORCE_REOPEN)) - { - result= TRUE; - goto err_with_reopen; - } - pthread_mutex_lock(&LOCK_open); - close_data_files_and_leave_as_placeholders(thd, dbname, tname); - pthread_mutex_unlock(&LOCK_open); - } - } - } - else - { - for (TABLE_LIST *table= tables; table; table= table->next_local) - { - /* This should always succeed thanks to check in caller. */ - TABLE *tab= find_write_locked_table(thd->open_tables, table->db, - table->table_name); - /* - Checking TABLE::db_stat is essential in case when we have - several instances of the table open and locked. - */ - if (tab->db_stat) - { - if (wait_while_table_is_used(thd, tab, HA_EXTRA_FORCE_REOPEN)) - { - result= TRUE; - goto err_with_reopen; - } - pthread_mutex_lock(&LOCK_open); - close_data_files_and_leave_as_placeholders(thd, table->db, - table->table_name); - pthread_mutex_unlock(&LOCK_open); - } + result= TRUE; + goto err_with_reopen; } + close_all_tables_for_name(thd, table->s, FALSE); } } @@ -1145,16 +1036,12 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, err_with_reopen: if (thd->locked_tables_mode) { - pthread_mutex_lock(&LOCK_open); /* No other thread has the locked tables open; reopen them and get the old locks. This should always succeed (unless some external process has removed the tables) */ - thd->in_lock_tables=1; - result|= reopen_tables(thd, 1); - thd->in_lock_tables=0; - pthread_mutex_unlock(&LOCK_open); + thd->locked_tables_list.reopen_tables(thd); /* Since mdl_downgrade_exclusive_lock() won't do anything with shared metadata lock it is much simplier to go through all open tables rather @@ -1311,6 +1198,8 @@ static void mark_used_tables_as_free_for_reuse(THD *thd, TABLE *table) { for (; table ; table= table->next) { + DBUG_ASSERT(table->pos_in_locked_tables == NULL || + table->pos_in_locked_tables->table == table); if (table->query_id == thd->query_id) { table->query_id= 0; @@ -1355,6 +1244,75 @@ static void close_open_tables(THD *thd) } +/** + Close all open instances of the table but keep the MDL lock, + if any. + + Works both under LOCK TABLES and in the normal mode. + Removes all closed instances of the table from the table cache. + + @param thd thread handle + @param[in] share table share, but is just a handy way to + access the table cache key + + @param[in] remove_from_locked_tables + TRUE if the table is being dropped or renamed. + In that case the documented behaviour is to + implicitly remove the table from LOCK TABLES + list. +*/ + +void +close_all_tables_for_name(THD *thd, TABLE_SHARE *share, + bool remove_from_locked_tables) +{ + char key[MAX_DBKEY_LENGTH]; + uint key_length= share->table_cache_key.length; + + memcpy(key, share->table_cache_key.str, key_length); + + safe_mutex_assert_not_owner(&LOCK_open); + /* + We need to hold LOCK_open while changing the open_tables + list, since another thread may work on it. + @sa notify_thread_having_shared_lock() + */ + pthread_mutex_lock(&LOCK_open); + + for (TABLE **prev= &thd->open_tables; *prev; ) + { + TABLE *table= *prev; + + if (table->s->table_cache_key.length == key_length && + !memcmp(table->s->table_cache_key.str, key, key_length)) + { + /* + Does nothing if the table is not locked. + This allows one to use this function after a table + has been unlocked, e.g. in partition management. + */ + mysql_lock_remove(thd, thd->lock, table); + + thd->locked_tables_list.unlink_from_list(thd, + table->pos_in_locked_tables, + remove_from_locked_tables); + + /* Make sure the table is removed from the cache */ + table->s->version= 0; + close_thread_table(thd, prev); + } + else + { + /* Step to next entry in open_tables list. */ + prev= &table->next; + } + } + /* We have been removing tables from the table cache. */ + broadcast_refresh(); + pthread_mutex_unlock(&LOCK_open); +} + + /* Close all tables used by the current substatement, or all tables used by this thread if we are on the upper level. @@ -1995,7 +1953,7 @@ int drop_temporary_table(THD *thd, TABLE_LIST *table_list) If LOCK TABLES list is not empty and contains this table, unlock the table and remove the table from this list. */ - mysql_lock_remove(thd, thd->lock, table, FALSE); + mysql_lock_remove(thd, thd->lock, table); close_temporary_table(thd, table, 1, 1); DBUG_RETURN(0); } @@ -2111,77 +2069,6 @@ bool rename_temporary_table(THD* thd, TABLE *table, const char *db, } -/** - Prepare an open merge table for close. - - @param[in] thd thread context - @param[in] table table to prepare - @param[in,out] prev_pp pointer to pointer of previous table - - @detail - If the table is a MERGE parent, just detach the children. - If the table is a MERGE child, close the parent (incl. detach). -*/ - -static void unlink_open_merge(THD *thd, TABLE *table, TABLE ***prev_pp) -{ - DBUG_ENTER("unlink_open_merge"); - - if (table->parent) - { - /* - If MERGE child, close parent too. Closing includes detaching. - - This is used for example in ALTER TABLE t1 RENAME TO t5 under - LOCK TABLES where t1 is a MERGE child: - CREATE TABLE t1 (c1 INT); - CREATE TABLE t2 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1); - LOCK TABLES t1 WRITE, t2 WRITE; - ALTER TABLE t1 RENAME TO t5; - */ - TABLE *parent= table->parent; - TABLE **prv_p; - - /* Find parent in open_tables list. */ - for (prv_p= &thd->open_tables; - *prv_p && (*prv_p != parent); - prv_p= &(*prv_p)->next) {} - if (*prv_p) - { - /* Special treatment required if child follows parent in list. */ - if (*prev_pp == &parent->next) - *prev_pp= prv_p; - /* - Remove parent from open_tables list and close it. - This includes detaching and hence clearing parent references. - */ - close_thread_table(thd, prv_p); - } - } - else if (table->child_l) - { - /* - When closing a MERGE parent, detach the children first. It is - not necessary to clear the child or parent table reference of - this table because the TABLE is freed. But we need to clear - the child or parent references of the other belonging tables - so that they cannot be moved into the unused_tables chain with - these pointers set. - - This is used for example in ALTER TABLE t2 RENAME TO t5 under - LOCK TABLES where t2 is a MERGE parent: - CREATE TABLE t1 (c1 INT); - CREATE TABLE t2 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1); - LOCK TABLES t1 WRITE, t2 WRITE; - ALTER TABLE t2 RENAME TO t5; - */ - detach_merge_children(table, TRUE); - } - - DBUG_VOID_RETURN; -} - - /** Force all other threads to stop using the table by upgrading metadata lock on it and remove unused TABLE instances from cache. @@ -2230,119 +2117,22 @@ bool wait_while_table_is_used(THD *thd, TABLE *table, /** - Upgrade metadata lock on the table and close all its instances. + Close a and drop a just created table in CREATE TABLE ... SELECT. - @param thd Thread handler - @param table Table to remove from cache + @param thd Thread handle + @param table TABLE object for the table to be dropped + @param db_name Name of database for this table + @param table_name Name of this table - @retval FALSE Success. - @retval TRUE Failure (e.g. because thread was killed). -*/ - -bool close_cached_table(THD *thd, TABLE *table) -{ - DBUG_ENTER("close_cached_table"); - - /* FIXME: check if we pass proper parameters everywhere. */ - if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) - DBUG_RETURN(TRUE); - - /* Close lock if this is not got with LOCK TABLES */ - if (! thd->locked_tables_mode) - { - mysql_unlock_tables(thd, thd->lock); - thd->lock=0; // Start locked threads - } - - pthread_mutex_lock(&LOCK_open); - /* Close all copies of 'table'. This also frees all LOCK TABLES lock */ - unlink_open_table(thd, table, TRUE); - pthread_mutex_unlock(&LOCK_open); - DBUG_RETURN(FALSE); -} - - -/** - Remove all instances of table from thread's open list and - table cache. - - @param thd Thread context - @param find Table to remove - @param unlock TRUE - free all locks on tables removed that are - done with LOCK TABLES - FALSE - otherwise - - @note When unlock parameter is FALSE or current thread doesn't have - any tables locked with LOCK TABLES, tables are assumed to be - not locked (for example already unlocked). -*/ - -void unlink_open_table(THD *thd, TABLE *find, bool unlock) -{ - char key[MAX_DBKEY_LENGTH]; - uint key_length= find->s->table_cache_key.length; - TABLE *list, **prev; - DBUG_ENTER("unlink_open_table"); - - safe_mutex_assert_owner(&LOCK_open); - - memcpy(key, find->s->table_cache_key.str, key_length); - /* - Note that we need to hold LOCK_open while changing the - open_tables list. Another thread may work on it. - (See: notify_thread_having_shared_lock()) - Closing a MERGE child before the parent would be fatal if the - other thread tries to abort the MERGE lock in between. - */ - for (prev= &thd->open_tables; *prev; ) - { - list= *prev; - - if (list->s->table_cache_key.length == key_length && - !memcmp(list->s->table_cache_key.str, key, key_length)) - { - if (unlock && thd->locked_tables_mode) - mysql_lock_remove(thd, thd->lock, - list->parent ? list->parent : list, TRUE); - - /* Prepare MERGE table for close. Close parent if necessary. */ - unlink_open_merge(thd, list, &prev); - - /* Remove table from open_tables list. */ - *prev= list->next; - /* Close table. */ - free_cache_entry(list); - } - else - { - /* Step to next entry in open_tables list. */ - prev= &list->next; - } - } - - // Notify any 'refresh' threads - broadcast_refresh(); - DBUG_VOID_RETURN; -} - - -/** - Auxiliary routine which closes and drops open table. - - @param thd Thread handle - @param table TABLE object for table to be dropped - @param db_name Name of database for this table - @param table_name Name of this table - - @note This routine assumes that table to be closed is open only - by calling thread so we needn't wait until other threads - will close the table. Also unless called under implicit or - explicit LOCK TABLES mode it assumes that table to be - dropped is already unlocked. In the former case it will - also remove lock on the table. But one should not rely on - this behaviour as it may change in future. - Currently, however, this function is never called for a - table that was locked with LOCK TABLES. + This routine assumes that the table to be closed is open only + by the calling thread, so we needn't wait until other threads + close the table. It also assumes that the table is first + in thd->open_ables and a data lock on it, if any, has been + released. To sum up, it's tuned to work with + CREATE TABLE ... SELECT and CREATE TABLE .. SELECT only. + Note, that currently CREATE TABLE ... SELECT is not supported + under LOCK TABLES. This function, still, can be called in + prelocked mode, e.g. if we do CREATE TABLE .. SELECT f1(); */ void drop_open_table(THD *thd, TABLE *table, const char *db_name, @@ -2352,13 +2142,14 @@ void drop_open_table(THD *thd, TABLE *table, const char *db_name, close_temporary_table(thd, table, 1, 1); else { + DBUG_ASSERT(table == thd->open_tables); + handlerton *table_type= table->s->db_type(); + /* Ensure the table is removed from the cache. */ + table->s->version= 0; + pthread_mutex_lock(&LOCK_open); - /* - unlink_open_table() also tells threads waiting for refresh or close - that something has happened. - */ - unlink_open_table(thd, table, FALSE); + close_thread_table(thd, &thd->open_tables); quick_rm_table(table_type, db_name, table_name, 0); pthread_mutex_unlock(&LOCK_open); } @@ -2409,73 +2200,6 @@ void wait_for_condition(THD *thd, pthread_mutex_t *mutex, pthread_cond_t *cond) } -/* - Open table for which this thread has exclusive meta-data lock. - - SYNOPSIS - reopen_name_locked_table() - thd Thread handle - table_list TABLE_LIST object for table to be open. - - NOTE - This function assumes that its caller already acquired LOCK_open mutex. - - RETURN VALUE - FALSE - Success - TRUE - Error -*/ - -bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list) -{ - TABLE *table; - TABLE_SHARE *share; - char key[MAX_DBKEY_LENGTH]; - uint key_length; - char *table_name= table_list->table_name; - DBUG_ENTER("reopen_name_locked_table"); - - if (thd->killed) - DBUG_RETURN(TRUE); - - key_length= create_table_def_key(thd, key, table_list, 0); - - if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME)))) - DBUG_RETURN(TRUE); - - if (reopen_table_entry(thd, table, table_list, table_name, key, key_length)) - { - my_free((uchar*)table, MYF(0)); - DBUG_RETURN(TRUE); - } - - share= table->s; - /* - We want to prevent other connections from opening this table until end - of statement as it is likely that modifications of table's metadata are - not yet finished (for example CREATE TRIGGER have to change .TRG file, - or we might want to drop table if CREATE TABLE ... SELECT fails). - This also allows us to assume that no other connection will sneak in - before we will get table-level lock on this table. - */ - share->version=0; - table->in_use = thd; - - table_def_add_used_table(thd, table); - - table->next= thd->open_tables; - thd->open_tables= table; - - table->tablenr=thd->current_tablenr++; - table->used_fields=0; - table->const_table=0; - table->null_row= table->maybe_null= 0; - table->force_index= table->force_index_order= table->force_index_group= 0; - table->status=STATUS_NO_RECORD; - table_list->table= table; - DBUG_RETURN(FALSE); -} - - /** Check that table exists in table definition cache, on disk or in some storage engine. @@ -2554,6 +2278,62 @@ void table_share_release_hook(void *share) } +/* + A helper function that acquires an MDL lock for a table + being opened. +*/ + +static bool +open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, + MDL_LOCK_DATA *mdl_lock_data, + uint flags, + enum_open_table_action *action) +{ + mdl_add_lock(&thd->mdl_context, mdl_lock_data); + + if (table_list->open_type) + { + /* + In case of CREATE TABLE .. If NOT EXISTS .. SELECT, the table + may not yet exist. Let's acquire an exclusive lock for that + case. If later it turns out the table existsed, we will + downgrade the lock to shared. Note that, according to the + locking protocol, all exclusive locks must be acquired before + shared locks. This invariant is preserved here and is also + enforced by asserts in metadata locking subsystem. + */ + mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE); + if (mdl_acquire_exclusive_locks(&thd->mdl_context)) + return 1; + } + else + { + bool retry; + + /* + There is no MDL_SHARED_UPGRADABLE_HIGH_PRIO type of metadata lock so we + want to be sure that caller doesn't pass us both flags simultaneously. + */ + DBUG_ASSERT(!(flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL) || + !(flags & MYSQL_LOCK_IGNORE_FLUSH)); + + if (flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL && + table_list->lock_type >= TL_WRITE_ALLOW_WRITE) + mdl_set_lock_type(mdl_lock_data, MDL_SHARED_UPGRADABLE); + if (flags & MYSQL_LOCK_IGNORE_FLUSH) + mdl_set_lock_type(mdl_lock_data, MDL_SHARED_HIGH_PRIO); + + if (mdl_acquire_shared_lock(&thd->mdl_context, mdl_lock_data, &retry)) + { + if (retry) + *action= OT_BACK_OFF_AND_RETRY; + return 1; + } + } + return 0; +} + + /* Open a table. @@ -2628,7 +2408,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, same name. This block implements the behaviour. TODO: move this block into a separate function. */ - if (!table_list->skip_temporary) + if (!table_list->skip_temporary && ! (flags & MYSQL_OPEN_SKIP_TEMPORARY)) { for (table= thd->temporary_tables; table ; table=table->next) { @@ -2673,7 +2453,8 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, open not pre-opened tables in pre-locked/LOCK TABLES mode. TODO: move this block into a separate function. */ - if (thd->locked_tables_mode) + if (thd->locked_tables_mode && + ! (flags & MYSQL_OPEN_GET_NEW_TABLE)) { // Using table locks TABLE *best_table= 0; int best_distance= INT_MIN; @@ -2785,47 +2566,12 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, */ mdl_lock_data= table_list->mdl_lock_data; - mdl_add_lock(&thd->mdl_context, mdl_lock_data); - - if (table_list->open_type) + if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK)) { - /* - In case of CREATE TABLE .. If NOT EXISTS .. SELECT, the table - may not yet exist. Let's acquire an exclusive lock for that - case. If later it turns out the table existsed, we will - downgrade the lock to shared. Note that, according to the - locking protocol, all exclusive locks must be acquired before - shared locks. This invariant is preserved here and is also - enforced by asserts in metadata locking subsystem. - */ - mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE); - if (mdl_acquire_exclusive_locks(&thd->mdl_context)) + if (open_table_get_mdl_lock(thd, table_list, mdl_lock_data, flags, + action)) DBUG_RETURN(TRUE); } - else - { - bool retry; - - /* - There is no MDL_SHARED_UPGRADABLE_HIGH_PRIO type of metadata lock so we - want to be sure that caller doesn't pass us both flags simultaneously. - */ - DBUG_ASSERT(!(flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL) || - !(flags & MYSQL_LOCK_IGNORE_FLUSH)); - - if (flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL && - table_list->lock_type >= TL_WRITE_ALLOW_WRITE) - mdl_set_lock_type(mdl_lock_data, MDL_SHARED_UPGRADABLE); - if (flags & MYSQL_LOCK_IGNORE_FLUSH) - mdl_set_lock_type(mdl_lock_data, MDL_SHARED_HIGH_PRIO); - - if (mdl_acquire_shared_lock(&thd->mdl_context, mdl_lock_data, &retry)) - { - if (retry) - *action= OT_BACK_OFF_AND_RETRY; - DBUG_RETURN(TRUE); - } - } pthread_mutex_lock(&LOCK_open); @@ -3061,7 +2807,6 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, MYF(MY_WME)); memcpy((char*) table->alias, alias, length); } - /* These variables are also set in reopen_table() */ table->tablenr=thd->current_tablenr++; table->used_fields=0; table->const_table=0; @@ -3153,427 +2898,264 @@ TABLE *find_write_locked_table(TABLE *list, const char *db, const char *table_na } -/* - Reopen an table because the definition has changed. - - SYNOPSIS - reopen_table() - table Table object - - NOTES - The data file for the table is already closed and the share is released - The table has a 'dummy' share that mainly contains database and table name. - - RETURN - 0 ok - 1 error. The old table object is not changed. -*/ - -bool reopen_table(TABLE *table) -{ - TABLE tmp; - bool error= 1; - Field **field; - uint key,part; - TABLE_LIST table_list; - THD *thd= table->in_use; - DBUG_ENTER("reopen_table"); - DBUG_PRINT("tcache", ("table: '%s'.'%s' 0x%lx", table->s->db.str, - table->s->table_name.str, (long) table)); - - DBUG_ASSERT(table->s->ref_count == 0); - DBUG_ASSERT(!table->sort.io_cache); - DBUG_ASSERT(!table->children_attached); - -#ifdef EXTRA_DEBUG - if (table->db_stat) - sql_print_error("Table %s had a open data handler in reopen_table", - table->alias); -#endif - bzero((char*) &table_list, sizeof(TABLE_LIST)); - table_list.db= table->s->db.str; - table_list.table_name= table->s->table_name.str; - table_list.table= table; - - if (reopen_table_entry(thd, &tmp, &table_list, - table->alias, - table->s->table_cache_key.str, - table->s->table_cache_key.length)) - goto end; - - /* This list copies variables set by open_table */ - tmp.tablenr= table->tablenr; - tmp.used_fields= table->used_fields; - tmp.const_table= table->const_table; - tmp.null_row= table->null_row; - tmp.maybe_null= table->maybe_null; - tmp.status= table->status; - - tmp.s->table_map_id= table->s->table_map_id; - - /* Get state */ - tmp.in_use= thd; - tmp.reginfo.lock_type=table->reginfo.lock_type; - tmp.grant= table->grant; - - /* Replace table in open list */ - tmp.next= table->next; - tmp.prev= table->prev; - - /* Preserve MERGE parent. */ - tmp.parent= table->parent; - /* Fix MERGE child list and check for unchanged union. */ - if ((table->child_l || tmp.child_l) && - fix_merge_after_open(table->child_l, table->child_last_l, - tmp.child_l, tmp.child_last_l)) - { - (void) closefrm(&tmp, 1); // close file, free everything - goto end; - } - tmp.mdl_lock_data= table->mdl_lock_data; - - table_def_change_share(table, tmp.s); - /* Avoid wiping out TABLE's position in new share's used tables list. */ - tmp.share_next= table->share_next; - tmp.share_prev= table->share_prev; - - delete table->triggers; - if (table->file) - (void) closefrm(table, 1); // close file, free everything - - *table= tmp; - table->default_column_bitmaps(); - table->file->change_table_ptr(table, table->s); - - DBUG_ASSERT(table->alias != 0); - for (field=table->field ; *field ; field++) - { - (*field)->table= (*field)->orig_table= table; - (*field)->table_name= &table->alias; - } - for (key=0 ; key < table->s->keys ; key++) - { - for (part=0 ; part < table->key_info[key].usable_key_parts ; part++) - { - table->key_info[key].key_part[part].field->table= table; - table->key_info[key].key_part[part].field->orig_table= table; - } - } - if (table->triggers) - table->triggers->set_table(table); - /* - Do not attach MERGE children here. The children might be reopened - after the parent. Attach children after reopening all tables that - require reopen. See for example reopen_tables(). - */ - - broadcast_refresh(); - error=0; - - end: - DBUG_RETURN(error); -} - +/*********************************************************************** + class Locked_tables_list implementation. Declared in sql_class.h +************************************************************************/ /** - Close all instances of a table open by this thread and replace - them with placeholder in THD::open_tables list for future reopening. + Enter LTM_LOCK_TABLES mode. - @param thd Thread context - @param db Database name for the table to be closed - @param table_name Name of the table to be closed + Enter the LOCK TABLES mode using all the tables that are + currently open and locked in this connection. + Initializes a TABLE_LIST instance for every locked table. - @note This function assumes that if we are not under LOCK TABLES, - then there is only one table open and locked. This means that - the function probably has to be adjusted before it can be used - anywhere outside ALTER TABLE. + @param thd thread handle - @note Must not use TABLE_SHARE::table_name/db of the table being closed, - the strings are used in a loop even after the share may be freed. + @return TRUE if out of memory. */ -void close_data_files_and_leave_as_placeholders(THD *thd, const char *db, - const char *table_name) +bool +Locked_tables_list::init_locked_tables(THD *thd) { - TABLE *table; - DBUG_ENTER("close_data_files_and_leave_as_placeholders"); + DBUG_ASSERT(thd->locked_tables_mode == LTM_NONE); + DBUG_ASSERT(m_locked_tables == NULL); - safe_mutex_assert_owner(&LOCK_open); - - if (! thd->locked_tables_mode) + for (TABLE *table= thd->open_tables; table; table= table->next) { - /* - If we are not under LOCK TABLES we should have only one table - open and locked so it makes sense to remove the lock at once. - */ - mysql_unlock_tables(thd, thd->lock); - thd->lock= 0; - } + TABLE_LIST *src_table_list= table->pos_in_table_list; + char *db, *table_name, *alias; + size_t db_len= strlen(src_table_list->db) + 1; + size_t table_name_len= strlen(src_table_list->table_name) + 1; + size_t alias_len= strlen(src_table_list->alias) + 1; + TABLE_LIST *dst_table_list; - for (table=thd->open_tables; table ; table=table->next) - { - if (!strcmp(table->s->table_name.str, table_name) && - !strcmp(table->s->db.str, db)) + if (! multi_alloc_root(&m_locked_tables_root, + &dst_table_list, sizeof(*dst_table_list), + &db, db_len, + &table_name, table_name_len, + &alias, alias_len, + 0)) { - if (thd->locked_tables_mode) - { - if (table->parent) - { - /* - If MERGE child, need to reopen parent too. This means that - the first child to be closed will detach all children from - the parent and close it. OTOH in most cases a MERGE table - won't have multiple children with the same db.table_name. - */ - mysql_lock_remove(thd, thd->lock, table->parent, TRUE); - close_handle_and_leave_table_as_placeholder(table->parent); - } - else - mysql_lock_remove(thd, thd->lock, table, TRUE); - } - table->s->version= 0; - close_handle_and_leave_table_as_placeholder(table); + unlock_locked_tables(0); + return TRUE; } + + /** + Sic: remember the *actual* table level lock type taken, to + acquire the exact same type in reopen_tables(). + E.g. if the table was locked for write, src_table_list->lock_type is + TL_WRITE_DEFAULT, whereas reginfo.lock_type has been updated from + thd->update_lock_default. + */ + dst_table_list->init_one_table(db, table_name, alias, + src_table_list->table->reginfo.lock_type); + dst_table_list->mdl_lock_data= src_table_list->mdl_lock_data; + dst_table_list->table= table; + memcpy(db, src_table_list->db, db_len); + memcpy(table_name, src_table_list->table_name, table_name_len); + memcpy(alias, src_table_list->alias, alias_len); + /* Link last into the list of tables */ + *(dst_table_list->prev_global= m_locked_tables_last)= dst_table_list; + m_locked_tables_last= &dst_table_list->next_global; + table->pos_in_locked_tables= dst_table_list; } - DBUG_VOID_RETURN; + thd->locked_tables_mode= LTM_LOCK_TABLES; + return FALSE; } - /** - Reattach MERGE children after reopen. + Leave LTM_LOCK_TABLES mode if it's been entered. - @param[in] thd thread context - @param[in,out] err_tables_p pointer to pointer of tables in error + Close all locked tables, free memory, and leave the mode. - @return status - @retval FALSE OK, err_tables_p unchanged - @retval TRUE Error, err_tables_p contains table(s) + @note This function is a no-op if we're not in LOCK TABLES. */ -static bool reattach_merge(THD *thd, TABLE **err_tables_p) +void +Locked_tables_list::unlock_locked_tables(THD *thd) + { - TABLE *table; - TABLE *next; - TABLE **prv_p= &thd->open_tables; - bool error= FALSE; - DBUG_ENTER("reattach_merge"); - - for (table= thd->open_tables; table; table= next) - { - next= table->next; - DBUG_PRINT("tcache", ("check table: '%s'.'%s' 0x%lx next: 0x%lx", - table->s->db.str, table->s->table_name.str, - (long) table, (long) next)); - /* Reattach children for MERGE tables with "closed data files" only. */ - if (table->child_l && !table->children_attached) - { - DBUG_PRINT("tcache", ("MERGE parent, attach children")); - if(table->file->extra(HA_EXTRA_ATTACH_CHILDREN)) - { - my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias); - error= TRUE; - /* Remove table from open_tables. */ - *prv_p= next; - if (next) - prv_p= &next->next; - /* Stack table on error list. */ - table->next= *err_tables_p; - *err_tables_p= table; - continue; - } - else - { - table->children_attached= TRUE; - DBUG_PRINT("myrg", ("attached parent: '%s'.'%s' 0x%lx", - table->s->db.str, - table->s->table_name.str, (long) table)); - } - } - prv_p= &table->next; - } - DBUG_RETURN(error); -} - - -/** - Reopen all tables with closed data files. - - @param thd Thread context - @param get_locks Should we get locks after reopening tables ? - - @note Since this function can't properly handle prelocking and - create placeholders it should be used in very special - situations like FLUSH TABLES or ALTER TABLE. In general - case one should just repeat open_tables()/lock_tables() - combination when one needs tables to be reopened (for - example see open_and_lock_tables()). - - @note One should have lock on LOCK_open when calling this. - - @return FALSE in case of success, TRUE - otherwise. -*/ - -bool reopen_tables(THD *thd, bool get_locks) -{ - TABLE *table,*next,**prev; - TABLE **tables,**tables_ptr; // For locks - TABLE *err_tables= NULL, *err_tab_tmp; - bool error=0, not_used; - bool merge_table_found= FALSE; - - DBUG_ENTER("reopen_tables"); - - if (!thd->open_tables) - DBUG_RETURN(0); - - safe_mutex_assert_owner(&LOCK_open); - if (get_locks) + if (thd) { + DBUG_ASSERT(!thd->in_sub_stmt && + !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL)); /* - The ptr is checked later - Do not handle locks of MERGE children. + Sic: we must be careful to not close open tables if + we're not in LOCK TABLES mode: unlock_locked_tables() is + sometimes called implicitly, expecting no effect on + open tables, e.g. from begin_trans(). */ - uint opens=0; - for (table= thd->open_tables; table ; table=table->next) - if (!table->parent) - opens++; - DBUG_PRINT("tcache", ("open tables to lock: %u", opens)); - tables= (TABLE**) my_alloca(sizeof(TABLE*)*opens); - } - else - tables= &thd->open_tables; - tables_ptr =tables; + if (thd->locked_tables_mode != LTM_LOCK_TABLES) + return; - prev= &thd->open_tables; - for (table=thd->open_tables; table ; table=next) - { - uint db_stat=table->db_stat; - next=table->next; - DBUG_PRINT("tcache", ("open table: '%s'.'%s' 0x%lx " - "parent: 0x%lx db_stat: %u", - table->s->db.str, table->s->table_name.str, - (long) table, (long) table->parent, db_stat)); - if (table->child_l && !db_stat) - merge_table_found= TRUE; - if (!tables || (!db_stat && reopen_table(table))) - { - my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias); - /* - If we could not allocate 'tables', we may close open tables - here. If a MERGE table is affected, detach the children first. - It is not necessary to clear the child or parent table reference - of this table because the TABLE is freed. But we need to clear - the child or parent references of the other belonging tables so - that they cannot be moved into the unused_tables chain with - these pointers set. - */ - if (table->child_l || table->parent) - detach_merge_children(table, TRUE); - free_cache_entry(table); - error=1; - } - else - { - DBUG_PRINT("tcache", ("opened. need lock: %d", - get_locks && !db_stat && !table->parent)); - *prev= table; - prev= &table->next; - /* Do not handle locks of MERGE children. */ - if (get_locks && !db_stat && !table->parent) - { - *tables_ptr++= table; // need new lock on this - /* - We rely on having exclusive metadata lock on the table to be - able safely re-acquire table locks on it. - */ - DBUG_ASSERT(mdl_is_exclusive_lock_owner(&thd->mdl_context, 0, - table->s->db.str, - table->s->table_name.str)); - } - } - } - *prev=0; - /* - When all tables are open again, we can re-attach MERGE children to - their parents. All TABLE objects are still present. - */ - DBUG_PRINT("tcache", ("re-attaching MERGE tables: %d", merge_table_found)); - if (!error && merge_table_found && reattach_merge(thd, &err_tables)) - { - while (err_tables) - { - err_tab_tmp= err_tables->next; - free_cache_entry(err_tables); - err_tables= err_tab_tmp; - } - } - DBUG_PRINT("tcache", ("open tables to lock: %u", - (uint) (tables_ptr - tables))); - if (tables != tables_ptr) // Should we get back old locks - { - MYSQL_LOCK *lock; - const uint flags= MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK | - MYSQL_LOCK_IGNORE_FLUSH; - /* - Since we have exclusive metadata locks on tables which we - are reopening we should always get these locks (We won't - wait on table level locks so can't get aborted and we ignore - other threads that set THD::some_tables_deleted by using - MYSQL_LOCK_IGNORE_FLUSH flag). - */ - thd->some_tables_deleted=0; - if ((lock= mysql_lock_tables(thd, tables, (uint) (tables_ptr - tables), - flags, ¬_used))) - { - thd->lock= mysql_lock_merge(thd->lock, lock); - } - else + for (TABLE_LIST *table_list= m_locked_tables; + table_list; table_list= table_list->next_global) { /* - This case should only happen if there is a bug in the reopen logic. - Need to issue error message to have a reply for the application. - Not exactly what happened though, but close enough. + Clear the position in the list, the TABLE object will be + returned to the table cache. */ - my_error(ER_LOCK_DEADLOCK, MYF(0)); - error=1; + table_list->table->pos_in_locked_tables= NULL; } + thd->locked_tables_mode= LTM_NONE; + + close_thread_tables(thd); } - if (get_locks && tables) - { - my_afree((uchar*) tables); - } - broadcast_refresh(); - DBUG_RETURN(error); + /* + After closing tables we can free memory used for storing lock + request for metadata locks and TABLE_LIST elements. + */ + free_root(&m_locked_tables_root, MYF(0)); + m_locked_tables= NULL; + m_locked_tables_last= &m_locked_tables; } /** - Unlock and close tables open and locked by LOCK TABLES statement. + Unlink a locked table from the locked tables list, either + temporarily or permanently. - @param thd Current thread's context. + @param thd thread handle + @param table_list the element of locked tables list. + The implementation assumes that this argument + points to a TABLE_LIST element linked into + the locked tables list. Passing a TABLE_LIST + instance that is not part of locked tables + list will lead to a crash. + @parma remove_from_locked_tables + TRUE if the table is removed from the list + permanently. + + This function is a no-op if we're not under LOCK TABLES. + + @sa Locked_tables_list::reopen_tables() */ -void unlock_locked_tables(THD *thd) -{ - DBUG_ASSERT(!thd->in_sub_stmt && - !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL)); +void Locked_tables_list::unlink_from_list(THD *thd, + TABLE_LIST *table_list, + bool remove_from_locked_tables) +{ /* - Sic: we must be careful to not close open tables if - we're not in LOCK TABLES mode: unlock_locked_tables() is - sometimes called implicitly, expecting no effect on - open tables, e.g. from begin_trans(). + If mode is not LTM_LOCK_TABLES, we needn't do anything. Moreover, + outside this mode pos_in_locked_tables value is not trustworthy. */ if (thd->locked_tables_mode != LTM_LOCK_TABLES) return; - thd->locked_tables_mode= LTM_NONE; - close_thread_tables(thd); /* - After closing tables we can free memory used for storing lock - request objects for metadata locks + table_list must be set and point to pos_in_locked_tables of some + table. */ - free_root(&thd->locked_tables_root, MYF(MY_MARK_BLOCKS_FREE)); + DBUG_ASSERT(table_list->table->pos_in_locked_tables == table_list); + + /* Clear the pointer, the table will be returned to the table cache. */ + table_list->table->pos_in_locked_tables= NULL; + + /* Mark the table as closed in the locked tables list. */ + table_list->table= NULL; + + /* + If the table is being dropped or renamed, remove it from + the locked tables list (implicitly drop the LOCK TABLES lock + on it). + */ + if (remove_from_locked_tables) + { + *table_list->prev_global= table_list->next_global; + if (table_list->next_global == NULL) + m_locked_tables_last= table_list->prev_global; + else + table_list->next_global->prev_global= table_list->prev_global; + } +} + +/** + This is an attempt to recover (somewhat) in case of an error. + If we failed to reopen a closed table, let's unlink it from the + list and forget about it. From a user perspective that would look + as if the server "lost" the lock on one of the locked tables. + + @note This function is a no-op if we're not under LOCK TABLES. +*/ + +void Locked_tables_list::unlink_all_closed_tables() +{ + for (TABLE_LIST *table_list= m_locked_tables; table_list; table_list= + table_list->next_global) + { + if (table_list->table == NULL) + { + /* Unlink from list. */ + *table_list->prev_global= table_list->next_global; + if (table_list->next_global == NULL) + m_locked_tables_last= table_list->prev_global; + else + table_list->next_global->prev_global= table_list->prev_global; + } + } +} + + +/** + Reopen the tables locked with LOCK TABLES and temporarily closed + by a DDL statement or FLUSH TABLES. + + @note This function is a no-op if we're not under LOCK TABLES. + + @return TRUE if an error reopening the tables. May happen in + case of some fatal system error only, e.g. a disk + corruption, out of memory or a serious bug in the + locking. +*/ + +bool +Locked_tables_list::reopen_tables(THD *thd) +{ + enum enum_open_table_action ot_action_unused; + bool lt_refresh_unused; + + for (TABLE_LIST *table_list= m_locked_tables; + table_list; table_list= table_list->next_global) + { + MYSQL_LOCK *lock; + + if (table_list->table) /* The table was not closed */ + continue; + + /* Links into thd->open_tables upon success */ + if (open_table(thd, table_list, thd->mem_root, &ot_action_unused, + MYSQL_OPEN_REOPEN)) + { + unlink_all_closed_tables(); + return TRUE; + } + table_list->table->pos_in_locked_tables= table_list; + /* See also the comment on lock type in init_locked_tables(). */ + table_list->table->reginfo.lock_type= table_list->lock_type; + thd->in_lock_tables= 1; + lock= mysql_lock_tables(thd, &table_list->table, 1, + MYSQL_OPEN_REOPEN, <_refresh_unused); + thd->in_lock_tables= 0; + if (lock) + lock= mysql_lock_merge(thd->lock, lock); + if (lock == NULL) + { + /* + No one's seen this branch work. Recover and report an + error just in case. + */ + pthread_mutex_lock(&LOCK_open); + close_thread_table(thd, &thd->open_tables); + pthread_mutex_unlock(&LOCK_open); + table_list->table= 0; + unlink_all_closed_tables(); + my_error(ER_LOCK_DEADLOCK, MYF(0)); + return TRUE; + } + thd->lock= lock; + } + return FALSE; } @@ -3759,147 +3341,6 @@ err: } -/** - Load table definition from file and open table while holding exclusive - meta-data lock on it. - - @param thd Thread handle - @param entry Memory for TABLE object to be created - @param table_list TABLE_LIST with db, table_name & belong_to_view - @param alias Alias name - @param cache_key Key for table definition cache - @param cache_key_length Length of cache_key - - @note This auxiliary function is mostly inteded for re-opening table - in situations when we hold exclusive meta-data lock. It is not - intended for normal case in which we have only shared meta-data - lock on the table to be open. - - @note Extra argument for open is taken from thd->open_options. - - @note One must have a lock on LOCK_open as well as exclusive meta-data - lock on the table when calling this function. - - @return FALSE in case of success, TRUE otherwise. -*/ - -static bool reopen_table_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list, - const char *alias, char *cache_key, - uint cache_key_length) -{ - int error; - TABLE_SHARE *share; - uint discover_retry_count= 0; - DBUG_ENTER("reopen_table_entry"); - - safe_mutex_assert_owner(&LOCK_open); - DBUG_ASSERT(mdl_is_exclusive_lock_owner(&thd->mdl_context, 0, - table_list->db, - table_list->table_name)); - -retry: - if (!(share= get_table_share_with_create(thd, table_list, cache_key, - cache_key_length, - OPEN_VIEW | - table_list->i_s_requested_object, - &error))) - DBUG_RETURN(1); - - if (share->is_view) - { - /* - If parent_l of the table_list is non null then a merge table - has this view as child table, which is not supported. - */ - if (table_list->parent_l) - { - my_error(ER_WRONG_MRG_TABLE, MYF(0)); - goto err; - } - - /* - This table is a view. Validate its metadata version: in particular, - that it was a view when the statement was prepared. - */ - if (check_and_update_table_version(thd, table_list, share)) - goto err; - if (table_list->i_s_requested_object & OPEN_TABLE_ONLY) - goto err; - /* Attempt to reopen view will bring havoc to upper layers anyway. */ - release_table_share(share); - my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str, - "BASE TABLE"); - DBUG_RETURN(1); - } - - if (table_list->i_s_requested_object & OPEN_VIEW_ONLY) - goto err; - - while ((error= open_table_from_share(thd, share, alias, - (uint) (HA_OPEN_KEYFILE | - HA_OPEN_RNDFILE | - HA_GET_INDEX | - HA_TRY_READ_ONLY), - (READ_KEYINFO | COMPUTE_TYPES | - EXTRA_RECORD), - thd->open_options, entry, FALSE))) - { - if (error == 7) // Table def changed - { - share->version= 0; // Mark share as old - if (discover_retry_count++) // Retry once - goto err; - - /* - Since we have exclusive metadata lock on the table here the only - practical case when share->ref_count != 1 is when we have several - instances of the table opened by this thread (i.e we are under LOCK - TABLES). - */ - if (share->ref_count != 1) - goto err; - - release_table_share(share); - - if (ha_create_table_from_engine(thd, table_list->db, - table_list->table_name)) - goto err; - - thd->warning_info->clear_warning_info(thd->query_id); - thd->clear_error(); // Clear error message - goto retry; - } - if (!entry->s || !entry->s->crashed) - goto err; - - entry->s->version= 0; - - /* TODO: We don't need to release share here. */ - release_table_share(share); - pthread_mutex_unlock(&LOCK_open); - error= (int)auto_repair_table(thd, table_list); - pthread_mutex_lock(&LOCK_open); - - if (error) - goto err; - - goto retry; - } - - if (open_table_entry_fini(thd, share, entry)) - { - closefrm(entry, 0); - goto err; - } - - DBUG_RETURN(0); - -err: - release_table_share(share); - DBUG_RETURN(1); -} - - /** Finalize the process of TABLE creation by loading table triggers and taking action if a HEAP table content was emptied implicitly. @@ -8791,24 +8232,10 @@ bool is_equal(const LEX_STRING *a, const LEX_STRING *b) int abort_and_upgrade_lock(ALTER_PARTITION_PARAM_TYPE *lpt) { - DBUG_ENTER("abort_and_upgrade_locks"); + DBUG_ENTER("abort_and_upgrade_lock"); - lpt->old_lock_type= lpt->table->reginfo.lock_type; - /* If MERGE child, forward lock handling to parent. */ - mysql_lock_abort(lpt->thd, lpt->table->parent ? lpt->table->parent : - lpt->table, TRUE); - if (mdl_upgrade_shared_lock_to_exclusive(&lpt->thd->mdl_context, - lpt->table->mdl_lock_data)) - { - mysql_lock_downgrade_write(lpt->thd, - lpt->table->parent ? lpt->table->parent : - lpt->table, - lpt->old_lock_type); + if (wait_while_table_is_used(lpt->thd, lpt->table, HA_EXTRA_FORCE_REOPEN)) DBUG_RETURN(1); - } - pthread_mutex_lock(&LOCK_open); - tdc_remove_table(lpt->thd, TDC_RT_REMOVE_NOT_OWN, lpt->db, lpt->table_name); - pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); } diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 1f0c8321608..d36ea5c52c9 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -469,7 +469,7 @@ THD::THD() debug_sync_control(0), #endif /* defined(ENABLED_DEBUG_SYNC) */ main_warning_info(0), - mdl_el_root(NULL) + locked_tables_root(NULL) { ulong tmp; @@ -574,8 +574,6 @@ THD::THD() thr_lock_owner_init(&main_lock_id, &lock_info); m_internal_handler= NULL; - - init_sql_alloc(&locked_tables_root, ALLOC_ROOT_MIN_BLOCK_SIZE, 0); } @@ -995,7 +993,7 @@ void THD::cleanup(void) ha_rollback(this); xid_cache_delete(&transaction.xid_state); } - unlock_locked_tables(this); + locked_tables_list.unlock_locked_tables(this); #if defined(ENABLED_DEBUG_SYNC) /* End the Debug Sync Facility. See debug_sync.cc. */ @@ -1074,7 +1072,6 @@ THD::~THD() #endif free_root(&main_mem_root, MYF(0)); - free_root(&locked_tables_root, MYF(0)); DBUG_VOID_RETURN; } diff --git a/sql/sql_class.h b/sql/sql_class.h index 0fa3231e458..68ae3afe931 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1169,6 +1169,58 @@ private: Internal_error_handler *m_err_handler; }; +/** + Tables that were locked with LOCK TABLES statement. + + Encapsulates a list of TABLE_LIST instances for tables + locked by LOCK TABLES statement, memory root for metadata locks, + and, generally, the context of LOCK TABLES statement. + + In LOCK TABLES mode, the locked tables are kept open between + statements. + Therefore, we can't allocate metadata locks on execution memory + root -- as well as tables, the locks need to stay around till + UNLOCK TABLES is called. + The locks are allocated in the memory root encapsulate in this + class. + + Some SQL commands, like FLUSH TABLE or ALTER TABLE, demand that + the tables they operate on are closed, at least temporarily. + This class encapsulates a list of TABLE_LIST instances, one + for each base table from LOCK TABLES list, + which helps conveniently close the TABLEs when it's necessary + and later reopen them. + + Implemented in sql_base.cc +*/ + +class Locked_tables_list +{ +private: + MEM_ROOT m_locked_tables_root; + TABLE_LIST *m_locked_tables; + TABLE_LIST **m_locked_tables_last; +public: + Locked_tables_list() + :m_locked_tables(NULL), + m_locked_tables_last(&m_locked_tables) + { + init_sql_alloc(&m_locked_tables_root, MEM_ROOT_BLOCK_SIZE, 0); + } + void unlock_locked_tables(THD *thd); + ~Locked_tables_list() + { + unlock_locked_tables(0); + } + bool init_locked_tables(THD *thd); + TABLE_LIST *locked_tables() { return m_locked_tables; } + MEM_ROOT *locked_tables_root() { return &m_locked_tables_root; } + void unlink_from_list(THD *thd, TABLE_LIST *table_list, + bool remove_from_locked_tables); + void unlink_all_closed_tables(); + bool reopen_tables(THD *thd); +}; + /** Storage engine specific thread local data. @@ -1810,6 +1862,8 @@ public: */ Parser_state *m_parser_state; + Locked_tables_list locked_tables_list; + #ifdef WITH_PARTITION_STORAGE_ENGINE partition_info *work_part_info; #endif @@ -1819,8 +1873,13 @@ public: struct st_debug_sync_control *debug_sync_control; #endif /* defined(ENABLED_DEBUG_SYNC) */ - MEM_ROOT *mdl_el_root; - MEM_ROOT locked_tables_root; + /** + Points to the memory root of Locked_tables_list if + we're locking the tables for LOCK TABLES. Otherwise is NULL. + This is necessary to ensure that metadata locks allocated for + tables used in triggers will persist after statement end. + */ + MEM_ROOT *locked_tables_root; THD(); ~THD(); diff --git a/sql/sql_db.cc b/sql/sql_db.cc index b5c51601faf..1b61a96d7ff 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -1956,8 +1956,8 @@ bool mysql_upgrade_db(THD *thd, LEX_STRING *old_db) /* Step7: drop the old database. - remove_db_from_cache(olddb) and query_cache_invalidate(olddb) - are done inside mysql_rm_db(), no needs to execute them again. + query_cache_invalidate(olddb) is done inside mysql_rm_db(), no need + to execute them again. mysql_rm_db() also "unuses" if we drop the current database. */ error= mysql_rm_db(thd, old_db->str, 0, 1); diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 00bb013218d..e0199fcdf20 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -3542,16 +3542,22 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) { - pthread_mutex_lock(&LOCK_open); - if (reopen_name_locked_table(thd, create_table)) + enum enum_open_table_action ot_action_unused; + /* + Here we open the destination table, on which we already have + an exclusive metadata lock. + */ + if (open_table(thd, create_table, thd->mem_root, + &ot_action_unused, MYSQL_OPEN_REOPEN)) { + pthread_mutex_lock(&LOCK_open); quick_rm_table(create_info->db_type, create_table->db, table_case_name(create_info, create_table->table_name), 0); + pthread_mutex_unlock(&LOCK_open); } else table= create_table->table; - pthread_mutex_unlock(&LOCK_open); } else { diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 95f142bc8a2..9de07e90560 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -184,7 +184,7 @@ bool begin_trans(THD *thd) return 1; } - unlock_locked_tables(thd); + thd->locked_tables_list.unlock_locked_tables(thd); if (end_active_trans(thd)) error= -1; @@ -3583,7 +3583,7 @@ end_with_restore_list: done FLUSH TABLES WITH READ LOCK + BEGIN. If this assumption becomes false, mysqldump will not work. */ - unlock_locked_tables(thd); + thd->locked_tables_list.unlock_locked_tables(thd); if (thd->options & OPTION_TABLE_LOCK) { end_active_trans(thd); @@ -3594,7 +3594,7 @@ end_with_restore_list: my_ok(thd); break; case SQLCOM_LOCK_TABLES: - unlock_locked_tables(thd); + thd->locked_tables_list.unlock_locked_tables(thd); /* we must end the trasaction first, regardless of anything */ if (end_active_trans(thd)) goto error; @@ -3604,24 +3604,23 @@ end_with_restore_list: if (lex->protect_against_global_read_lock && !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) goto error; - thd->in_lock_tables=1; - thd->options|= OPTION_TABLE_LOCK; - alloc_mdl_locks(all_tables, &thd->locked_tables_root); - thd->mdl_el_root= &thd->locked_tables_root; - if (!(res= open_and_lock_tables_derived(thd, all_tables, FALSE, - MYSQL_OPEN_TAKE_UPGRADABLE_MDL))) + alloc_mdl_locks(all_tables, thd->locked_tables_list.locked_tables_root()); + + thd->options|= OPTION_TABLE_LOCK; + thd->in_lock_tables=1; + thd->locked_tables_root= thd->locked_tables_list.locked_tables_root(); + + res= (open_and_lock_tables_derived(thd, all_tables, FALSE, + MYSQL_OPEN_TAKE_UPGRADABLE_MDL) || + thd->locked_tables_list.init_locked_tables(thd)); + + thd->in_lock_tables= 0; + thd->locked_tables_root= NULL; + + if (res) { -#ifdef HAVE_QUERY_CACHE - if (thd->variables.query_cache_wlock_invalidate) - query_cache.invalidate_locked_for_write(first_table); -#endif /*HAVE_QUERY_CACHE*/ - thd->locked_tables_mode= LTM_LOCK_TABLES; - my_ok(thd); - } - else - { - /* + /* Need to end the current transaction, so the storage engine (InnoDB) can free its locks if LOCK TABLES locked some tables before finding that it can't lock a table in its list @@ -3630,8 +3629,14 @@ end_with_restore_list: end_active_trans(thd); thd->options&= ~(OPTION_TABLE_LOCK); } - thd->in_lock_tables=0; - thd->mdl_el_root= 0; + else + { +#ifdef HAVE_QUERY_CACHE + if (thd->variables.query_cache_wlock_invalidate) + query_cache.invalidate_locked_for_write(first_table); +#endif /*HAVE_QUERY_CACHE*/ + my_ok(thd); + } break; case SQLCOM_CREATE_DB: { @@ -6534,8 +6539,8 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, /* Link table in global list (all used tables) */ lex->add_to_query_tables(ptr); ptr->mdl_lock_data= mdl_alloc_lock(0 , ptr->db, ptr->table_name, - thd->mdl_el_root ? thd->mdl_el_root : - thd->mem_root); + thd->locked_tables_root ? + thd->locked_tables_root : thd->mem_root); DBUG_RETURN(ptr); } diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index 75ab9a73a5b..f4525db7def 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -4314,7 +4314,6 @@ set_engine_all_partitions(partition_info *part_info, static int fast_end_partition(THD *thd, ulonglong copied, ulonglong deleted, - TABLE *table, TABLE_LIST *table_list, bool is_empty, ALTER_PARTITION_PARAM_TYPE *lpt, bool written_bin_log) @@ -4333,11 +4332,7 @@ static int fast_end_partition(THD *thd, ulonglong copied, error= 1; if (error) - { - /* If error during commit, no need to rollback, it's done. */ - table->file->print_error(error, MYF(0)); - DBUG_RETURN(TRUE); - } + DBUG_RETURN(TRUE); /* The error has been reported */ if ((!is_empty) && (!written_bin_log) && (!thd->lex->no_write_to_binlog)) @@ -6215,30 +6210,13 @@ static void release_log_entries(partition_info *part_info) */ static void alter_partition_lock_handling(ALTER_PARTITION_PARAM_TYPE *lpt) { - int err; - if (lpt->thd->locked_tables_mode) - { - /* - When we have the table locked, it is necessary to reopen the table - since all table objects were closed and removed as part of the - ALTER TABLE of partitioning structure. - */ - pthread_mutex_lock(&LOCK_open); - lpt->thd->in_lock_tables= 1; - err= reopen_tables(lpt->thd, 1); - lpt->thd->in_lock_tables= 0; - if (err) - { - /* - Issue a warning since we weren't able to regain the lock again. - We also need to unlink table from thread's open list and from - table_cache - */ - unlink_open_table(lpt->thd, lpt->table, FALSE); - sql_print_warning("We failed to reacquire LOCKs in ALTER TABLE"); - } - pthread_mutex_unlock(&LOCK_open); - } + THD *thd= lpt->thd; + + close_all_tables_for_name(thd, lpt->table->s, FALSE); + lpt->table= 0; + lpt->table_list->table= 0; + if (thd->locked_tables_list.reopen_tables(thd)) + sql_print_warning("We failed to reacquire LOCKs in ALTER TABLE"); } /* @@ -6252,17 +6230,37 @@ static void alter_partition_lock_handling(ALTER_PARTITION_PARAM_TYPE *lpt) static int alter_close_tables(ALTER_PARTITION_PARAM_TYPE *lpt) { + TABLE_SHARE *share= lpt->table->s; THD *thd= lpt->thd; - const char *db= lpt->db; - const char *table_name= lpt->table_name; + TABLE *table; DBUG_ENTER("alter_close_tables"); /* - We need to also unlock tables and close all handlers. - We set lock to zero to ensure we don't do this twice - and we set db_stat to zero to ensure we don't close twice. + We must keep LOCK_open while manipulating with thd->open_tables. + Another thread may be working on it. */ pthread_mutex_lock(&LOCK_open); - close_data_files_and_leave_as_placeholders(thd, db, table_name); + /* + We can safely remove locks for all tables with the same name: + later they will all be closed anyway in + alter_partition_lock_handling(). + */ + for (table= thd->open_tables; table ; table= table->next) + { + if (!strcmp(table->s->table_name.str, share->table_name.str) && + !strcmp(table->s->db.str, share->db.str)) + { + mysql_lock_remove(thd, thd->lock, table); + table->file->close(); + table->db_stat= 0; // Mark file closed + /* + Ensure that we won't end up with a crippled table instance + in the table cache if an error occurs before we reach + alter_partition_lock_handling() and the table is closed + by close_thread_tables() instead. + */ + table->s->version= 0; + } + } pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); } @@ -6429,6 +6427,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, DBUG_ENTER("fast_alter_partition_table"); lpt->thd= thd; + lpt->table_list= table_list; lpt->part_info= part_info; lpt->alter_info= alter_info; lpt->create_info= create_info; @@ -6745,7 +6744,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, user */ DBUG_RETURN(fast_end_partition(thd, lpt->copied, lpt->deleted, - table, table_list, FALSE, NULL, + table_list, FALSE, NULL, written_bin_log)); err: close_thread_tables(thd); diff --git a/sql/sql_servers.cc b/sql/sql_servers.cc index 5251a50cab9..6d25b0c2b59 100644 --- a/sql/sql_servers.cc +++ b/sql/sql_servers.cc @@ -224,7 +224,8 @@ bool servers_reload(THD *thd) bool return_val= TRUE; DBUG_ENTER("servers_reload"); - unlock_locked_tables(thd); // Can't have locked tables here + /* Can't have locked tables here */ + thd->locked_tables_list.unlock_locked_tables(thd); DBUG_PRINT("info", ("locking servers_cache")); rw_wrlock(&THR_LOCK_servers); diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 2c91ac69f9f..52596f417b2 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1966,7 +1966,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, case -1: DBUG_ASSERT(thd->in_sub_stmt); error= 1; - goto err_with_placeholders; + goto err; default: // temporary table not found error= 0; @@ -2003,18 +2003,19 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, { if (thd->locked_tables_mode) { - if (close_cached_table(thd, table->table)) + if (wait_while_table_is_used(thd, table->table, HA_EXTRA_FORCE_REOPEN)) { error= -1; - goto err_with_placeholders; + goto err; } + close_all_tables_for_name(thd, table->table->s, TRUE); table->table= 0; } if (thd->killed) { error= -1; - goto err_with_placeholders; + goto err; } alias= (lower_case_table_names == 2) ? table->alias : table->table_name; /* remove .frm file and engine files */ @@ -2178,7 +2179,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, */ } } -err_with_placeholders: +err: if (!drop_temporary) { /* @@ -2195,7 +2196,7 @@ err_with_placeholders: if (thd->locked_tables_mode && thd->lock && thd->lock->table_count == 0 && non_temp_tables_count > 0) { - unlock_locked_tables(thd); + thd->locked_tables_list.unlock_locked_tables(thd); goto end; } for (table= tables; table; table= table->next_local) @@ -4315,6 +4316,7 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table, HA_CHECK_OPT *check_opt) { MDL_LOCK_DATA *mdl_lock_data= 0; + enum enum_open_table_action ot_action_unused; DBUG_ENTER("prepare_for_restore"); if (table->table) // do not overwrite existing tables on restore @@ -4360,16 +4362,16 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table, DBUG_RETURN(send_check_errmsg(thd, table, "restore", "Failed generating table from .frm file")); } + table->mdl_lock_data= mdl_lock_data; } /* Now we should be able to open the partially restored table to finish the restore in the handler later on */ - pthread_mutex_lock(&LOCK_open); - if (reopen_name_locked_table(thd, table)) + if (open_table(thd, table, thd->mem_root, + &ot_action_unused, MYSQL_OPEN_REOPEN)) { - pthread_mutex_unlock(&LOCK_open); if (mdl_lock_data) mdl_release_lock(&thd->mdl_context, mdl_lock_data); DBUG_RETURN(send_check_errmsg(thd, table, "restore", @@ -4377,7 +4379,6 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table, } /* A MERGE table must not come here. */ DBUG_ASSERT(!table->table || !table->table->child_l); - pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); } @@ -4392,7 +4393,10 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, const char **ext; MY_STAT stat_info; MDL_LOCK_DATA *mdl_lock_data; + enum enum_open_table_action ot_action_unused; DBUG_ENTER("prepare_for_repair"); + uint reopen_for_repair_flags= (MYSQL_LOCK_IGNORE_FLUSH | + MYSQL_OPEN_HAS_MDL_LOCK); if (!(check_opt->sql_flags & TT_USEFRM)) DBUG_RETURN(0); @@ -4428,8 +4432,9 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); // Out of memory } - table= &tmp_table; pthread_mutex_unlock(&LOCK_open); + table= &tmp_table; + table_list->mdl_lock_data= mdl_lock_data; } else { @@ -4490,8 +4495,9 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, Table was successfully open in mysql_admin_table(). Now we need to close it, but leave it protected by exclusive metadata lock. */ - if (close_cached_table(thd, table)) + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) goto end; + close_all_tables_for_name(thd, table_list->table->s, FALSE); table_list->table= 0; } /* @@ -4519,21 +4525,23 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, goto end; } + if (thd->locked_tables_list.reopen_tables(thd)) + goto end; + /* Now we should be able to open the partially repaired table to finish the repair in the handler later on. */ - pthread_mutex_lock(&LOCK_open); - if (reopen_name_locked_table(thd, table_list)) + if (open_table(thd, table_list, thd->mem_root, + &ot_action_unused, reopen_for_repair_flags)) { - pthread_mutex_unlock(&LOCK_open); error= send_check_errmsg(thd, table_list, "repair", "Failed to open partially repaired table"); goto end; } - pthread_mutex_unlock(&LOCK_open); end: + thd->locked_tables_list.unlink_all_closed_tables(); if (table == &tmp_table) { pthread_mutex_lock(&LOCK_open); @@ -5334,6 +5342,11 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, db, table_name, reg_ext, 0); if (!access(dst_path, F_OK)) goto table_exists; + /* + Make the metadata lock available to open_table() called to + reopen the table down the road. + */ + table->mdl_lock_data= target_lock_data; } DBUG_EXECUTE_IF("sleep_create_like_before_copy", my_sleep(6000000);); @@ -5463,20 +5476,16 @@ binlog: char buf[2048]; String query(buf, sizeof(buf), system_charset_info); query.length(0); // Have to zero it since constructor doesn't - + enum enum_open_table_action ot_action_unused; /* Here we open the destination table, on which we already have - exclusive metada lock. This is needed for store_create_info() - to work. The table will be closed by unlink_open_table() at - the end of this function. + exclusive metadata lock. This is needed for store_create_info() + to work. The table will be closed by close_thread_table() at + the end of this branch. */ - pthread_mutex_lock(&LOCK_open); - if (reopen_name_locked_table(thd, table)) - { - pthread_mutex_unlock(&LOCK_open); + if (open_table(thd, table, thd->mem_root, &ot_action_unused, + MYSQL_OPEN_REOPEN)) goto err; - } - pthread_mutex_unlock(&LOCK_open); int result __attribute__((unused))= store_create_info(thd, table, &query, @@ -5485,8 +5494,14 @@ binlog: DBUG_ASSERT(result == 0); // store_create_info() always return 0 write_bin_log(thd, TRUE, query.ptr(), query.length()); + DBUG_ASSERT(thd->open_tables == table->table); pthread_mutex_lock(&LOCK_open); - unlink_open_table(thd, table->table, FALSE); + /* + When opening the table, we ignored the locked tables + (MYSQL_OPEN_GET_NEW_TABLE). Now we can close the table without + risking to close some locked table. + */ + close_thread_table(thd, &thd->open_tables); pthread_mutex_unlock(&LOCK_open); } else // Case 1 @@ -6789,13 +6804,14 @@ view_err: /* Then do a 'simple' rename of the table. First we need to close all instances of 'source' table. - Note that if close_cached_table() returns error here (i.e. if + Note that if wait_while_table_is_used() returns error here (i.e. if this thread was killed) then it must be that previous step of - simple rename did nothing and therefore we can safely reture + simple rename did nothing and therefore we can safely return without additional clean-up. */ - if (close_cached_table(thd, table)) + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) goto err; + close_all_tables_for_name(thd, table->s, TRUE); /* Then, we want check once again that target table does not exist. Actually the order of these two steps does not matter since @@ -7384,11 +7400,12 @@ view_err: if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME)) goto err_new_table_cleanup; - pthread_mutex_lock(&LOCK_open); - close_data_files_and_leave_as_placeholders(thd, db, table_name); + close_all_tables_for_name(thd, table->s, + new_name != table_name || new_db != db); error=0; + table_list->table= table= 0; /* Safety */ save_old_db_type= old_db_type; /* @@ -7410,6 +7427,7 @@ view_err: /* This type cannot happen in regular ALTER. */ new_db_type= old_db_type= NULL; } + pthread_mutex_lock(&LOCK_open); if (mysql_rename_table(old_db_type, db, table_name, db, old_name, FN_TO_IS_TMP)) { @@ -7433,10 +7451,15 @@ view_err: FN_FROM_IS_TMP); } + if (! error) + (void) quick_rm_table(old_db_type, db, old_name, FN_IS_TMP); + + pthread_mutex_unlock(&LOCK_open); + if (error) { /* This shouldn't happen. But let us play it safe. */ - goto err_with_placeholders; + goto err_with_mdl; } if (need_copy_table == ALTER_TABLE_METADATA_ONLY) @@ -7446,6 +7469,7 @@ view_err: To do this we need to obtain a handler object for it. NO need to tamper with MERGE tables. The real open is done later. */ + enum enum_open_table_action ot_action_unused; TABLE *t_table; if (new_name != table_name || new_db != db) { @@ -7454,51 +7478,39 @@ view_err: table_list->table_name_length= strlen(new_name); table_list->db= new_db; table_list->db_length= strlen(new_db); - if (reopen_name_locked_table(thd, table_list)) - goto err_with_placeholders; - t_table= table_list->table; + table_list->mdl_lock_data= target_lock_data; } else { - if (reopen_table(table)) - goto err_with_placeholders; - t_table= table; + /* + Under LOCK TABLES, we have a different mdl_lock_data + points to a different instance than the one set initially + to request the lock. + */ + table_list->mdl_lock_data= mdl_lock_data; } - /* Tell the handler that a new frm file is in place. */ - if (t_table->file->ha_create_handler_files(path, NULL, CHF_INDEX_FLAG, - create_info)) - goto err_with_placeholders; - if (thd->locked_tables_mode) + if (open_table(thd, table_list, thd->mem_root, + &ot_action_unused, MYSQL_OPEN_REOPEN)) { - if (new_name == table_name && new_db == db) - { - /* - We are going to reopen table down on the road, so we have to restore - state of the TABLE object which we used for obtaining of handler - object to make it suitable for reopening. - */ - DBUG_ASSERT(t_table == table); - close_handle_and_leave_table_as_placeholder(table); - } - else - { - /* Unlink the new name from the list of locked tables. */ - unlink_open_table(thd, t_table, FALSE); - } + goto err_with_mdl; } - } + t_table= table_list->table; - (void) quick_rm_table(old_db_type, db, old_name, FN_IS_TMP); + /* Tell the handler that a new frm file is in place. */ + error= t_table->file->ha_create_handler_files(path, NULL, CHF_INDEX_FLAG, + create_info); + + DBUG_ASSERT(thd->open_tables == t_table); + pthread_mutex_lock(&LOCK_open); + close_thread_table(thd, &thd->open_tables); + pthread_mutex_unlock(&LOCK_open); + table_list->table= 0; - if (thd->locked_tables_mode && new_name == table_name && new_db == db) - { - thd->in_lock_tables= 1; - error= reopen_tables(thd, 1); - thd->in_lock_tables= 0; if (error) - goto err_with_placeholders; + goto err_with_mdl; } - pthread_mutex_unlock(&LOCK_open); + if (thd->locked_tables_list.reopen_tables(thd)) + goto err_with_mdl; thd_proc_info(thd, "end"); @@ -7541,9 +7553,6 @@ view_err: { if ((new_name != table_name || new_db != db)) { - pthread_mutex_lock(&LOCK_open); - unlink_open_table(thd, table, FALSE); - pthread_mutex_unlock(&LOCK_open); mdl_release_lock(&thd->mdl_context, target_lock_data); mdl_release_all_locks_for_name(&thd->mdl_context, mdl_lock_data); } @@ -7607,14 +7616,14 @@ err: mdl_release_lock(&thd->mdl_context, target_lock_data); DBUG_RETURN(TRUE); -err_with_placeholders: +err_with_mdl: /* An error happened while we were holding exclusive name metadata lock - on table being altered. To be safe under LOCK TABLES we should remove - placeholders from the list of open tables and relese metadata lock. + on table being altered. To be safe under LOCK TABLES we should + remove all references to the altered table from the list of locked + tables and release the exclusive metadata lock. */ - unlink_open_table(thd, table, FALSE); - pthread_mutex_unlock(&LOCK_open); + thd->locked_tables_list.unlink_all_closed_tables(); if (target_lock_data) mdl_release_lock(&thd->mdl_context, target_lock_data); mdl_release_all_locks_for_name(&thd->mdl_context, mdl_lock_data); diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index c8f01a56a72..9a42dd189e7 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -328,6 +328,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) bool result= TRUE; String stmt_query; bool need_start_waiting= FALSE; + bool lock_upgrade_done= FALSE; DBUG_ENTER("mysql_create_or_drop_trigger"); @@ -450,72 +451,54 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) if (!(tables->table= find_write_locked_table(thd->open_tables, tables->db, tables->table_name))) goto end; - /* - Ensure that table is opened only by this thread and that no other - statement will open this table. - */ - if (wait_while_table_is_used(thd, tables->table, HA_EXTRA_FORCE_REOPEN)) - goto end; - - pthread_mutex_lock(&LOCK_open); + /* Later on we will need it to downgrade the lock */ + tables->mdl_lock_data= tables->table->mdl_lock_data; } else { - /* - Obtain exlusive meta-data lock on the table and remove TABLE - instances from cache. - */ - if (lock_table_names(thd, tables)) + tables->table= open_n_lock_single_table(thd, tables, + TL_WRITE_ALLOW_READ, + MYSQL_OPEN_TAKE_UPGRADABLE_MDL); + if (! tables->table) goto end; - - pthread_mutex_lock(&LOCK_open); - tdc_remove_table(thd, TDC_RT_REMOVE_ALL, tables->db, tables->table_name); - - if (reopen_name_locked_table(thd, tables)) - goto end_unlock; + tables->table->use_all_columns(); } table= tables->table; + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + goto end; + + lock_upgrade_done= TRUE; + if (!table->triggers) { if (!create) { my_error(ER_TRG_DOES_NOT_EXIST, MYF(0)); - goto end_unlock; + goto end; } if (!(table->triggers= new (&table->mem_root) Table_triggers_list(table))) - goto end_unlock; + goto end; } + pthread_mutex_lock(&LOCK_open); result= (create ? table->triggers->create_trigger(thd, tables, &stmt_query): table->triggers->drop_trigger(thd, tables, &stmt_query)); - - /* Under LOCK TABLES we must reopen the table to activate the trigger. */ - if (!result && thd->locked_tables_mode) - { - /* Make table suitable for reopening */ - close_data_files_and_leave_as_placeholders(thd, tables->db, - tables->table_name); - thd->in_lock_tables= 1; - if (reopen_tables(thd, 1)) - { - /* To be safe remove this table from the set of LOCKED TABLES */ - unlink_open_table(thd, tables->table, FALSE); - - /* - Ignore reopen_tables errors for now. It's better not leave master/slave - in a inconsistent state. - */ - thd->clear_error(); - } - thd->in_lock_tables= 0; - } - -end_unlock: pthread_mutex_unlock(&LOCK_open); + if (result) + goto end; + + close_all_tables_for_name(thd, table->s, FALSE); + /* + Reopen the table if we were under LOCK TABLES. + Ignore the return value for now. It's better to + keep master/slave in consistent state. + */ + thd->locked_tables_list.reopen_tables(thd); + end: if (!result) { @@ -525,11 +508,11 @@ end: /* If we are under LOCK TABLES we should restore original state of meta-data locks. Otherwise call to close_thread_tables() will take care about both - TABLE instance created by reopen_name_locked_table() and metadata lock. + TABLE instance created by open_n_lock_single_table() and metadata lock. */ - if (thd->locked_tables_mode && tables && tables->table) + if (thd->locked_tables_mode && tables && lock_upgrade_done) mdl_downgrade_exclusive_lock(&thd->mdl_context, - tables->table->mdl_lock_data); + tables->mdl_lock_data); if (need_start_waiting) start_waiting_global_read_lock(thd); diff --git a/sql/table.h b/sql/table.h index 48caf962894..432523e5db2 100644 --- a/sql/table.h +++ b/sql/table.h @@ -632,7 +632,6 @@ private: TABLE *share_next, **share_prev; friend struct TABLE_share; - friend bool reopen_table(TABLE *table); public: @@ -679,6 +678,8 @@ public: /* Table's triggers, 0 if there are no of them */ Table_triggers_list *triggers; TABLE_LIST *pos_in_table_list;/* Element referring to this table */ + /* Position in thd->locked_table_list under LOCK TABLES */ + TABLE_LIST *pos_in_locked_tables; ORDER *group; const char *alias; /* alias or table name */ uchar *null_flags;