#At file:///home/dlenev/src/bzr/mysql-6.0-3726-w2/
2667 Dmitry Lenev 2008-06-11 [merge]
Merged patch with after review fix for WL#3726 "DDL locking
for all metadata objects" that changes open_table() to return
bool with current mysql-6.0-3726 tree.
modified:
mysql-test/r/lock.result
mysql-test/suite/rpl/r/rpl_trigger.result
mysql-test/t/lock.test
sql/lock.cc
sql/mdl.cc
sql/mdl.h
sql/mysql_priv.h
sql/sp_head.cc
sql/sql_acl.cc
sql/sql_base.cc
sql/sql_class.cc
sql/sql_class.h
sql/sql_db.cc
sql/sql_delete.cc
sql/sql_insert.cc
sql/sql_parse.cc
sql/sql_partition.cc
sql/sql_rename.cc
sql/sql_servers.cc
sql/sql_show.cc
sql/sql_table.cc
sql/sql_trigger.cc
sql/table.h
=== modified file 'mysql-test/r/lock.result'
--- a/mysql-test/r/lock.result 2008-05-23 13:54:03 +0000
+++ b/mysql-test/r/lock.result 2008-06-11 11:49:58 +0000
@@ -174,3 +174,48 @@ Cleanup.
drop table t2, t3;
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.
+#
=== modified file 'mysql-test/suite/rpl/r/rpl_trigger.result'
--- a/mysql-test/suite/rpl/r/rpl_trigger.result 2007-12-18 09:07:08 +0000
+++ b/mysql-test/suite/rpl/r/rpl_trigger.result 2008-06-11 11:49:58 +0000
@@ -890,8 +890,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;
=== modified file 'mysql-test/t/lock.test'
--- a/mysql-test/t/lock.test 2008-05-23 13:54:03 +0000
+++ b/mysql-test/t/lock.test 2008-06-11 11:49:58 +0000
@@ -222,3 +222,46 @@ create view v_bug5719 as select * from t
drop table t2, t3;
--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 #
=== modified file 'sql/lock.cc'
--- a/sql/lock.cc 2008-06-06 19:19:04 +0000
+++ b/sql/lock.cc 2008-06-11 11:49:58 +0000
@@ -497,28 +497,15 @@ void mysql_unlock_read_tables(THD *thd,
/**
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;
@@ -532,9 +519,8 @@ void mysql_lock_remove(THD *thd, MYSQL_L
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;
=== modified file 'sql/mdl.cc'
--- a/sql/mdl.cc 2008-06-06 10:28:58 +0000
+++ b/sql/mdl.cc 2008-06-10 14:01:56 +0000
@@ -611,6 +611,7 @@ static bool can_grant_lock(MDL_LOCK *loc
This function must be called after the lock is added to a context.
+ @param context [in] Context containing request for lock
@param lock_data [in] Lock request object for lock to be acquired
@param retry [out] Indicates that conflicting lock exists and another
attempt should be made after releasing all current
@@ -622,16 +623,19 @@ static bool can_grant_lock(MDL_LOCK *loc
In the latter case "retry" parameter is set to TRUE.
*/
-bool mdl_acquire_shared_lock(MDL_LOCK_DATA *lock_data, bool *retry)
+bool mdl_acquire_shared_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data,
+ bool *retry)
{
MDL_LOCK *lock;
*retry= FALSE;
DBUG_ASSERT(is_shared(lock_data) && lock_data->state == MDL_PENDING);
+ DBUG_ASSERT(lock_data->ctx == context);
+
safe_mutex_assert_not_owner(&LOCK_open);
- if (lock_data->ctx->has_global_shared_lock &&
+ if (context->has_global_shared_lock &&
lock_data->type == MDL_SHARED_UPGRADABLE)
{
my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0));
=== modified file 'sql/mdl.h'
--- a/sql/mdl.h 2008-06-06 10:28:58 +0000
+++ b/sql/mdl.h 2008-06-10 14:01:56 +0000
@@ -164,7 +164,8 @@ inline void mdl_set_lock_type(MDL_LOCK_D
lock_data->type= lock_type;
}
-bool mdl_acquire_shared_lock(MDL_LOCK_DATA *lock_data, bool *retry);
+bool mdl_acquire_shared_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data,
+ bool *retry);
bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context);
bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context,
MDL_LOCK_DATA *lock_data);
=== modified file 'sql/mysql_priv.h'
--- a/sql/mysql_priv.h 2008-06-09 10:01:19 +0000
+++ b/sql/mysql_priv.h 2008-06-11 15:37:09 +0000
@@ -1323,23 +1323,16 @@ bool open_table(THD *thd, TABLE_LIST *ta
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);
-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);
bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias,
uint db_stat, uint prgflag,
uint ha_open_flags, TABLE *outparam,
TABLE_LIST *table_desc, MEM_ROOT *mem_root);
-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;
@@ -1505,12 +1498,12 @@ void add_join_on(TABLE_LIST *b,Item *exp
void add_join_natural(TABLE_LIST *a,TABLE_LIST *b,List<String> *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);
@@ -1633,7 +1626,6 @@ void close_temporary_table(THD *thd, TAB
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);
@@ -1654,8 +1646,11 @@ uint prep_alter_part_table(THD *thd, TAB
#endif
bool notify_thread_having_shared_lock(THD *thd, THD *in_use);
-void expel_table_from_cache(THD *leave_thd, const char *db,
- const char *table_name);
+
+enum enum_tdc_remove_table_type {TDC_RT_REMOVE_ALL, TDC_RT_REMOVE_NOT_OWN,
+ TDC_RT_REMOVE_UNUSED};
+void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
+ const char *db, const char *table_name);
#define NORMAL_PART_NAME 0
#define TEMP_PART_NAME 1
@@ -1669,7 +1664,7 @@ void create_subpartition_name(char *out,
typedef struct st_lock_param_type
{
- TABLE_LIST table_list;
+ TABLE_LIST *table_list;
ulonglong copied;
ulonglong deleted;
THD *thd;
@@ -1680,7 +1675,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;
@@ -2137,12 +2131,31 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd,
#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);
=== modified file 'sql/sp_head.cc'
--- a/sql/sp_head.cc 2008-06-08 18:13:58 +0000
+++ b/sql/sp_head.cc 2008-06-11 11:49:58 +0000
@@ -3934,8 +3934,8 @@ sp_head::add_used_tables_to_table_list(T
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 */
@@ -3981,8 +3981,8 @@ sp_add_to_query_tables(THD *thd, LEX *le
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;
=== modified file 'sql/sql_acl.cc'
--- a/sql/sql_acl.cc 2008-05-23 13:54:03 +0000
+++ b/sql/sql_acl.cc 2008-06-11 11:49:58 +0000
@@ -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
=== modified file 'sql/sql_base.cc'
--- a/sql/sql_base.cc 2008-06-11 12:50:51 +0000
+++ b/sql/sql_base.cc 2008-06-11 15:37:09 +0000
@@ -107,9 +107,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);
@@ -392,20 +389,6 @@ static void table_def_unuse_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.
@@ -702,65 +685,6 @@ static void reference_table_share(TABLE_
}
-/**
- 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
@@ -897,12 +821,17 @@ void free_io_cache(TABLE *table)
particular table identified by its share.
@param share Table share.
+
+ @pre Caller should have LOCK_open mutex acquired.
*/
static void kill_delayed_threads_for_table(TABLE_SHARE *share)
{
I_P_List_iterator<TABLE, TABLE_share> it(share->used_tables);
TABLE *tab;
+
+ safe_mutex_assert_owner(&LOCK_open);
+
while ((tab= it++))
{
THD *in_use= tab->in_use;
@@ -959,6 +888,15 @@ bool close_cached_tables(THD *thd, TABLE
DBUG_PRINT("tcache", ("incremented global refresh_version to: %lu",
refresh_version));
kill_delayed_threads();
+ /*
+ Get rid of all unused TABLE and TABLE_SHARE instances. By doing
+ this we automatically close all tables which were marked as "old".
+ */
+ while (unused_tables)
+ free_cache_entry(unused_tables);
+ /* Free table shares which were not freed implicitly by loop above. */
+ while (oldest_unused_share->next)
+ (void) hash_delete(&table_def_cache, (uchar*) oldest_unused_share);
}
else
{
@@ -969,8 +907,10 @@ bool close_cached_tables(THD *thd, TABLE
if (share)
{
- share->version= 0;
kill_delayed_threads_for_table(share);
+ /* tdc_remove_table() also sets TABLE_SHARE::version to 0. */
+ tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, table->db,
+ table->table_name);
found=1;
}
}
@@ -978,90 +918,43 @@ bool close_cached_tables(THD *thd, TABLE
wait_for_refresh=0; // Nothing to wait for
}
- /*
- Get rid of all unused TABLE and TABLE_SHARE instances. By doing
- this we automatically close all tables which were marked as "old".
-
- FIXME: Do not close all unused TABLE instances when flushing
- particular table.
- */
- while (unused_tables)
- free_cache_entry(unused_tables);
- /* Free table shares */
- while (oldest_unused_share->next)
- (void) hash_delete(&table_def_cache, (uchar*) oldest_unused_share);
+ if (!have_lock)
+ pthread_mutex_unlock(&LOCK_open);
if (!wait_for_refresh)
- {
- if (!have_lock)
- pthread_mutex_unlock(&LOCK_open);
DBUG_RETURN(result);
- }
+ /* Code below assume that LOCK_open is released. */
DBUG_ASSERT(!have_lock);
- pthread_mutex_unlock(&LOCK_open);
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)
- {
- for (TABLE *tab= thd->open_tables; tab; tab= tab->next)
- {
- /*
- 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
+ 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_LIST *table= tables; table; table= table->next_local)
+ /* 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))
{
- /* 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);
}
}
@@ -1117,16 +1010,12 @@ bool close_cached_tables(THD *thd, TABLE
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
@@ -1262,6 +1151,8 @@ static void mark_used_tables_as_free_for
{
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;
@@ -1306,6 +1197,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.
@@ -1929,7 +1889,7 @@ int drop_temporary_table(THD *thd, TABLE
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);
}
@@ -2050,98 +2010,6 @@ bool rename_temporary_table(THD* thd, TA
}
- /* move table first in unused links */
-
-static void relink_unused(TABLE *table)
-{
- /* Assert that MERGE children are not attached in unused_tables. */
- DBUG_ASSERT(!table->is_children_attached());
-
- if (table != unused_tables)
- {
- table->prev->next=table->next; /* Remove from unused list */
- table->next->prev=table->prev;
- table->next=unused_tables; /* Link in unused tables */
- table->prev=unused_tables->prev;
- unused_tables->prev->next=table;
- unused_tables->prev=table;
- unused_tables=table;
- check_unused();
- }
-}
-
-
-/**
- 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.
@@ -2182,125 +2050,30 @@ bool wait_while_table_is_used(THD *thd,
}
pthread_mutex_lock(&LOCK_open);
- expel_table_from_cache(thd, table->s->db.str, table->s->table_name.str);
+ tdc_remove_table(thd, TDC_RT_REMOVE_NOT_OWN,
+ table->s->db.str, table->s->table_name.str);
pthread_mutex_unlock(&LOCK_open);
DBUG_RETURN(FALSE);
}
/**
- Upgrade metadata lock on the table and close all its instances.
-
- @param thd Thread handler
- @param table Table to remove from cache
-
- @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
+ Close a and drop a just created table in CREATE TABLE ... SELECT.
- @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.
+ @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
+
+ 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,
@@ -2310,13 +2083,14 @@ void drop_open_table(THD *thd, TABLE *ta
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);
}
@@ -2367,72 +2141,6 @@ void wait_for_condition(THD *thd, pthrea
}
-/*
- 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= table->force_index= 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.
@@ -2512,6 +2220,62 @@ void table_share_release_hook(void *shar
/*
+ 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.
SYNOPSIS
@@ -2585,7 +2349,7 @@ bool open_table(THD *thd, TABLE_LIST *ta
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)
{
@@ -2630,7 +2394,8 @@ bool open_table(THD *thd, TABLE_LIST *ta
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;
@@ -2758,47 +2523,12 @@ bool open_table(THD *thd, TABLE_LIST *ta
*/
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(mdl_lock_data, &retry))
- {
- if (retry)
- *action= OT_BACK_OFF_AND_RETRY;
- DBUG_RETURN(TRUE);
- }
- }
pthread_mutex_lock(&LOCK_open);
@@ -3124,424 +2854,264 @@ TABLE *find_write_locked_table(TABLE *li
}
-/*
- Reopen an table because the definition has changed.
+/***********************************************************************
+ class Locked_tables_list implementation. Declared in sql_class.h
+************************************************************************/
- SYNOPSIS
- reopen_table()
- table Table object
+/**
+ Enter LTM_LOCK_TABLES mode.
- 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.
+ 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.
- RETURN
- 0 ok
- 1 error. The old table object is not changed.
+ @param thd thread handle
+
+ @return TRUE if out of memory.
*/
-bool reopen_table(TABLE *table)
+bool
+Locked_tables_list::init_locked_tables(THD *thd)
{
- 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' %p", table->s->db.str,
- table->s->table_name.str, table));
-
- DBUG_ASSERT(table->s->ref_count == 0);
- DBUG_ASSERT(!table->sort.io_cache);
- DBUG_ASSERT(!table->children_attached);
+ DBUG_ASSERT(thd->locked_tables_mode == LTM_NONE);
+ DBUG_ASSERT(m_locked_tables == NULL);
-#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;
+ for (TABLE *table= thd->open_tables; table; table= table->next)
+ {
+ 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;
+
+ 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))
+ {
+ unlock_locked_tables(0);
+ return TRUE;
+ }
- delete table->triggers;
- if (table->file)
- (void) closefrm(table, 1); // close file, free everything
+ /**
+ 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;
+ }
+ thd->locked_tables_mode= LTM_LOCK_TABLES;
+ return FALSE;
+}
- *table= tmp;
- table->default_column_bitmaps();
- table->file->change_table_ptr(table, table->s);
+/**
+ Leave LTM_LOCK_TABLES mode if it's been entered.
- 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++)
+ Close all locked tables, free memory, and leave the mode.
+
+ @note This function is a no-op if we're not in LOCK TABLES.
+*/
+
+void
+Locked_tables_list::unlock_locked_tables(THD *thd)
+
+{
+ if (thd)
{
- for (part=0 ; part < table->key_info[key].usable_key_parts ; part++)
- table->key_info[key].key_part[part].field->table= table;
+ DBUG_ASSERT(!thd->in_sub_stmt &&
+ !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL));
+ /*
+ 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 (thd->locked_tables_mode != LTM_LOCK_TABLES)
+ return;
+
+ for (TABLE_LIST *table_list= m_locked_tables;
+ table_list; table_list= table_list->next_global)
+ {
+ /*
+ Clear the position in the list, the TABLE object will be
+ returned to the table cache.
+ */
+ table_list->table->pos_in_locked_tables= NULL;
+ }
+ thd->locked_tables_mode= LTM_NONE;
+
+ close_thread_tables(thd);
}
- 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().
+ After closing tables we can free memory used for storing lock
+ request for metadata locks and TABLE_LIST elements.
*/
-
- broadcast_refresh();
- error=0;
-
- end:
- DBUG_RETURN(error);
+ free_root(&m_locked_tables_root, MYF(0));
+ m_locked_tables= NULL;
+ m_locked_tables_last= &m_locked_tables;
}
/**
- Close all instances of a table open by this thread and replace
- them with placeholder in THD::open_tables list for future reopening.
+ Unlink a locked table from the locked tables list, either
+ temporarily or permanently.
- @param thd Thread context
- @param db Database name for the table to be closed
- @param table_name Name of the table to be closed
+ @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.
- @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.
+ This function is a no-op if we're not under LOCK TABLES.
- @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.
+ @sa Locked_tables_list::reopen_tables()
*/
-void close_data_files_and_leave_as_placeholders(THD *thd, const char *db,
- const char *table_name)
+
+void Locked_tables_list::unlink_from_list(THD *thd,
+ TABLE_LIST *table_list,
+ bool remove_from_locked_tables)
{
- TABLE *table;
- DBUG_ENTER("close_data_files_and_leave_as_placeholders");
+ /*
+ 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;
- safe_mutex_assert_owner(&LOCK_open);
+ /*
+ table_list must be set and point to pos_in_locked_tables of some
+ table.
+ */
+ DBUG_ASSERT(table_list->table->pos_in_locked_tables == table_list);
- if (! thd->locked_tables_mode)
- {
- /*
- 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;
- }
+ /* 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;
- for (table=thd->open_tables; table ; table=table->next)
+ /*
+ 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)
{
- if (!strcmp(table->s->table_name.str, table_name) &&
- !strcmp(table->s->db.str, db))
- {
- 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);
- }
+ *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;
}
- DBUG_VOID_RETURN;
}
-
/**
- Reattach MERGE children after reopen.
-
- @param[in] thd thread context
- @param[in,out] err_tables_p pointer to pointer of tables in error
+ 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.
- @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 under LOCK TABLES.
*/
-static bool reattach_merge(THD *thd, TABLE **err_tables_p)
+void Locked_tables_list::unlink_all_closed_tables()
{
- 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)
+ for (TABLE_LIST *table_list= m_locked_tables; table_list; table_list=
+ table_list->next_global)
{
- next= table->next;
- DBUG_PRINT("tcache", ("check table: '%s'.'%s' %p next: %p",
- table->s->db.str, table->s->table_name.str,
- table, 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;
- }
+ 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->children_attached= TRUE;
- DBUG_PRINT("myrg", ("attached parent: '%s'.'%s' %p",
- table->s->db.str,
- table->s->table_name.str, table));
- }
+ table_list->next_global->prev_global= table_list->prev_global;
}
- 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 ?
+ Reopen the tables locked with LOCK TABLES and temporarily closed
+ by a DDL statement or FLUSH 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 This function is a no-op if we're not under LOCK TABLES.
- @note One should have lock on LOCK_open when calling this.
-
- @return FALSE in case of success, TRUE - otherwise.
+ @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 reopen_tables(THD *thd, bool get_locks)
+bool
+Locked_tables_list::reopen_tables(THD *thd)
{
- 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);
+ enum enum_open_table_action ot_action_unused;
+ bool lt_refresh_unused;
- safe_mutex_assert_owner(&LOCK_open);
- if (get_locks)
- {
- /*
- The ptr is checked later
- Do not handle locks of MERGE children.
- */
- 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;
-
- 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' %p "
- "parent: %p db_stat: %u",
- table->s->db.str, table->s->table_name.str,
- table, 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
+ for (TABLE_LIST *table_list= m_locked_tables;
+ table_list; table_list= table_list->next_global)
{
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)))
+
+ 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))
{
- thd->lock= mysql_lock_merge(thd->lock, lock);
+ unlink_all_closed_tables();
+ return TRUE;
}
- else
+ 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)
{
/*
- 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.
+ 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));
- error=1;
+ return TRUE;
}
+ thd->lock= lock;
}
- if (get_locks && tables)
- {
- my_afree((uchar*) tables);
- }
- broadcast_refresh();
- DBUG_RETURN(error);
-}
-
-
-/**
- Unlock and close tables open and locked by LOCK TABLES statement.
-
- @param thd Current thread's context.
-*/
-
-void unlock_locked_tables(THD *thd)
-{
- DBUG_ASSERT(!thd->in_sub_stmt &&
- !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL));
-
- /*
- 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 (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
- */
- free_root(&thd->locked_tables_root, MYF(MY_MARK_BLOCKS_FREE));
+ return FALSE;
}
@@ -3722,138 +3292,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)
- {
- /*
- 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, OTM_OPEN)))
- {
- 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;
-
- mysql_reset_errors(thd, 1); // Clear warnings
- 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.
*/
@@ -3977,18 +3415,21 @@ end_with_lock_open:
/**
- Handle failed attempt ot open table by performing requested action.
+ Recover from failed attempt ot open table by performing requested action.
@param thd Thread context
@param table Table list element for table that caused problem
@param action Type of action requested by failed open_table() call
+ @pre This function should be called only with "action" != OT_NO_ACTION.
+
@retval FALSE - Success. One should try to open tables once again.
@retval TRUE - Error
*/
-static bool handle_failed_open_table_attempt(THD *thd, TABLE_LIST *table,
- enum_open_table_action action)
+static bool
+recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table,
+ enum_open_table_action action)
{
bool result= FALSE;
@@ -4005,7 +3446,7 @@ static bool handle_failed_open_table_att
if (mdl_acquire_exclusive_locks(&thd->mdl_context))
return TRUE;
pthread_mutex_lock(&LOCK_open);
- expel_table_from_cache(0, table->db, table->table_name);
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name);
ha_create_table_from_engine(thd, table->db, table->table_name);
pthread_mutex_unlock(&LOCK_open);
@@ -4019,7 +3460,7 @@ static bool handle_failed_open_table_att
if (mdl_acquire_exclusive_locks(&thd->mdl_context))
return TRUE;
pthread_mutex_lock(&LOCK_open);
- expel_table_from_cache(0, table->db, table->table_name);
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name);
pthread_mutex_unlock(&LOCK_open);
result= auto_repair_table(thd, table);
@@ -4580,7 +4021,7 @@ int open_tables(THD *thd, TABLE_LIST **s
TABLE_LIST element. Altough currently this assumption is valid
it may change in future.
*/
- if (handle_failed_open_table_attempt(thd, tables, action))
+ if (recover_from_failed_open_table_attempt(thd, tables, action))
{
result= -1;
goto err;
@@ -4893,7 +4334,7 @@ retry:
might have been acquired successfully.
*/
close_thread_tables(thd, (action == OT_BACK_OFF_AND_RETRY));
- if (handle_failed_open_table_attempt(thd, table_list, action))
+ if (recover_from_failed_open_table_attempt(thd, table_list, action))
break;
}
@@ -8318,7 +7759,13 @@ bool notify_thread_having_shared_lock(TH
thd_table ;
thd_table= thd_table->next)
{
- /* TODO With new MDL check for db_stat is probably a legacy */
+ /*
+ Check for TABLE::db_stat is needed since in some places we call
+ handler::close() for table instance (and set TABLE::db_stat to 0)
+ and do not remove such instances from the THD::open_tables
+ for some time, during which other thread can see those instances
+ (e.g. see partitioning code).
+ */
if (thd_table->db_stat)
signalled|= mysql_lock_abort_for_thread(thd, thd_table);
}
@@ -8328,50 +7775,82 @@ bool notify_thread_having_shared_lock(TH
/**
- Remove all instances of the table from cache assuming that current thread
- has exclusive meta-data lock on it (optionally leave instances belonging
- to the current thread in cache).
-
- @param leave_thd 0 If we should remove all instances
- non-0 Pointer to current thread context if we should
- leave instances belonging to this thread.
- @param db Name of database
- @param table_name Name of table
+ Remove all or some (depending on parameter) instances of TABLE and
+ TABLE_SHARE from the table definition cache.
+
+ @param thd Thread context
+ @param remove_type Type of removal:
+ TDC_RT_REMOVE_ALL - remove all TABLE instances and
+ TABLE_SHARE instance. There
+ should be no used TABLE objects
+ and caller should have exclusive
+ metadata lock on the table.
+ TDC_RT_REMOVE_NOT_OWN - remove all TABLE instances
+ except those that belong to
+ this thread. There should be
+ no TABLE objects used by other
+ threads and caller should have
+ exclusive metadata lock on the
+ table.
+ TDC_RT_REMOVE_UNUSED - remove all unused TABLE
+ instances (if there are no
+ used instances will also
+ remove TABLE_SHARE).
+ @param db Name of database
+ @param table_name Name of table
@note Unlike remove_table_from_cache() it assumes that table instances
are already not used by any (other) thread (this should be achieved
by using meta-data locks).
*/
-void expel_table_from_cache(THD *leave_thd, const char *db, const char *table_name)
+void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
+ const char *db, const char *table_name)
{
char key[MAX_DBKEY_LENGTH];
uint key_length;
TABLE *table;
TABLE_SHARE *share;
- key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1;
-
- if ((share= (TABLE_SHARE*) hash_search(&table_def_cache,(uchar*) key,
- key_length)))
- {
- I_P_List_iterator<TABLE, TABLE_share> it(share->free_tables);
- share->version= 0;
+ safe_mutex_assert_owner(&LOCK_open);
- while ((table= it++))
- relink_unused(table);
- }
+ DBUG_ASSERT(remove_type == TDC_RT_REMOVE_UNUSED ||
+ mdl_is_exclusive_lock_owner(&thd->mdl_context, 0,
+ db, table_name));
- /* This may destroy share so we have to do new look-up later. */
- while (unused_tables && !unused_tables->s->version)
- free_cache_entry(unused_tables);
+ key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1;
if ((share= (TABLE_SHARE*) hash_search(&table_def_cache,(uchar*) key,
key_length)))
{
- DBUG_ASSERT(leave_thd || share->ref_count == 0);
- if (share->ref_count == 0)
- hash_delete(&table_def_cache, (uchar*) share);
+ if (share->ref_count)
+ {
+ I_P_List_iterator<TABLE, TABLE_share> it(share->free_tables);
+#ifndef DBUG_OFF
+ if (remove_type == TDC_RT_REMOVE_ALL)
+ {
+ DBUG_ASSERT(share->used_tables.is_empty());
+ }
+ else if (remove_type == TDC_RT_REMOVE_NOT_OWN)
+ {
+ I_P_List_iterator<TABLE, TABLE_share> it2(share->used_tables);
+ while ((table= it2++))
+ if (table->in_use != thd)
+ {
+ DBUG_ASSERT(0);
+ }
+ }
+#endif
+ /*
+ Set share's version to zero in order to ensure that it gets
+ automatically deleted once it is no longer referenced.
+ */
+ share->version= 0;
+ while ((table= it++))
+ free_cache_entry(table);
+ }
+ else
+ (void) hash_delete(&table_def_cache, (uchar*) share);
}
}
@@ -8556,24 +8035,10 @@ bool is_equal(const LEX_STRING *a, const
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);
- expel_table_from_cache(lpt->thd, lpt->db, lpt->table_name);
- pthread_mutex_unlock(&LOCK_open);
DBUG_RETURN(0);
}
=== modified file 'sql/sql_class.cc'
--- a/sql/sql_class.cc 2008-06-06 19:19:04 +0000
+++ b/sql/sql_class.cc 2008-06-11 11:49:58 +0000
@@ -540,7 +540,7 @@ THD::THD()
when the DDL blocker is engaged.
*/
DDL_exception(FALSE),
- mdl_el_root(NULL)
+ locked_tables_root(NULL)
{
ulong tmp;
@@ -650,8 +650,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);
}
@@ -846,7 +844,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);
mysql_ha_cleanup(this);
delete_dynamic(&user_var_events);
hash_free(&user_vars);
@@ -947,7 +945,6 @@ THD::~THD()
#endif
free_root(&main_mem_root, MYF(0));
- free_root(&locked_tables_root, MYF(0));
DBUG_VOID_RETURN;
}
=== modified file 'sql/sql_class.h'
--- a/sql/sql_class.h 2008-06-06 19:19:04 +0000
+++ b/sql/sql_class.h 2008-06-11 11:49:58 +0000
@@ -1223,6 +1223,58 @@ private:
*/
};
+/**
+ 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.
@@ -1867,6 +1919,8 @@ public:
*/
my_bool DDL_exception; // Allow some DDL if there is an exception
+ Locked_tables_list locked_tables_list;
+
#ifdef WITH_PARTITION_STORAGE_ENGINE
partition_info *work_part_info;
#endif
@@ -1885,8 +1939,13 @@ public:
unsigned long audit_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
#endif
- 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();
=== modified file 'sql/sql_db.cc'
--- a/sql/sql_db.cc 2008-06-04 11:26:57 +0000
+++ b/sql/sql_db.cc 2008-06-11 11:49:58 +0000
@@ -1925,8 +1925,8 @@ bool mysql_upgrade_db(THD *thd, LEX_STRI
/*
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);
=== modified file 'sql/sql_delete.cc'
--- a/sql/sql_delete.cc 2008-06-05 06:48:36 +0000
+++ b/sql/sql_delete.cc 2008-06-11 04:33:36 +0000
@@ -1046,7 +1046,8 @@ bool mysql_truncate(THD *thd, TABLE_LIST
if (mdl_acquire_exclusive_locks(&thd->mdl_context))
DBUG_RETURN(TRUE);
pthread_mutex_lock(&LOCK_open);
- expel_table_from_cache(0, table_list->db, table_list->table_name);
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table_list->db,
+ table_list->table_name);
pthread_mutex_unlock(&LOCK_open);
}
=== modified file 'sql/sql_insert.cc'
--- a/sql/sql_insert.cc 2008-06-09 10:01:19 +0000
+++ b/sql/sql_insert.cc 2008-06-11 15:37:09 +0000
@@ -3456,16 +3456,22 @@ static TABLE *create_table_from_items(TH
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
{
=== modified file 'sql/sql_parse.cc'
--- a/sql/sql_parse.cc 2008-06-08 18:13:58 +0000
+++ b/sql/sql_parse.cc 2008-06-11 11:49:58 +0000
@@ -136,7 +136,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;
@@ -3273,7 +3273,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);
@@ -3328,29 +3328,27 @@ end_with_restore_list:
*/
if (check_transactional_lock(thd, all_tables))
goto error;
- 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;
- thd->in_lock_tables=1;
+
+ alloc_mdl_locks(all_tables, thd->locked_tables_list.locked_tables_root());
+
thd->options|= OPTION_TABLE_LOCK;
- alloc_mdl_locks(all_tables, &thd->locked_tables_root);
- thd->mdl_el_root= &thd->locked_tables_root;
+ thd->in_lock_tables=1;
+ thd->locked_tables_root= thd->locked_tables_list.locked_tables_root();
- if (!(res= open_and_lock_tables_derived(thd, all_tables, FALSE,
- MYSQL_OPEN_TAKE_UPGRADABLE_MDL)))
- {
-#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;
- (void) set_handler_table_locks(thd, all_tables, FALSE);
- my_ok(thd);
- }
- else
+ 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)
{
- /*
+ /*
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
@@ -3359,8 +3357,15 @@ 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*/
+ (void) set_handler_table_locks(thd, all_tables, FALSE);
+ my_ok(thd);
+ }
break;
case SQLCOM_CREATE_DB:
{
@@ -6140,8 +6145,8 @@ TABLE_LIST *st_select_lex::add_table_to_
/* 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);
}
=== modified file 'sql/sql_partition.cc'
--- a/sql/sql_partition.cc 2008-06-06 19:19:04 +0000
+++ b/sql/sql_partition.cc 2008-06-11 11:49:58 +0000
@@ -3953,7 +3953,6 @@ set_engine_all_partitions(partition_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)
@@ -3972,11 +3971,7 @@ static int fast_end_partition(THD *thd,
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))
@@ -4154,7 +4149,7 @@ uint prep_alter_part_table(THD *thd, TAB
without any changes at all.
*/
DBUG_RETURN(fast_end_partition(thd, ULL(0), ULL(0),
- table, NULL,
+ NULL,
TRUE, NULL, FALSE));
}
else if (new_part_no > curr_part_no)
@@ -5781,30 +5776,13 @@ static void release_log_entries(partitio
*/
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");
}
/*
@@ -5818,17 +5796,37 @@ static void alter_partition_lock_handlin
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);
}
@@ -5995,6 +5993,7 @@ uint fast_alter_partition_table(THD *thd
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;
@@ -6349,7 +6348,7 @@ uint fast_alter_partition_table(THD *thd
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);
=== modified file 'sql/sql_rename.cc'
--- a/sql/sql_rename.cc 2008-06-06 19:19:04 +0000
+++ b/sql/sql_rename.cc 2008-06-11 04:33:36 +0000
@@ -139,7 +139,8 @@ bool mysql_rename_tables(THD *thd, TABLE
pthread_mutex_lock(&LOCK_open);
for (ren_table= table_list; ren_table; ren_table= ren_table->next_local)
- expel_table_from_cache(0, ren_table->db, ren_table->table_name);
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, ren_table->db,
+ ren_table->table_name);
error=0;
if ((ren_table=rename_tables(thd,table_list,0)))
=== modified file 'sql/sql_servers.cc'
--- a/sql/sql_servers.cc 2008-05-23 13:54:03 +0000
+++ b/sql/sql_servers.cc 2008-06-11 11:49:58 +0000
@@ -223,7 +223,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);
=== modified file 'sql/sql_show.cc'
--- a/sql/sql_show.cc 2008-06-06 10:28:58 +0000
+++ b/sql/sql_show.cc 2008-06-11 11:53:53 +0000
@@ -3070,6 +3070,56 @@ static uint get_table_open_method(TABLE_
/**
+ Acquire high priority share metadata lock on a table.
+
+ @param thd Thread context.
+ @param mdl_lock_data Pointer to memory to be used for MDL_LOCK_DATA
+ object for a lock request.
+ @param mdlkey Pointer to the buffer for key for the lock request
+ (should be at least strlen(db) + strlen(name) + 2
+ bytes, or, if the lengths are not known,
+ MAX_DBNAME_LENGTH)
+ @param table Table list element for the table
+
+ @note This is an auxiliary function to be used in cases when we want to
+ access table's description by looking up info in TABLE_SHARE without
+ going through full-blown table open.
+ @note This function assumes that there are no other metadata lock requests
+ in the current metadata locking context.
+
+ @retval FALSE Success
+ @retval TRUE Some error occured (probably thread was killed).
+*/
+
+static bool
+acquire_high_prio_shared_mdl_lock(THD *thd, MDL_LOCK_DATA *mdl_lock_data,
+ char *mdlkey, TABLE_LIST *table)
+{
+ bool retry;
+
+ mdl_init_lock(mdl_lock_data, mdlkey, 0, table->db, table->table_name);
+ table->mdl_lock_data= mdl_lock_data;
+ mdl_add_lock(&thd->mdl_context, mdl_lock_data);
+ mdl_set_lock_type(mdl_lock_data, MDL_SHARED_HIGH_PRIO);
+
+ while (1)
+ {
+ if (mdl_acquire_shared_lock(&thd->mdl_context, mdl_lock_data, &retry))
+ {
+ if (!retry || mdl_wait_for_locks(&thd->mdl_context))
+ {
+ mdl_remove_all_locks(&thd->mdl_context);
+ return TRUE;
+ }
+ continue;
+ }
+ break;
+ }
+ return FALSE;
+}
+
+
+/**
@brief Fill I_S table with data from FRM file only
@param[in] thd thread handler
@@ -3102,39 +3152,27 @@ static int fill_schema_table_from_frm(TH
uint key_length;
MDL_LOCK_DATA mdl_lock_data;
char mdlkey[MAX_DBKEY_LENGTH];
- bool retry;
bzero((char*) &table_list, sizeof(TABLE_LIST));
bzero((char*) &tbl, sizeof(TABLE));
table_list.table_name= table_name->str;
table_list.db= db_name->str;
- mdl_init_lock(&mdl_lock_data, mdlkey, 0, db_name->str, table_name->str);
- table_list.mdl_lock_data= &mdl_lock_data;
- mdl_add_lock(&thd->mdl_context, &mdl_lock_data);
- mdl_set_lock_type(&mdl_lock_data, MDL_SHARED_HIGH_PRIO);
/*
TODO: investigate if in this particular situation we can get by
simply obtaining internal lock of data-dictionary (ATM it
is LOCK_open) instead of obtaning full-blown metadata lock.
*/
- while (1)
+ if (acquire_high_prio_shared_mdl_lock(thd, &mdl_lock_data, mdlkey,
+ &table_list))
{
- if (mdl_acquire_shared_lock(&mdl_lock_data, &retry))
- {
- if (!retry || mdl_wait_for_locks(&thd->mdl_context))
- {
- /*
- Some error occured or we have been killed while waiting
- for conflicting locks to go away, let the caller to handle
- the situation.
- */
- return 1;
- }
- continue;
- }
- break;
+ /*
+ Some error occured (most probably we have been killed while
+ waiting for conflicting locks to go away), let the caller to
+ handle the situation.
+ */
+ return 1;
}
if (schema_table->i_s_requested_object & OPEN_TRIGGER_ONLY)
=== modified file 'sql/sql_table.cc'
--- a/sql/sql_table.cc 2008-06-09 10:01:19 +0000
+++ b/sql/sql_table.cc 2008-06-11 15:37:09 +0000
@@ -1588,7 +1588,7 @@ int mysql_rm_table_part2(THD *thd, TABLE
DBUG_RETURN(1);
pthread_mutex_lock(&LOCK_open);
for (table= tables; table; table= table->next_local)
- expel_table_from_cache(0, table->db, table->table_name);
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name);
pthread_mutex_unlock(&LOCK_open);
}
else
@@ -1641,7 +1641,7 @@ int mysql_rm_table_part2(THD *thd, TABLE
case -1:
DBUG_ASSERT(thd->in_sub_stmt);
error= 1;
- goto err_with_placeholders;
+ goto err;
default:
// temporary table not found
error= 0;
@@ -1678,18 +1678,19 @@ int mysql_rm_table_part2(THD *thd, TABLE
{
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 */
@@ -1823,7 +1824,7 @@ int mysql_rm_table_part2(THD *thd, TABLE
*/
}
}
-err_with_placeholders:
+err:
if (!drop_temporary)
{
/*
@@ -1840,7 +1841,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)
@@ -3930,7 +3931,10 @@ static int prepare_for_repair(THD *thd,
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);
@@ -3966,8 +3970,9 @@ static int prepare_for_repair(THD *thd,
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
{
@@ -4028,8 +4033,9 @@ static int prepare_for_repair(THD *thd,
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;
}
/*
@@ -4057,21 +4063,23 @@ static int prepare_for_repair(THD *thd,
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);
@@ -4767,6 +4775,11 @@ bool mysql_create_like_table(THD* thd, T
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););
@@ -4874,20 +4887,16 @@ bool mysql_create_like_table(THD* thd, T
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);
IF_DBUG(int result=) store_create_info(thd, table, &query,
create_info);
@@ -4895,8 +4904,14 @@ bool mysql_create_like_table(THD* thd, T
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
@@ -5666,95 +5681,88 @@ TABLE *create_altered_table(THD *thd,
/*
Perform a fast or on-line alter table
- SYNOPSIS
- mysql_fast_or_online_alter_table()
- thd Thread handle
- table The original table
- altered_table A temporary table showing how we will change table
- create_info Information from the parsing phase about new
- table properties.
- alter_info Storage place for data used during different phases
- ha_alter_flags Bitmask that shows what will be changed
- keys_onoff Specifies if keys are to be enabled/disabled
- RETURN
- 0 OK
- >0 An error occured during the on-line alter table operation
- -1 Error when re-opening table
- NOTES
- If mysql_alter_table does not need to copy the table, it is
- either a fast alter table where the storage engine does not
- need to know about the change, only the frm will change,
- or the storage engine supports performing the alter table
- operation directly, on-line without mysql having to copy
- the table.
+ @param thd Thread handle
+ @param table_list The original table
+ @param altered_table A temporary table showing how we will change table
+ @param create_info Information from the parsing phase about new
+ table properties.
+ @param alter_info Storage place for data used during different phases
+ @param ha_alter_flags Bitmask that shows what will be changed
+ @param keys_onoff Specifies if keys are to be enabled/disabled
+
+ If mysql_alter_table() does not need to copy the table, it is
+ either a fast alter table, where the storage engine does not
+ need to know about the change, only the frm will change,
+ or the storage engine supports performing the alter table
+ operation directly, on-line, without mysql having to copy
+ the table.
+
+ @retval 0 success
+ @retval 1 error, that happened before we took an
+ exclusive metadata lock
+ @retval 2 error, that happened after we took an
+ exclusive metadata lock
*/
-int mysql_fast_or_online_alter_table(THD *thd,
- TABLE *table,
- TABLE *altered_table,
- HA_CREATE_INFO *create_info,
- HA_ALTER_INFO *alter_info,
- HA_ALTER_FLAGS *ha_alter_flags,
- enum enum_enable_or_disable keys_onoff)
+
+int
+mysql_fast_or_online_alter_table(THD *thd,
+ TABLE_LIST *table_list,
+ TABLE *altered_table,
+ HA_CREATE_INFO *create_info,
+ HA_ALTER_INFO *alter_info,
+ HA_ALTER_FLAGS *ha_alter_flags,
+ enum enum_enable_or_disable keys_onoff)
{
- int error= 0;
- bool online= (table->file->ha_table_flags() & HA_ONLINE_ALTER)?true:false;
- TABLE *t_table;
+ TABLE *table= table_list->table;
+ bool is_online_alter= table->file->ha_table_flags() & HA_ONLINE_ALTER;
+ int error;
- DBUG_ENTER(" mysql_fast_or_online_alter_table");
- if (online)
+ DBUG_ENTER("mysql_fast_or_online_alter_table");
+
+ if (is_online_alter)
{
- /*
- Tell the handler to prepare for the online alter
- */
- if ((error= table->file->alter_table_phase1(thd,
- altered_table,
- create_info,
- alter_info,
- ha_alter_flags)))
- {
- goto err;
- }
+ /* Tell the storage engine to prepare for the online ALTER. */
+ if (table->file->alter_table_phase1(thd, altered_table,
+ create_info, alter_info,
+ ha_alter_flags))
+ DBUG_RETURN(1);
/*
- Tell the storage engine to perform the online alter table
- TODO:
- if check_if_supported_alter() returned HA_ALTER_SUPPORTED_WAIT_LOCK
- we need to wrap the next call with a DDL lock.
- */
- if ((error= table->file->alter_table_phase2(thd,
- altered_table,
- create_info,
- alter_info,
- ha_alter_flags)))
- {
- goto err;
- }
+ Tell the storage engine to perform the online ALTER.
+ @todo If check_if_supported_alter() returns
+ HA_ALTER_SUPPORTED_WAIT_LOCK we need to wrap the next call
+ with a DDL lock.
+ */
+ if (table->file->alter_table_phase2(thd, altered_table,
+ create_info, alter_info,
+ ha_alter_flags))
+ DBUG_RETURN(1);
}
+
/*
- The final .frm file is already created as a temporary file
- and will be renamed to the original table name.
+ Upgrade the shared metadata lock to exclusive and close all
+ instances of the table in the TDC except those used in this thread.
*/
if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
- {
- error= 1;
- goto err;
- }
- pthread_mutex_lock(&LOCK_open);
+ DBUG_RETURN(1);
+
alter_table_manage_keys(table, table->file->indexes_are_disabled(),
keys_onoff);
- close_data_files_and_leave_as_placeholders(thd, table->pos_in_table_list->db,
- table->pos_in_table_list->table_name);
- if (mysql_rename_table(NULL,
- altered_table->s->db.str,
- altered_table->s->table_name.str,
- table->s->db.str,
- table->s->table_name.str, FN_FROM_IS_TMP))
- {
- error= 1;
- pthread_mutex_unlock(&LOCK_open);
- goto err;
- }
- broadcast_refresh();
+
+ /* Now close all open instances of the table in this thread */
+ close_all_tables_for_name(thd, table->s, FALSE);
+ table_list->table= 0; /* The table was closed */
+
+ /*
+ The final .frm file is already created as a temporary file.
+ Let's rename it to the original table name.
+ */
+ pthread_mutex_lock(&LOCK_open);
+ error= mysql_rename_table(NULL,
+ altered_table->s->db.str,
+ altered_table->s->table_name.str,
+ table_list->db, table_list->table_name,
+ FN_FROM_IS_TMP);
pthread_mutex_unlock(&LOCK_open);
/*
@@ -5764,45 +5772,38 @@ int mysql_fast_or_online_alter_table(THD
with LOCK_open.
*/
error= ha_autocommit_or_rollback(thd, 0);
-
if (ha_commit(thd))
- error=1;
+ error= 1;
+
if (error)
- goto err;
- if (online)
- {
- pthread_mutex_lock(&LOCK_open);
- if (reopen_table(table))
- {
- error= -1;
- goto err;
- }
- pthread_mutex_unlock(&LOCK_open);
- t_table= table;
+ DBUG_RETURN(2);
- /*
- Tell the handler that the changed frm is on disk and table
- has been re-opened
- */
- if ((error= t_table->file->alter_table_phase3(thd, t_table)))
- {
- goto err;
- }
+ if (is_online_alter)
+ {
+ 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, table_list, thd->mem_root, &ot_action_unused,
+ MYSQL_OPEN_REOPEN))
+ DBUG_RETURN(2);
+ table= table_list->table;
/*
- 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.
+ Tell the handler that the changed .frm is on disk and the
+ table has been re-opened successfully.
*/
- DBUG_ASSERT(t_table == table);
+ if (table->file->alter_table_phase3(thd, table))
+ error= 2;
+
+ DBUG_ASSERT(thd->open_tables == table);
pthread_mutex_lock(&LOCK_open);
- close_handle_and_leave_table_as_placeholder(table);
+ close_thread_table(thd, &thd->open_tables);
pthread_mutex_unlock(&LOCK_open);
+ table_list->table= 0;
}
-
- err:
- if (error)
- DBUG_PRINT("info", ("Got error %u", error));
+ DBUG_ASSERT(table_list->table == 0);
DBUG_RETURN(error);
}
@@ -6575,13 +6576,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
@@ -6810,32 +6812,24 @@ view_err:
if (!need_copy_table)
{
error= mysql_fast_or_online_alter_table(thd,
- table,
+ table_list,
altered_table,
create_info,
&ha_alter_info,
&ha_alter_flags,
alter_info->keys_onoff);
- if (thd->lock && !thd->locked_tables_mode)
- {
- mysql_unlock_tables(thd, thd->lock);
- thd->lock=0;
- }
close_temporary_table(thd, altered_table, 1, 1);
- if (error)
- {
- switch (error) {
- case(-1):
- goto err_with_placeholders;
- default:
- goto err;
- }
- }
- else
- {
- pthread_mutex_lock(&LOCK_open);
+ switch (error) {
+ case 0:
goto end_online;
+ case 1:
+ goto err;
+ case 2:
+ goto err_with_mdl;
+ default:
+ DBUG_ASSERT(0);
+ goto err;
}
}
@@ -6971,11 +6965,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;
/*
@@ -6991,6 +6986,7 @@ view_err:
table is renamed and the SE is also changed, then an intermediate table
is created and the additional call will not take place.
*/
+ pthread_mutex_lock(&LOCK_open);
if (mysql_rename_table(old_db_type, db, table_name, db, old_name,
FN_TO_IS_TMP))
{
@@ -7011,25 +7007,18 @@ view_err:
FN_FROM_IS_TMP);
}
- if (error)
- {
- /* This shouldn't happen. But let us play it safe. */
- goto err_with_placeholders;
- }
-
+ if (! error)
(void) quick_rm_table(old_db_type, db, old_name, FN_IS_TMP);
-end_online:
- 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;
- }
pthread_mutex_unlock(&LOCK_open);
+ if (error)
+ goto err_with_mdl;
+
+end_online:
+ if (thd->locked_tables_list.reopen_tables(thd))
+ goto err_with_mdl;
+
thd_proc_info(thd, "end");
DBUG_EXECUTE_IF("sleep_alter_before_main_binlog", my_sleep(6000000););
@@ -7071,9 +7060,6 @@ end_online:
{
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);
}
@@ -7135,14 +7121,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);
=== modified file 'sql/sql_trigger.cc'
--- a/sql/sql_trigger.cc 2008-06-06 19:19:04 +0000
+++ b/sql/sql_trigger.cc 2008-06-11 11:49:58 +0000
@@ -328,6 +328,7 @@ bool mysql_create_or_drop_trigger(THD *t
bool result= TRUE;
String stmt_query;
bool need_start_waiting= FALSE;
+ bool lock_upgrade_done= FALSE;
DBUG_ENTER("mysql_create_or_drop_trigger");
@@ -450,71 +451,53 @@ bool mysql_create_or_drop_trigger(THD *t
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);
- expel_table_from_cache(0, 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));
+ pthread_mutex_unlock(&LOCK_open);
- /* 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;
- }
+ if (result)
+ goto end;
-end_unlock:
- pthread_mutex_unlock(&LOCK_open);
+ 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);
=== modified file 'sql/table.h'
--- a/sql/table.h 2008-06-05 15:33:38 +0000
+++ b/sql/table.h 2008-06-11 11:49:58 +0000
@@ -626,6 +626,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;
| Thread |
|---|
| • bzr commit into mysql-6.0 branch (dlenev:2667) WL#3726 | Dmitry Lenev | 11 Jun |