Commit f56cc2a3 authored by Konstantin Osipov's avatar Konstantin Osipov

Backport of:

------------------------------------------------------------
revno: 2630.4.16
committer: Dmitry Lenev <dlenev@mysql.com>
branch nick: mysql-6.0-3726-w
timestamp: Thu 2008-05-29 09:45:02 +0400
message:
  WL#3726 "DDL locking for all metadata objects".

  After review changes in progress.

  Tweaked some comments and did some renames to
  avoid ambiguites.
parent 3d19fdad
...@@ -1227,7 +1227,6 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT* mem, ...@@ -1227,7 +1227,6 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT* mem,
bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias, bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias,
char *cache_key, uint cache_key_length, char *cache_key, uint cache_key_length,
MEM_ROOT *mem_root, uint flags); MEM_ROOT *mem_root, uint flags);
bool name_lock_locked_table(THD *thd, TABLE_LIST *tables);
bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list); 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_locked_table(TABLE *list, const char *db, const char *table_name);
TABLE *find_write_locked_table(TABLE *list, const char *db, TABLE *find_write_locked_table(TABLE *list, const char *db,
......
...@@ -2340,39 +2340,6 @@ void wait_for_condition(THD *thd, pthread_mutex_t *mutex, pthread_cond_t *cond) ...@@ -2340,39 +2340,6 @@ void wait_for_condition(THD *thd, pthread_mutex_t *mutex, pthread_cond_t *cond)
} }
/**
Exclusively name-lock a table that is already write-locked by the
current thread.
@param thd current thread context
@param tables table list containing one table to open.
@return FALSE on success, TRUE otherwise.
*/
bool name_lock_locked_table(THD *thd, TABLE_LIST *tables)
{
bool result= TRUE;
DBUG_ENTER("name_lock_locked_table");
/* Under LOCK TABLES we must only accept write locked tables. */
tables->table= find_write_locked_table(thd->open_tables, tables->db,
tables->table_name);
if (tables->table)
{
/*
Ensures that table is opened only by this thread and that no
other statement will open this table.
*/
result= wait_while_table_is_used(thd, tables->table, HA_EXTRA_FORCE_REOPEN);
}
DBUG_RETURN(result);
}
/* /*
Open table for which this thread has exclusive meta-data lock. Open table for which this thread has exclusive meta-data lock.
...@@ -2576,8 +2543,6 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, ...@@ -2576,8 +2543,6 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
/* Parsing of partitioning information from .frm needs thd->lex set up. */ /* Parsing of partitioning information from .frm needs thd->lex set up. */
DBUG_ASSERT(thd->lex->is_lex_started); DBUG_ASSERT(thd->lex->is_lex_started);
/* find a unused table in the open table cache */
if (action)
*action= OT_NO_ACTION; *action= OT_NO_ACTION;
/* an open table operation needs a lot of the stack space */ /* an open table operation needs a lot of the stack space */
...@@ -2716,6 +2681,13 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, ...@@ -2716,6 +2681,13 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
enum legacy_db_type not_used; enum legacy_db_type not_used;
build_table_filename(path, sizeof(path) - 1, build_table_filename(path, sizeof(path) - 1,
table_list->db, table_list->table_name, reg_ext, 0); table_list->db, table_list->table_name, reg_ext, 0);
/*
Note that we can't be 100% sure that it is a view since it's
possible that we either simply have not found unused TABLE
instance in THD::open_tables list or were unable to open table
during prelocking process (in this case in theory we still
should hold shared metadata lock on it).
*/
if (mysql_frm_type(thd, path, &not_used) == FRMTYPE_VIEW) if (mysql_frm_type(thd, path, &not_used) == FRMTYPE_VIEW)
{ {
if (!tdc_open_view(thd, table_list, alias, key, key_length, if (!tdc_open_view(thd, table_list, alias, key, key_length,
...@@ -2741,28 +2713,25 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, ...@@ -2741,28 +2713,25 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
} }
/* /*
Non pre-locked/LOCK TABLES mode, and the table is not temporary: Non pre-locked/LOCK TABLES mode, and the table is not temporary.
this is the normal use case. This is the normal use case.
Now we should:
- try to find the table in the table cache.
- if one of the discovered TABLE instances is name-locked
(table->s->version == 0) or some thread has started FLUSH TABLES
(refresh_version > table->s->version), back off -- we have to wait
until no one holds a name lock on the table.
- if there is no such TABLE in the name cache, read the table definition
and insert it into the cache.
We perform all of the above under LOCK_open which currently protects
the open cache (also known as table cache) and table definitions stored
on disk.
*/ */
mdl_lock= table_list->mdl_lock; mdl_lock= table_list->mdl_lock;
mdl_add_lock(&thd->mdl_context, mdl_lock); mdl_add_lock(&thd->mdl_context, mdl_lock);
if (table_list->open_table_type) 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, MDL_EXCLUSIVE); mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE);
/* TODO: This case can be significantly optimized. */
if (mdl_acquire_exclusive_locks(&thd->mdl_context)) if (mdl_acquire_exclusive_locks(&thd->mdl_context))
DBUG_RETURN(0); DBUG_RETURN(0);
} }
...@@ -2776,7 +2745,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, ...@@ -2776,7 +2745,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
MDL_HIGH_PRIO : MDL_NORMAL_PRIO); MDL_HIGH_PRIO : MDL_NORMAL_PRIO);
if (mdl_acquire_shared_lock(mdl_lock, &retry)) if (mdl_acquire_shared_lock(mdl_lock, &retry))
{ {
if (action && retry) if (retry)
*action= OT_BACK_OFF_AND_RETRY; *action= OT_BACK_OFF_AND_RETRY;
DBUG_RETURN(0); DBUG_RETURN(0);
} }
...@@ -2798,13 +2767,12 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, ...@@ -2798,13 +2767,12 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
! (flags & MYSQL_LOCK_IGNORE_FLUSH)) ! (flags & MYSQL_LOCK_IGNORE_FLUSH))
{ {
/* Someone did a refresh while thread was opening tables */ /* Someone did a refresh while thread was opening tables */
if (action)
*action= OT_BACK_OFF_AND_RETRY; *action= OT_BACK_OFF_AND_RETRY;
pthread_mutex_unlock(&LOCK_open); pthread_mutex_unlock(&LOCK_open);
DBUG_RETURN(0); DBUG_RETURN(0);
} }
if (table_list->open_table_type == TABLE_LIST::OPEN_OR_CREATE) if (table_list->open_type == TABLE_LIST::OPEN_OR_CREATE)
{ {
bool exists; bool exists;
...@@ -2818,7 +2786,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, ...@@ -2818,7 +2786,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
} }
/* Table exists. Let us try to open it. */ /* Table exists. Let us try to open it. */
} }
else if (table_list->open_table_type == TABLE_LIST::TAKE_EXCLUSIVE_MDL) else if (table_list->open_type == TABLE_LIST::TAKE_EXCLUSIVE_MDL)
{ {
pthread_mutex_unlock(&LOCK_open); pthread_mutex_unlock(&LOCK_open);
DBUG_RETURN(0); DBUG_RETURN(0);
...@@ -2926,7 +2894,16 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, ...@@ -2926,7 +2894,16 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
{ {
if (!(flags & MYSQL_LOCK_IGNORE_FLUSH)) if (!(flags & MYSQL_LOCK_IGNORE_FLUSH))
{ {
if (action) /*
We already have an MDL lock. But we have encountered an old
version of table in the table definition cache which is possible
when someone changes the table version directly in the cache
without acquiring a metadata lock (e.g. this can happen during
"rolling" FLUSH TABLE(S)).
Note, that to avoid a "busywait" in this case, we have to wait
separately in the caller for old table versions to go away
(see tdc_wait_for_old_versions()).
*/
*action= OT_BACK_OFF_AND_RETRY; *action= OT_BACK_OFF_AND_RETRY;
release_table_share(share); release_table_share(share);
pthread_mutex_unlock(&LOCK_open); pthread_mutex_unlock(&LOCK_open);
...@@ -2966,8 +2943,6 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, ...@@ -2966,8 +2943,6 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
{ {
my_free(table, MYF(0)); my_free(table, MYF(0));
if (action)
{
if (error == 7) if (error == 7)
{ {
share->version= 0; share->version= 0;
...@@ -2978,7 +2953,6 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, ...@@ -2978,7 +2953,6 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
share->version= 0; share->version= 0;
*action= OT_REPAIR; *action= OT_REPAIR;
} }
}
goto err_unlock; goto err_unlock;
} }
...@@ -2996,16 +2970,19 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, ...@@ -2996,16 +2970,19 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
pthread_mutex_unlock(&LOCK_open); pthread_mutex_unlock(&LOCK_open);
// Table existed /*
if (table_list->open_table_type == TABLE_LIST::OPEN_OR_CREATE) In CREATE TABLE .. If NOT EXISTS .. SELECT we have found that
table exists now we should downgrade our exclusive metadata
lock on this table to shared metadata lock.
*/
if (table_list->open_type == TABLE_LIST::OPEN_OR_CREATE)
mdl_downgrade_exclusive_locks(&thd->mdl_context); mdl_downgrade_exclusive_locks(&thd->mdl_context);
table->mdl_lock= mdl_lock; table->mdl_lock= mdl_lock;
if (action)
{
table->next=thd->open_tables; /* Link into simple list */ table->next=thd->open_tables; /* Link into simple list */
thd->open_tables=table; thd->open_tables=table;
}
table->reginfo.lock_type=TL_READ; /* Assume read */ table->reginfo.lock_type=TL_READ; /* Assume read */
reset: reset:
...@@ -3856,8 +3833,8 @@ err: ...@@ -3856,8 +3833,8 @@ err:
/** /**
Auxiliary routine which finalizes process of TABLE object creation Finalize the process of TABLE creation by loading table triggers
by loading triggers and handling implicitly emptied tables. and taking action if a HEAP table content was emptied implicitly.
*/ */
static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry) static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry)
...@@ -4636,7 +4613,7 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) ...@@ -4636,7 +4613,7 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
table and successful table creation. table and successful table creation.
... ...
*/ */
if (tables->open_table_type) if (tables->open_type)
continue; continue;
if (action) if (action)
......
...@@ -798,6 +798,7 @@ void mysql_ha_flush(THD *thd) ...@@ -798,6 +798,7 @@ void mysql_ha_flush(THD *thd)
for (uint i= 0; i < thd->handler_tables_hash.records; i++) for (uint i= 0; i < thd->handler_tables_hash.records; i++)
{ {
hash_tables= (TABLE_LIST*) my_hash_element(&thd->handler_tables_hash, i); hash_tables= (TABLE_LIST*) my_hash_element(&thd->handler_tables_hash, i);
/* TABLE::mdl_lock is 0 for temporary tables so we need extra check. */
if (hash_tables->table && if (hash_tables->table &&
(hash_tables->table->mdl_lock && (hash_tables->table->mdl_lock &&
mdl_has_pending_conflicting_lock(hash_tables->table->mdl_lock) || mdl_has_pending_conflicting_lock(hash_tables->table->mdl_lock) ||
......
...@@ -3453,6 +3453,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, ...@@ -3453,6 +3453,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
Item *item; Item *item;
Field *tmp_field; Field *tmp_field;
bool not_used; bool not_used;
enum_open_table_action not_used2;
DBUG_ENTER("create_table_from_items"); DBUG_ENTER("create_table_from_items");
tmp_table.alias= 0; tmp_table.alias= 0;
...@@ -3544,8 +3545,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, ...@@ -3544,8 +3545,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
} }
else else
{ {
if (!(table= open_table(thd, create_table, thd->mem_root, if (!(table= open_table(thd, create_table, thd->mem_root, &not_used2,
(enum_open_table_action*) 0,
MYSQL_OPEN_TEMPORARY_ONLY)) && MYSQL_OPEN_TEMPORARY_ONLY)) &&
!create_info->table_existed) !create_info->table_existed)
{ {
......
...@@ -2631,7 +2631,7 @@ case SQLCOM_PREPARE: ...@@ -2631,7 +2631,7 @@ case SQLCOM_PREPARE:
if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE)) if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE))
{ {
lex->link_first_table_back(create_table, link_to_local); lex->link_first_table_back(create_table, link_to_local);
create_table->open_table_type= TABLE_LIST::OPEN_OR_CREATE; create_table->open_type= TABLE_LIST::OPEN_OR_CREATE;
} }
if (!(res= open_and_lock_tables(thd, lex->query_tables))) if (!(res= open_and_lock_tables(thd, lex->query_tables)))
......
...@@ -1673,7 +1673,7 @@ static bool mysql_test_create_table(Prepared_statement *stmt) ...@@ -1673,7 +1673,7 @@ static bool mysql_test_create_table(Prepared_statement *stmt)
if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE)) if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE))
{ {
lex->link_first_table_back(create_table, link_to_local); lex->link_first_table_back(create_table, link_to_local);
create_table->open_table_type= TABLE_LIST::OPEN_OR_CREATE; create_table->open_type= TABLE_LIST::OPEN_OR_CREATE;
} }
if (open_normal_and_derived_tables(stmt->thd, lex->query_tables, 0)) if (open_normal_and_derived_tables(stmt->thd, lex->query_tables, 0))
......
...@@ -7208,12 +7208,13 @@ view_err: ...@@ -7208,12 +7208,13 @@ view_err:
{ {
if (table->s->tmp_table) if (table->s->tmp_table)
{ {
enum_open_table_action not_used;
TABLE_LIST tbl; TABLE_LIST tbl;
bzero((void*) &tbl, sizeof(tbl)); bzero((void*) &tbl, sizeof(tbl));
tbl.db= new_db; tbl.db= new_db;
tbl.table_name= tbl.alias= tmp_name; tbl.table_name= tbl.alias= tmp_name;
/* Table is in thd->temporary_tables */ /* Table is in thd->temporary_tables */
new_table= open_table(thd, &tbl, thd->mem_root, (enum_open_table_action*) 0, new_table= open_table(thd, &tbl, thd->mem_root, &not_used,
MYSQL_LOCK_IGNORE_FLUSH); MYSQL_LOCK_IGNORE_FLUSH);
} }
else else
......
...@@ -446,8 +446,17 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) ...@@ -446,8 +446,17 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
if (thd->locked_tables) if (thd->locked_tables)
{ {
if (name_lock_locked_table(thd, tables)) /* Under LOCK TABLES we must only accept write locked tables. */
if (!(tables->table= find_write_locked_table(thd->open_tables, tables->db,
tables->table_name)))
goto end; 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); pthread_mutex_lock(&LOCK_open);
} }
else else
......
...@@ -395,7 +395,7 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, ...@@ -395,7 +395,7 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views,
goto err; goto err;
lex->link_first_table_back(view, link_to_local); lex->link_first_table_back(view, link_to_local);
view->open_table_type= TABLE_LIST::TAKE_EXCLUSIVE_MDL; view->open_type= TABLE_LIST::TAKE_EXCLUSIVE_MDL;
if (open_and_lock_tables(thd, lex->query_tables)) if (open_and_lock_tables(thd, lex->query_tables))
{ {
......
...@@ -1349,14 +1349,26 @@ struct TABLE_LIST ...@@ -1349,14 +1349,26 @@ struct TABLE_LIST
used for implicit LOCK TABLES only and won't be used in real statement. used for implicit LOCK TABLES only and won't be used in real statement.
*/ */
bool prelocking_placeholder; bool prelocking_placeholder;
/**
Indicates that if TABLE_LIST object corresponds to the table/view
which requires special handling/meta-data locking.
*/
enum
{
/* Normal open, shared metadata lock should be taken. */
NORMAL_OPEN= 0,
/*
It's target table of CREATE TABLE ... SELECT so we should
either open table if it exists (and take shared metadata lock)
or take exclusive metadata lock if it doesn't exist.
*/
OPEN_OR_CREATE,
/* /*
This TABLE_LIST object corresponds to the table/view which requires It's target view of CREATE/ALTER VIEW. We should take exclusive
special handling/meta-data locking. For example this is a target metadata lock for this table list element.
table in CREATE TABLE ... SELECT so it is possible that it does not
exist and we should take exclusive meta-data lock on it in this
case.
*/ */
enum {NORMAL_OPEN= 0, OPEN_OR_CREATE, TAKE_EXCLUSIVE_MDL} open_table_type; TAKE_EXCLUSIVE_MDL
} open_type;
/** /**
Indicates that for this table/view we need to take shared metadata Indicates that for this table/view we need to take shared metadata
lock which should be upgradable to exclusive metadata lock. lock which should be upgradable to exclusive metadata lock.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment