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,
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 name_lock_locked_table(THD *thd, TABLE_LIST *tables);
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,
......
......@@ -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.
......@@ -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. */
DBUG_ASSERT(thd->lex->is_lex_started);
/* find a unused table in the open table cache */
if (action)
*action= OT_NO_ACTION;
/* 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,
enum legacy_db_type not_used;
build_table_filename(path, sizeof(path) - 1,
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 (!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,
}
/*
Non pre-locked/LOCK TABLES mode, and the table is not temporary:
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.
Non pre-locked/LOCK TABLES mode, and the table is not temporary.
This is the normal use case.
*/
mdl_lock= table_list->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);
/* TODO: This case can be significantly optimized. */
if (mdl_acquire_exclusive_locks(&thd->mdl_context))
DBUG_RETURN(0);
}
......@@ -2776,7 +2745,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
MDL_HIGH_PRIO : MDL_NORMAL_PRIO);
if (mdl_acquire_shared_lock(mdl_lock, &retry))
{
if (action && retry)
if (retry)
*action= OT_BACK_OFF_AND_RETRY;
DBUG_RETURN(0);
}
......@@ -2798,13 +2767,12 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
! (flags & MYSQL_LOCK_IGNORE_FLUSH))
{
/* Someone did a refresh while thread was opening tables */
if (action)
*action= OT_BACK_OFF_AND_RETRY;
pthread_mutex_unlock(&LOCK_open);
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;
......@@ -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. */
}
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);
DBUG_RETURN(0);
......@@ -2926,7 +2894,16 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
{
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;
release_table_share(share);
pthread_mutex_unlock(&LOCK_open);
......@@ -2966,8 +2943,6 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
{
my_free(table, MYF(0));
if (action)
{
if (error == 7)
{
share->version= 0;
......@@ -2978,7 +2953,6 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
share->version= 0;
*action= OT_REPAIR;
}
}
goto err_unlock;
}
......@@ -2996,16 +2970,19 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
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);
table->mdl_lock= mdl_lock;
if (action)
{
table->next=thd->open_tables; /* Link into simple list */
thd->open_tables=table;
}
table->reginfo.lock_type=TL_READ; /* Assume read */
reset:
......@@ -3856,8 +3833,8 @@ static bool reopen_table_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list,
/**
Auxiliary routine which finalizes process of TABLE object creation
by loading triggers and handling implicitly emptied tables.
Finalize the process of TABLE creation by loading table triggers
and taking action if a HEAP table content was emptied implicitly.
*/
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)
table and successful table creation.
...
*/
if (tables->open_table_type)
if (tables->open_type)
continue;
if (action)
......
......@@ -798,6 +798,7 @@ void mysql_ha_flush(THD *thd)
for (uint i= 0; i < thd->handler_tables_hash.records; 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 &&
(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,
Item *item;
Field *tmp_field;
bool not_used;
enum_open_table_action not_used2;
DBUG_ENTER("create_table_from_items");
tmp_table.alias= 0;
......@@ -3544,8 +3545,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
}
else
{
if (!(table= open_table(thd, create_table, thd->mem_root,
(enum_open_table_action*) 0,
if (!(table= open_table(thd, create_table, thd->mem_root, &not_used2,
MYSQL_OPEN_TEMPORARY_ONLY)) &&
!create_info->table_existed)
{
......
......@@ -2631,7 +2631,7 @@ case SQLCOM_PREPARE:
if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE))
{
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)))
......
......@@ -1673,7 +1673,7 @@ static bool mysql_test_create_table(Prepared_statement *stmt)
if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE))
{
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))
......
......@@ -7208,12 +7208,13 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
{
if (table->s->tmp_table)
{
enum_open_table_action not_used;
TABLE_LIST tbl;
bzero((void*) &tbl, sizeof(tbl));
tbl.db= new_db;
tbl.table_name= tbl.alias= tmp_name;
/* 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);
}
else
......
......@@ -446,8 +446,17 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
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;
/*
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);
}
else
......
......@@ -395,7 +395,7 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views,
goto err;
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))
{
......
......@@ -1349,14 +1349,26 @@ struct TABLE_LIST
used for implicit LOCK TABLES only and won't be used in real statement.
*/
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
special handling/meta-data locking. For example this is a target
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.
It's target view of CREATE/ALTER VIEW. We should take exclusive
metadata lock for this table list element.
*/
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
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