diff options
author | Dmitry Lenev <dlenev@mysql.com> | 2010-05-25 16:35:01 +0400 |
---|---|---|
committer | Dmitry Lenev <dlenev@mysql.com> | 2010-05-25 16:35:01 +0400 |
commit | bee0f214fdbb214471b3d043b2b00b6b8f2c1edd (patch) | |
tree | 5ca3eccef7fd43ef9cca0e1b7c7d9d307f34add2 /sql | |
parent | 705b98dff714298a9b9e77cab364a05f439c3b7b (diff) | |
download | mariadb-git-bee0f214fdbb214471b3d043b2b00b6b8f2c1edd.tar.gz |
Pre-requisite patch for bug #51263 "Deadlock between
transactional SELECT and ALTER TABLE ... REBUILD PARTITION".
The goal of this patch is to decouple type of metadata
lock acquired for table by open_tables() from type of
table-level lock to be acquired on it.
To achieve this we change approach to how we determine what
type of metadata lock should be acquired on table to be open.
Now instead of inferring it at open_tables() time from flags
and type of table-level lock we rely on that type of metadata
lock is properly set at parsing time and is not changed
further.
sql/ha_ndbcluster.cc:
Now one needs to properly initialize table list element's
MDL_request object before calling mysql_rm_table_part2().
sql/lock.cc:
lock_table_names() no longer initializes table list elements'
MDL_request objects. Now proper initialization of these
requests is a responsibility of the caller.
sql/lock.h:
Removed MYSQL_OPEN_TAKE_UPGRADABLE_MDL flag which became
unnecessary. Thanks to the fact that we don't reset type of
requests for metadata locks between re-executions we now can
figure out that upgradable locks are requested by simply
looking at their type which were set in the parser. As result
this flag became redundant.
sql/mdl.h:
Added version of new operator which simplifies allocation of
MDL_request objects on a MEM_ROOT.
sql/sp_head.cc:
Added comment explaining why it is OK to infer type of
metadata lock to request from type of table-level lock
for prelocking.
Added enum_mdl_type argument to sp_add_to_query_tables()
to simplify its usage in trigger implementation.
sql/sp_head.h:
Added enum_mdl_type argument to sp_add_to_query_tables()
to simplify its usage in trigger implementation.
sql/sql_base.cc:
- open_table_get_mdl_lock():
Preserve type of MDL_request for table list element which
was set in the parser by creating MDL_request objects on
memory root if MYSQL_OPEN_FORCE_SHARED_MDL or
MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL flag were specified.
Thanks to this and to the fact that we no longer reset
type of requests for metadata locks between re-executions
we no longer need to acquire exclusive metadata lock on
table to be created in a special way. This lock is acquired
by code handling acquiring of upgradable locks.
Also changed signature/calling convention for this function
to simplify its usage.
- Accordingly special lock strategy for table list elements
which was used for such locks became unnecessary and was
removed. Other strategies were renamed.
- Since we no longer have guarantee that MDL_request object
which were not satisfied due to lock conflict belongs to
table list element Open_table_context class and its methods
were extended to remember pointer to MDL_request which has
caused problem at request_backoff_action() time and use it
in recover_from_failed_open(). Similar approach is used
for cases when problem from which we need to recover is
not related to MDL but to the table itself. In this case
we store pointer to the element of table list.
- Changed open_tables()/open_tables_check_upgradable_mdl()/
open_tables_acquire_upgradable_mdl() not to rely on
MYSQL_OPEN_TAKE_UPGRADABLE_MDL flag to understand when
upgradable metadata locks should be acquired and not to
infer type of MDL lock from type of table-level lock.
Instead we assume that type of MDL to be acquired was set
in the parser (we can do this as type of MDL_request is
no longer reset between re-executions).
sql/sql_class.h:
Since we no longer have guarantee that MDL_request object
which were not satisfied due to lock conflict belongs to
table list element Open_table_context class and its methods
were extended to remember pointer to MDL_request which has
caused problem at request_backoff_action() time and use it
in recover_from_failed_open(). Similar approach is used
for cases when problem from which we need to recover is
not related to MDL but to the table itself. In this case
we store pointer to the element of table list.
sql/sql_db.cc:
Now one needs to properly initialize table list element's
MDL_request object before calling mysql_rm_table_part2()
or mysql_rename_tables().
sql/sql_lex.cc:
st_select_lex/st_select_lex_node::add_table_to_list() method
now has argument which allows specify type of metadata lock
to be requested for table list element being added.
sql/sql_lex.h:
- st_select_lex/st_select_lex_node::add_table_to_list()
method now has argument which specifies type of metadata
lock to be requested for table list element being added.
This allows to explicitly set type of MDL lock to be
acquired for a DDL statement in parser. It is also more
future-proof than inferring type of MDL request from type
of table-level lock.
- Added Yacc_state::m_mdl_type member which specifies which
type of metadata lock should be requested for tables to be
added to table list by a grammar rule in cases when the same
rule is used in several statements requiring different kinds
of metadata locks.
sql/sql_parse.cc:
- st_select_lex::add_table_to_list() method now has argument
which specifies type of metadata lock to be requested for
table list element being added. This allows to explicitly
set type of MDL lock to be acquired for a DDL statement in
parser. It is also more future-proof than inferring type of
MDL request from type of table-level lock.
- EXCLUSIVE_DOWNGRADABLE_MDL lock strategy has a new name -
OTLS_DOWNGRADE_IF_EXISTS.
- Adjusted LOCK TABLES implementation to the fact that we no
longer infer type of metadata lock to be acquired from table
level lock and that type of MDL request is set at parsing.
And thus MYSQL_OPEN_TAKE_UPGRADABLE_MDL flag became
unnecessary.
sql/sql_prepare.cc:
TABLE_LIST's lock strategy SHARED_MDL was renamed to OTLS_NONE
as now it means that metadata lock should not be changed during
call to open_table() (if it has been already acquired) and is
also used for exclusive metadata lock.
sql/sql_show.cc:
st_select_lex::add_table_to_list() method now has argument
which specifies type of metadata lock to be requested for
table list element being added.
sql/sql_table.cc:
- Adjusted mysql_admin_table()'s code to the fact that
open_tables() no longer determines what kind of metadata
lock should be obtained basing on type of table-level
lock and flags. Instead type of metadata lock for table
to be open should be set before calling open_tables().
- Changed mysql_alter_table() code to the facts:
a) that now it is responsibility of caller to properly
initalize MDL_request in table list elements before calling
lock_table_names()
b) and that MYSQL_OPEN_TAKE_UPGRADABLE_MDL is no longer
necessary since type of metadata lock to be obtained
at open_tables() time is set during parsing.
- Changed code of mysql_recreate_table() to properly set
type of metadata and table-level lock to be obtained
by mysql_alter_table() which it calls.
sql/sql_trigger.cc:
Instead of relying on MYSQL_OPEN_TAKE_UPGRADABLE_MDL flag to
force open_tables() to take an upgradable lock we now specify
exact type of lock to be taken when constructing table list
element for table to be open for CREATE/DROP TRIGGER.
sql/sql_view.cc:
We no longer use TABLE_LIST::EXCLUSIVE_MDL strategy to force
open_tables() to take an exclusive metadata lock on view to
be created. Instead we rely on parser setting proper type of
metadata lock to request and open_tables() acquiring it.
This became possible thanks to the fact that we no longer
reset type of MDL_request between statement re-executions.
sql/sql_yacc.yy:
Instead of inferring type of MDL_request for table to be
open from type of table-level lock and flags passed to
open_tables() we now explicitly specify them at parsing.
This became possible thanks to the fact that we no longer
reset type of MDL_request between statement re-executions.
In future this should allow to decouple type of metadata
lock from type of table-level lock.
The only exception to this approach is statements implemented
through mysql_admin_table() which re-uses same table list
element several times with different types of table-level
and metadata locks.
We now also properly initialize MDL_request objects for table
list elements which are later passed to lock_table_names()
function.
sql/table.cc:
Do not reset type of MDL_request between statement
re-executions. This became unnecessesary as we no longer
change type of MDL_request residing in table list element.
In its turn this change allows to set type of MDL_request
only once - at parsing time.
sql/table.h:
Got rid of TABLE_LIST::EXCLUSIVE_MDL lock strategy.
Now we can specify that we need to acquire exclusive lock
on table to be processed by open_tables() through setting
an appropriate type of MDL_request at parsing time (this
became possible thanks to the fact that we no longer reset
types of MDL_request's belonging to table list elements
between statement re-execution).
Strategy SHARED_MDL was renamed to OTLS_NONE as now it
means that metadata lock should not be changed during call
to open_table() (if it has been already acquired) and is
also used for exclusive metadata lock.
Strategy EXCLUSIVE_DOWNGRADABLE_MDL was renamed to
OTLS_DOWNGRADE_IF_EXISTS.
Diffstat (limited to 'sql')
-rw-r--r-- | sql/ha_ndbcluster.cc | 7 | ||||
-rw-r--r-- | sql/lock.cc | 7 | ||||
-rw-r--r-- | sql/lock.h | 15 | ||||
-rw-r--r-- | sql/mdl.h | 4 | ||||
-rw-r--r-- | sql/sp_head.cc | 11 | ||||
-rw-r--r-- | sql/sp_head.h | 4 | ||||
-rw-r--r-- | sql/sql_base.cc | 316 | ||||
-rw-r--r-- | sql/sql_class.h | 14 | ||||
-rw-r--r-- | sql/sql_db.cc | 8 | ||||
-rw-r--r-- | sql/sql_lex.cc | 1 | ||||
-rw-r--r-- | sql/sql_lex.h | 10 | ||||
-rw-r--r-- | sql/sql_parse.cc | 16 | ||||
-rw-r--r-- | sql/sql_prepare.cc | 2 | ||||
-rw-r--r-- | sql/sql_show.cc | 4 | ||||
-rw-r--r-- | sql/sql_table.cc | 26 | ||||
-rw-r--r-- | sql/sql_trigger.cc | 6 | ||||
-rw-r--r-- | sql/sql_view.cc | 2 | ||||
-rw-r--r-- | sql/sql_yacc.yy | 103 | ||||
-rw-r--r-- | sql/table.cc | 7 | ||||
-rw-r--r-- | sql/table.h | 19 |
20 files changed, 357 insertions, 225 deletions
diff --git a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc index 2d082cc71f6..2af2b064020 100644 --- a/sql/ha_ndbcluster.cc +++ b/sql/ha_ndbcluster.cc @@ -7408,9 +7408,10 @@ int ndbcluster_find_files(handlerton *hton, THD *thd, DBUG_PRINT("info", ("Remove table %s/%s", db, file_name_str)); // Delete the table and all related files TABLE_LIST table_list; - bzero((char*) &table_list,sizeof(table_list)); - table_list.db= (char*) db; - table_list.alias= table_list.table_name= (char*)file_name_str; + table_list.init_one_table(db, strlen(db), file_name_str, + strlen(file_name_str), file_name_str, + TL_WRITE); + table_list.mdl_request.set_tpye(MDL_EXCLUSIVE); (void)mysql_rm_table_part2(thd, &table_list, FALSE, /* if_exists */ FALSE, /* drop_temporary */ diff --git a/sql/lock.cc b/sql/lock.cc index 758ea6cf914..8e91bd9360e 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -880,6 +880,8 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, before calling it. Also it cannot be called while holding LOCK_open mutex. Both these invariants are enforced by asserts in MDL_context::acquire_locks(). + @note Initialization of MDL_request members of TABLE_LIST elements + is a responsibility of the caller. @retval FALSE Success. @retval TRUE Failure (OOM or thread was killed). @@ -894,12 +896,7 @@ bool lock_table_names(THD *thd, TABLE_LIST *table_list) global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE); for (lock_table= table_list; lock_table; lock_table= lock_table->next_local) - { - lock_table->mdl_request.init(MDL_key::TABLE, - lock_table->db, lock_table->table_name, - MDL_EXCLUSIVE); mdl_requests.push_front(&lock_table->mdl_request); - } mdl_requests.push_front(&global_request); diff --git a/sql/lock.h b/sql/lock.h index 19b23f1f42b..5425a6ccb13 100644 --- a/sql/lock.h +++ b/sql/lock.h @@ -15,33 +15,32 @@ typedef struct st_mysql_lock MYSQL_LOCK; #define MYSQL_OPEN_TEMPORARY_ONLY 0x0004 #define MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY 0x0008 #define MYSQL_LOCK_LOG_TABLE 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 +#define MYSQL_OPEN_HAS_MDL_LOCK 0x0020 /** If in locked tables mode, ignore the locked tables and get a new instance of the table. */ -#define MYSQL_OPEN_GET_NEW_TABLE 0x0080 +#define MYSQL_OPEN_GET_NEW_TABLE 0x0040 /** Don't look up the table in the list of temporary tables. */ -#define MYSQL_OPEN_SKIP_TEMPORARY 0x0100 +#define MYSQL_OPEN_SKIP_TEMPORARY 0x0080 /** Fail instead of waiting when conficting metadata lock is discovered. */ -#define MYSQL_OPEN_FAIL_ON_MDL_CONFLICT 0x0200 +#define MYSQL_OPEN_FAIL_ON_MDL_CONFLICT 0x0100 /** Open tables using MDL_SHARED lock instead of one specified in parser. */ -#define MYSQL_OPEN_FORCE_SHARED_MDL 0x0400 +#define MYSQL_OPEN_FORCE_SHARED_MDL 0x0200 /** Open tables using MDL_SHARED_HIGH_PRIO lock instead of one specified in parser. */ -#define MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL 0x0800 +#define MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL 0x0400 /** When opening or locking the table, use the maximum timeout (LONG_TIMEOUT = 1 year) rather than the user-supplied timeout value. */ -#define MYSQL_LOCK_IGNORE_TIMEOUT 0x1000 +#define MYSQL_LOCK_IGNORE_TIMEOUT 0x0800 /** Please refer to the internals manual. */ #define MYSQL_OPEN_REOPEN (MYSQL_OPEN_IGNORE_FLUSH |\ diff --git a/sql/mdl.h b/sql/mdl.h index 2fb21a5aa18..2e296b73057 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -308,6 +308,10 @@ public: MDL_key key; public: + static void *operator new(size_t size, MEM_ROOT *mem_root) throw () + { return alloc_root(mem_root, size); } + static void operator delete(void *ptr, MEM_ROOT *mem_root) {} + void init(MDL_key::enum_mdl_namespace namespace_arg, const char *db_arg, const char *name_arg, enum_mdl_type mdl_type_arg); diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 2e66aec91e5..2d5b648e82e 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -4010,6 +4010,11 @@ sp_head::add_used_tables_to_table_list(THD *thd, table->prelocking_placeholder= 1; table->belong_to_view= belong_to_view; table->trg_event_map= stab->trg_event_map; + /* + Since we don't allow DDL on base tables in prelocked mode it + is safe to infer the type of metadata lock from the type of + table lock. + */ table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name, table->lock_type >= TL_WRITE_ALLOW_WRITE ? MDL_SHARED_WRITE : MDL_SHARED_READ); @@ -4040,7 +4045,8 @@ sp_head::add_used_tables_to_table_list(THD *thd, TABLE_LIST * sp_add_to_query_tables(THD *thd, LEX *lex, const char *db, const char *name, - thr_lock_type locktype) + thr_lock_type locktype, + enum_mdl_type mdl_type) { TABLE_LIST *table; @@ -4055,8 +4061,7 @@ sp_add_to_query_tables(THD *thd, LEX *lex, table->select_lex= lex->current_select; table->cacheable_table= 1; table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name, - table->lock_type >= TL_WRITE_ALLOW_WRITE ? - MDL_SHARED_WRITE : MDL_SHARED_READ); + mdl_type); lex->add_to_query_tables(table); return table; diff --git a/sql/sp_head.h b/sql/sp_head.h index 165f88321a9..8975c239810 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -1346,7 +1346,9 @@ set_routine_security_ctx(THD *thd, sp_head *sp, bool is_proc, TABLE_LIST * sp_add_to_query_tables(THD *thd, LEX *lex, const char *db, const char *name, - thr_lock_type locktype); + thr_lock_type locktype, + enum_mdl_type mdl_type); + Item * sp_prepare_func_item(THD* thd, Item **it_addr); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 28633365e28..85017886d24 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2358,82 +2358,90 @@ void table_share_release_hook(void *share) /** - A helper function that acquires an MDL lock for a table - being opened. + Try to acquire an MDL lock for a table being opened. + + @param[in,out] thd Session context, to report errors. + @param[out] ot_ctx Open table context, to hold the back off + state. If we failed to acquire a lock + due to a lock conflict, we add the + failed request to the open table context. + @param[in,out] mdl_request A request for an MDL lock. + If we managed to acquire a ticket + (no errors or lock conflicts occurred), + contains a reference to it on + return. However, is not modified if MDL + lock type- modifying flags were provided. + @param[in] flags flags MYSQL_OPEN_FORCE_SHARED_MDL, + MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL or + MYSQL_OPEN_FAIL_ON_MDL_CONFLICT + @sa open_table(). + @param[out] mdl_ticket Only modified if there was no error. + If we managed to acquire an MDL + lock, contains a reference to the + ticket, otherwise is set to NULL. + + @retval TRUE An error occurred. + @retval FALSE No error, but perhaps a lock conflict, check mdl_ticket. */ static bool -open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, +open_table_get_mdl_lock(THD *thd, Open_table_context *ot_ctx, MDL_request *mdl_request, - Open_table_context *ot_ctx, - uint flags) + uint flags, + MDL_ticket **mdl_ticket) { - if (table_list->lock_strategy) + if (flags & (MYSQL_OPEN_FORCE_SHARED_MDL | + MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL)) { - MDL_request_list mdl_requests; - MDL_request *global_request; /* - 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. + MYSQL_OPEN_FORCE_SHARED_MDL flag means that we are executing + PREPARE for a prepared statement and want to override + the type-of-operation aware metadata lock which was set + in the parser/during view opening with a simple shared + metadata lock. + This is necessary to allow concurrent execution of PREPARE + and LOCK TABLES WRITE statement against the same table. + + MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL flag means that we open + the table in order to get information about it for one of I_S + queries and also want to override the type-of-operation aware + shared metadata lock which was set earlier (e.g. during view + opening) with a high-priority shared metadata lock. + This is necessary to avoid unnecessary waiting and extra + ER_WARN_I_S_SKIPPED_TABLE warnings when accessing I_S tables. + + These two flags are mutually exclusive. */ + DBUG_ASSERT(!(flags & MYSQL_OPEN_FORCE_SHARED_MDL) || + !(flags & MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL)); - mdl_request->set_type(MDL_EXCLUSIVE); - DBUG_ASSERT(! thd->mdl_context.has_locks() || - thd->handler_tables_hash.records || - thd->global_read_lock.is_acquired()); - - if (!(global_request= ot_ctx->get_global_mdl_request(thd))) - return 1; - - mdl_requests.push_front(mdl_request); - mdl_requests.push_front(global_request); + mdl_request= new (thd->mem_root) MDL_request(mdl_request); + if (mdl_request == NULL) + return TRUE; - if (thd->mdl_context.acquire_locks(&mdl_requests, ot_ctx->get_timeout())) - return 1; + mdl_request->set_type((flags & MYSQL_OPEN_FORCE_SHARED_MDL) ? + MDL_SHARED : MDL_SHARED_HIGH_PRIO); } - else - { - if (flags & MYSQL_OPEN_FORCE_SHARED_MDL) - { - /* - While executing PREPARE for prepared statement we override - type-of-operation aware type of shared metadata lock which - was set in the parser with simple shared metadata lock. - This is necessary to allow concurrent execution of PREPARE - and LOCK TABLES WRITE statement which locks one of the tables - used in the statement being prepared. - */ - DBUG_ASSERT(!(flags & (MYSQL_OPEN_TAKE_UPGRADABLE_MDL | - MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL))); - mdl_request->set_type(MDL_SHARED); - } - else if (flags & MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL) - { - DBUG_ASSERT(!(flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL)); - mdl_request->set_type(MDL_SHARED_HIGH_PRIO); - } - - ot_ctx->add_request(mdl_request); + ot_ctx->add_request(mdl_request); - if (thd->mdl_context.try_acquire_lock(mdl_request)) - return 1; + if (thd->mdl_context.try_acquire_lock(mdl_request)) + return TRUE; - if (mdl_request->ticket == NULL) + if (mdl_request->ticket == NULL) + { + if (flags & MYSQL_OPEN_FAIL_ON_MDL_CONFLICT) { - if (flags & MYSQL_OPEN_FAIL_ON_MDL_CONFLICT) - my_error(ER_WARN_I_S_SKIPPED_TABLE, MYF(0), table_list->db, table_list->table_name); - else - ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_MDL_LOCK); - return 1; + my_error(ER_WARN_I_S_SKIPPED_TABLE, MYF(0), + mdl_request->key.db_name(), mdl_request->key.name()); + return TRUE; } + if (ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_MDL_LOCK, + mdl_request, NULL)) + return TRUE; } - return 0; + *mdl_ticket= mdl_request->ticket; + return FALSE; } @@ -2468,11 +2476,9 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, is never opened. In both cases, metadata locks are always taken according to the lock strategy. - This function will take a exclusive metadata lock on the table if - TABLE_LIST::lock_strategy is EXCLUSIVE_DOWNGRADABLE_MDL or EXCLUSIVE_MDL. - If the lock strategy is EXCLUSIVE_DOWNGRADABLE_MDL and opening the table - is successful, the exclusive metadata lock is downgraded to a shared - lock. + If the lock strategy is OTLS_DOWNGRADE_IF_EXISTS and opening the table + is successful, the exclusive metadata lock acquired by the caller + is downgraded to a shared lock. RETURN TRUE Open failed. "action" parameter may contain type of action @@ -2490,7 +2496,6 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, char key[MAX_DBKEY_LENGTH]; uint key_length; char *alias= table_list->alias; - MDL_request *mdl_request; MDL_ticket *mdl_ticket; int error; TABLE_SHARE *share; @@ -2528,7 +2533,8 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (thd->version != refresh_version) { - (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_TDC); + (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_TDC, + NULL, NULL); DBUG_RETURN(TRUE); } } @@ -2701,23 +2707,25 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, This is the normal use case. */ - mdl_request= &table_list->mdl_request; if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK)) { - if (open_table_get_mdl_lock(thd, table_list, mdl_request, ot_ctx, flags)) + if (open_table_get_mdl_lock(thd, ot_ctx, &table_list->mdl_request, + flags, &mdl_ticket) || + mdl_ticket == NULL) { DEBUG_SYNC(thd, "before_open_table_wait_refresh"); DBUG_RETURN(TRUE); } DEBUG_SYNC(thd, "after_open_table_mdl_shared"); } - - /* - Grab reference to the granted MDL lock ticket. Must be done after - open_table_get_mdl_lock as the lock on the table might have been - acquired previously (MYSQL_OPEN_HAS_MDL_LOCK). - */ - mdl_ticket= mdl_request->ticket; + else + { + /* + Grab reference to the MDL lock ticket that was acquired + by the caller. + */ + mdl_ticket= table_list->mdl_request.ticket; + } hash_value= my_calc_hash(&table_def_cache, (uchar*) key, key_length); mysql_mutex_lock(&LOCK_open); @@ -2737,7 +2745,8 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, { /* Someone did a refresh while thread was opening tables */ mysql_mutex_unlock(&LOCK_open); - (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_TDC); + (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_TDC, + NULL, NULL); DBUG_RETURN(TRUE); } @@ -2878,7 +2887,8 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, */ release_table_share(share); mysql_mutex_unlock(&LOCK_open); - (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_TDC); + (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_TDC, + NULL, NULL); DBUG_RETURN(TRUE); } /* Force close at once after usage */ @@ -2918,12 +2928,14 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (error == 7) { share->version= 0; - (void) ot_ctx->request_backoff_action(Open_table_context::OT_DISCOVER); + (void) ot_ctx->request_backoff_action(Open_table_context::OT_DISCOVER, + NULL, table_list); } else if (share->crashed) { share->version= 0; - (void) ot_ctx->request_backoff_action(Open_table_context::OT_REPAIR); + (void) ot_ctx->request_backoff_action(Open_table_context::OT_REPAIR, + NULL, table_list); } goto err_unlock; @@ -2947,7 +2959,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, table exists now we should downgrade our exclusive metadata lock on this table to SW metadata lock. */ - if (table_list->lock_strategy == TABLE_LIST::EXCLUSIVE_DOWNGRADABLE_MDL && + if (table_list->lock_strategy == TABLE_LIST::OTLS_DOWNGRADE_IF_EXISTS && !(flags & MYSQL_OPEN_HAS_MDL_LOCK)) mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_WRITE); @@ -3782,6 +3794,8 @@ end_with_lock_open: Open_table_context::Open_table_context(THD *thd, ulong timeout) :m_action(OT_NO_ACTION), + m_failed_mdl_request(NULL), + m_failed_table(NULL), m_start_of_statement_svp(thd->mdl_context.mdl_savepoint()), m_has_locks((thd->in_multi_stmt_transaction_mode() && thd->mdl_context.has_locks()) || @@ -3801,10 +3815,8 @@ MDL_request *Open_table_context::get_global_mdl_request(THD *thd) { if (! m_global_mdl_request) { - char *buff; - if ((buff= (char*)thd->alloc(sizeof(MDL_request)))) + if ((m_global_mdl_request= new (thd->mem_root) MDL_request())) { - m_global_mdl_request= new (buff) MDL_request(); m_global_mdl_request->init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE); } @@ -3823,7 +3835,8 @@ MDL_request *Open_table_context::get_global_mdl_request(THD *thd) bool Open_table_context:: -request_backoff_action(enum_open_table_action action_arg) +request_backoff_action(enum_open_table_action action_arg, + MDL_request *mdl_request, TABLE_LIST *table) { /* We are inside a transaction that already holds locks and have @@ -3847,6 +3860,19 @@ request_backoff_action(enum_open_table_action action_arg) return TRUE; } m_action= action_arg; + /* + If waiting for metadata lock is requested, a pointer to + MDL_request object for which we were unable to acquire the + lock is required. + */ + DBUG_ASSERT(m_action != OT_WAIT_MDL_LOCK || mdl_request); + m_failed_mdl_request= mdl_request; + /* + If auto-repair or discovery are requested, a pointer to table + list element must be provided. + */ + DBUG_ASSERT((m_action != OT_DISCOVER && m_action != OT_REPAIR) || table); + m_failed_table= table; return FALSE; } @@ -3855,10 +3881,6 @@ request_backoff_action(enum_open_table_action action_arg) Recover from failed attempt of open table by performing requested action. @param thd Thread context - @param mdl_request MDL_request of the object that caused the problem. - @param table Optional (can be NULL). Used only if action is OT_REPAIR. - In that case a TABLE_LIST for the table to be repaired. - @todo: It's unnecessary and should be removed. @pre This function should be called only with "action" != OT_NO_ACTION and after having called @sa close_tables_for_reopen(). @@ -3869,8 +3891,7 @@ request_backoff_action(enum_open_table_action action_arg) bool Open_table_context:: -recover_from_failed_open(THD *thd, MDL_request *mdl_request, - TABLE_LIST *table) +recover_from_failed_open(THD *thd) { bool result= FALSE; /* @@ -3882,7 +3903,8 @@ recover_from_failed_open(THD *thd, MDL_request *mdl_request, switch (m_action) { case OT_WAIT_MDL_LOCK: - result= thd->mdl_context.wait_for_lock(mdl_request, get_timeout()); + result= thd->mdl_context.wait_for_lock(m_failed_mdl_request, + get_timeout()); break; case OT_WAIT_TDC: result= tdc_wait_for_old_versions(thd, &m_mdl_requests, get_timeout()); @@ -3891,7 +3913,7 @@ recover_from_failed_open(THD *thd, MDL_request *mdl_request, case OT_DISCOVER: { MDL_request mdl_global_request; - MDL_request mdl_xlock_request(mdl_request); + MDL_request mdl_xlock_request(&m_failed_table->mdl_request); MDL_request_list mdl_requests; mdl_global_request.init(MDL_key::GLOBAL, "", "", @@ -3905,14 +3927,11 @@ recover_from_failed_open(THD *thd, MDL_request *mdl_request, thd->mdl_context.acquire_locks(&mdl_requests, get_timeout()))) break; - DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::TABLE); mysql_mutex_lock(&LOCK_open); - tdc_remove_table(thd, TDC_RT_REMOVE_ALL, - mdl_request->key.db_name(), - mdl_request->key.name()); - ha_create_table_from_engine(thd, - mdl_request->key.db_name(), - mdl_request->key.name()); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, m_failed_table->db, + m_failed_table->table_name); + ha_create_table_from_engine(thd, m_failed_table->db, + m_failed_table->table_name); mysql_mutex_unlock(&LOCK_open); thd->warning_info->clear_warning_info(thd->query_id); @@ -3923,7 +3942,7 @@ recover_from_failed_open(THD *thd, MDL_request *mdl_request, case OT_REPAIR: { MDL_request mdl_global_request; - MDL_request mdl_xlock_request(mdl_request); + MDL_request mdl_xlock_request(&m_failed_table->mdl_request); MDL_request_list mdl_requests; mdl_global_request.init(MDL_key::GLOBAL, "", "", @@ -3937,14 +3956,12 @@ recover_from_failed_open(THD *thd, MDL_request *mdl_request, thd->mdl_context.acquire_locks(&mdl_requests, get_timeout()))) break; - DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::TABLE); mysql_mutex_lock(&LOCK_open); - tdc_remove_table(thd, TDC_RT_REMOVE_ALL, - mdl_request->key.db_name(), - mdl_request->key.name()); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, m_failed_table->db, + m_failed_table->table_name); mysql_mutex_unlock(&LOCK_open); - result= auto_repair_table(thd, table); + result= auto_repair_table(thd, m_failed_table); thd->mdl_context.release_transactional_locks(); break; } @@ -3953,6 +3970,13 @@ recover_from_failed_open(THD *thd, MDL_request *mdl_request, } /* Remove all old requests, they will be re-added. */ m_mdl_requests.empty(); + /* + Reset the pointers to conflicting MDL request and the + TABLE_LIST element, set when we need auto-discovery or repair, + for safety. + */ + m_failed_mdl_request= NULL; + m_failed_table= NULL; /* Prepare for possible another back-off. */ m_action= OT_NO_ACTION; return result; @@ -4081,7 +4105,8 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx, if (rt->mdl_request.ticket == NULL) { /* A lock conflict. Someone's trying to modify SP metadata. */ - ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_MDL_LOCK); + ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_MDL_LOCK, + &rt->mdl_request, NULL); DBUG_RETURN(TRUE); } DEBUG_SYNC(thd, "after_shared_lock_pname"); @@ -4228,12 +4253,14 @@ open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables, */ if (tables->view) { + MDL_ticket *mdl_ticket; /* We still need to take a MDL lock on the merged view to protect it from concurrent changes. */ - if (!open_table_get_mdl_lock(thd, tables, &tables->mdl_request, - ot_ctx, flags)) + if (!open_table_get_mdl_lock(thd, ot_ctx, &tables->mdl_request, + flags, &mdl_ticket) && + mdl_ticket != NULL) goto process_view_routines; /* Fall-through to return error. */ } @@ -4423,6 +4450,8 @@ end: should be acquired. @param tables_end End of list of tables. @param ot_ctx Context of open_tables() operation. + @param flags Bitmap of flags to modify how the tables will be + open, see open_table() description for details. @retval FALSE Success. @retval TRUE Failure (e.g. connection was killed) @@ -4431,31 +4460,30 @@ end: static bool open_tables_acquire_upgradable_mdl(THD *thd, TABLE_LIST *tables_start, TABLE_LIST *tables_end, - Open_table_context *ot_ctx) + Open_table_context *ot_ctx, + uint flags) { MDL_request_list mdl_requests; TABLE_LIST *table; DBUG_ASSERT(!thd->locked_tables_mode); - DEBUG_SYNC(thd, "open_tables_acquire_upgradable_mdl"); for (table= tables_start; table && table != tables_end; table= table->next_global) { - if (table->lock_type >= TL_WRITE_ALLOW_WRITE && + if (table->mdl_request.type >= MDL_SHARED_NO_WRITE && !(table->open_type == OT_TEMPORARY_ONLY || + (flags & MYSQL_OPEN_TEMPORARY_ONLY) || (table->open_type != OT_BASE_ONLY && + ! (flags & MYSQL_OPEN_SKIP_TEMPORARY) && find_temporary_table(thd, table)))) - { - table->mdl_request.set_type(table->lock_type > TL_WRITE_ALLOW_READ ? - MDL_SHARED_NO_READ_WRITE : - MDL_SHARED_NO_WRITE); mdl_requests.push_front(&table->mdl_request); - } } if (! mdl_requests.is_empty()) { + DEBUG_SYNC(thd, "open_tables_acquire_upgradable_mdl"); + MDL_request *global_request= ot_ctx->get_global_mdl_request(thd); if (global_request == NULL) @@ -4469,11 +4497,8 @@ open_tables_acquire_upgradable_mdl(THD *thd, TABLE_LIST *tables_start, for (table= tables_start; table && table != tables_end; table= table->next_global) { - if (table->lock_type >= TL_WRITE_ALLOW_WRITE) - { + if (table->mdl_request.type >= MDL_SHARED_NO_WRITE) table->mdl_request.ticket= NULL; - table->mdl_request.set_type(MDL_SHARED_WRITE); - } } return FALSE; @@ -4489,6 +4514,8 @@ open_tables_acquire_upgradable_mdl(THD *thd, TABLE_LIST *tables_start, @param tables_start Start of list of tables on which upgradable locks should be searched for. @param tables_end End of list of tables. + @param flags Bitmap of flags to modify how the tables will be + open, see open_table() description for details. @retval FALSE Success. @retval TRUE Failure (e.g. connection was killed) @@ -4496,7 +4523,7 @@ open_tables_acquire_upgradable_mdl(THD *thd, TABLE_LIST *tables_start, static bool open_tables_check_upgradable_mdl(THD *thd, TABLE_LIST *tables_start, - TABLE_LIST *tables_end) + TABLE_LIST *tables_end, uint flags) { TABLE_LIST *table; @@ -4505,9 +4532,11 @@ open_tables_check_upgradable_mdl(THD *thd, TABLE_LIST *tables_start, for (table= tables_start; table && table != tables_end; table= table->next_global) { - if (table->lock_type >= TL_WRITE_ALLOW_WRITE && + if (table->mdl_request.type >= MDL_SHARED_NO_WRITE && !(table->open_type == OT_TEMPORARY_ONLY || + (flags & MYSQL_OPEN_TEMPORARY_ONLY) || (table->open_type != OT_BASE_ONLY && + ! (flags & MYSQL_OPEN_SKIP_TEMPORARY) && find_temporary_table(thd, table)))) { /* @@ -4519,8 +4548,14 @@ open_tables_check_upgradable_mdl(THD *thd, TABLE_LIST *tables_start, lock, all other instances of TABLE for the same table will have the same ticket. - Note that find_table_for_mdl_upgrade() will report an error if a - ticket is not found. + Note that this works OK even for CREATE TABLE statements which + request X type of metadata lock. This is because under LOCK TABLES + such statements don't create the table but only check if it exists + or, in most complex case, only insert into it. + Thus SNRW lock should be enough. + + Note that find_table_for_mdl_upgrade() will report an error if + no suitable ticket is found. */ if (!find_table_for_mdl_upgrade(thd->open_tables, table->db, table->table_name, FALSE)) @@ -4612,21 +4647,19 @@ restart: (in non-LOCK TABLES mode) we might have to acquire upgradable semi-exclusive metadata locks (SNW or SNRW) on some of the tables to be opened. - So we acquire all such locks at once here as doing this in one + When executing CREATE TABLE .. If NOT EXISTS .. SELECT, the + table may not yet exist, in which case we acquire an exclusive + lock. + We acquire all such locks at once here as doing this in one by one fashion may lead to deadlocks or starvation. Later when we will be opening corresponding table pre-acquired metadata lock will be reused (thanks to the fact that in recursive case metadata locks are acquired without waiting). */ - if (flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL) + if (! (flags & (MYSQL_OPEN_HAS_MDL_LOCK | + MYSQL_OPEN_FORCE_SHARED_MDL | + MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL))) { - /* - open_tables_acquire_upgradable_mdl() does not currenly handle - these two flags. At this point, that does not matter as they - are not used together with MYSQL_OPEN_TAKE_UPGRADABLE_MDL. - */ - DBUG_ASSERT(!(flags & (MYSQL_OPEN_SKIP_TEMPORARY | - MYSQL_OPEN_TEMPORARY_ONLY))); if (thd->locked_tables_mode) { /* @@ -4634,7 +4667,8 @@ restart: need to check if appropriate locks were pre-acquired. */ if (open_tables_check_upgradable_mdl(thd, *start, - thd->lex->first_not_own_table())) + thd->lex->first_not_own_table(), + flags)) { error= TRUE; goto err; @@ -4642,7 +4676,7 @@ restart: } else if (open_tables_acquire_upgradable_mdl(thd, *start, thd->lex->first_not_own_table(), - &ot_ctx)) + &ot_ctx, flags)) { error= TRUE; goto err; @@ -4688,7 +4722,6 @@ restart: have failed to open since closing tables can trigger removal of elements from the table list (if MERGE tables are involved), */ - TABLE_LIST *failed_table= *table_to_open; close_tables_for_reopen(thd, start, ot_ctx.start_of_statement_svp()); /* @@ -4696,8 +4729,7 @@ restart: TABLE_LIST element. Altough currently this assumption is valid it may change in future. */ - if (ot_ctx.recover_from_failed_open(thd, &failed_table->mdl_request, - failed_table)) + if (ot_ctx.recover_from_failed_open(thd)) goto err; error= FALSE; @@ -4741,7 +4773,7 @@ restart: { close_tables_for_reopen(thd, start, ot_ctx.start_of_statement_svp()); - if (ot_ctx.recover_from_failed_open(thd, &rt->mdl_request, NULL)) + if (ot_ctx.recover_from_failed_open(thd)) goto err; error= FALSE; @@ -5163,6 +5195,9 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type, /* open_ltable can be used only for BASIC TABLEs */ table_list->required_type= FRMTYPE_TABLE; + /* This function can't properly handle requests for such metadata locks. */ + DBUG_ASSERT(table_list->mdl_request.type < MDL_SHARED_NO_WRITE); + while ((error= open_table(thd, table_list, thd->mem_root, &ot_ctx, lock_flags)) && ot_ctx.can_recover_from_failed_open()) { @@ -5173,8 +5208,7 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type, */ thd->mdl_context.rollback_to_savepoint(ot_ctx.start_of_statement_svp()); table_list->mdl_request.ticket= 0; - if (ot_ctx.recover_from_failed_open(thd, &table_list->mdl_request, - table_list)) + if (ot_ctx.recover_from_failed_open(thd)) break; } diff --git a/sql/sql_class.h b/sql/sql_class.h index 916b79f8353..d494fdf86b5 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1329,9 +1329,9 @@ public: }; Open_table_context(THD *thd, ulong timeout); - bool recover_from_failed_open(THD *thd, MDL_request *mdl_request, - TABLE_LIST *table); - bool request_backoff_action(enum_open_table_action action_arg); + bool recover_from_failed_open(THD *thd); + bool request_backoff_action(enum_open_table_action action_arg, + MDL_request *mdl_request, TABLE_LIST *table); void add_request(MDL_request *request) { m_mdl_requests.push_front(request); } @@ -1362,6 +1362,14 @@ private: MDL_request_list m_mdl_requests; /** Back off action. */ enum enum_open_table_action m_action; + /** For OT_WAIT_MDL_LOCK action, the request for which we should wait. */ + MDL_request *m_failed_mdl_request; + /** + For OT_DISCOVER and OT_REPAIR actions, the table list element for + the table which definition should be re-discovered or which + should be repaired. + */ + TABLE_LIST *m_failed_table; MDL_ticket *m_start_of_statement_svp; /** Whether we had any locks when this context was created. diff --git a/sql/sql_db.cc b/sql/sql_db.cc index 15fdd842e34..2e48475f298 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -1203,6 +1203,8 @@ static long mysql_rm_known_files(THD *thd, MY_DIR *dirp, const char *db, table_list->alias= table_list->table_name; // If lower_case_table_names=2 table_list->internal_tmp_table= is_prefix(file->name, tmp_file_prefix); + table_list->mdl_request.init(MDL_key::TABLE, table_list->db, + table_list->table_name, MDL_EXCLUSIVE); /* Link into list */ (*tot_list_next)= table_list; tot_list_next= &table_list->next_local; @@ -1918,9 +1920,11 @@ bool mysql_upgrade_db(THD *thd, LEX_STRING *old_db) Table_ident *new_ident= new Table_ident(thd, new_db, table_str, 0); if (!old_ident || !new_ident || !sl->add_table_to_list(thd, old_ident, NULL, - TL_OPTION_UPDATING, TL_IGNORE) || + TL_OPTION_UPDATING, TL_IGNORE, + MDL_EXCLUSIVE) || !sl->add_table_to_list(thd, new_ident, NULL, - TL_OPTION_UPDATING, TL_IGNORE)) + TL_OPTION_UPDATING, TL_IGNORE, + MDL_EXCLUSIVE)) { error= 1; my_dirend(dirp); diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 1795bc272f1..48474609b9b 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -1978,6 +1978,7 @@ TABLE_LIST *st_select_lex_node::add_table_to_list (THD *thd, Table_ident *table, LEX_STRING *alias, ulong table_join_options, thr_lock_type flags, + enum_mdl_type mdl_type, List<Index_hint> *hints, LEX_STRING *option) { diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 2ce6bdeed42..38aac9a1042 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -502,6 +502,7 @@ public: LEX_STRING *alias, ulong table_options, thr_lock_type flags= TL_UNLOCK, + enum_mdl_type mdl_type= MDL_SHARED_READ, List<Index_hint> *hints= 0, LEX_STRING *option= 0); virtual void set_lock_for_tables(thr_lock_type lock_type) {} @@ -799,6 +800,7 @@ public: LEX_STRING *alias, ulong table_options, thr_lock_type flags= TL_UNLOCK, + enum_mdl_type mdl_type= MDL_SHARED_READ, List<Index_hint> *hints= 0, LEX_STRING *option= 0); TABLE_LIST* get_table_list(); @@ -2249,6 +2251,7 @@ public: yacc_yyvs= NULL; m_set_signal_info.clear(); m_lock_type= TL_READ_DEFAULT; + m_mdl_type= MDL_SHARED_READ; } ~Yacc_state(); @@ -2260,6 +2263,7 @@ public: void reset_before_substatement() { m_lock_type= TL_READ_DEFAULT; + m_mdl_type= MDL_SHARED_READ; } /** @@ -2299,6 +2303,12 @@ public: */ thr_lock_type m_lock_type; + /** + The type of requested metadata lock for tables added to + the statement table list. + */ + enum_mdl_type m_mdl_type; + /* TODO: move more attributes from the LEX structure here. */ diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 60b2ffa4179..4c3f44ea75c 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1606,7 +1606,8 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident, /* 'parent_lex' is used in init_query() so it must be before it. */ schema_select_lex->parent_lex= lex; schema_select_lex->init_query(); - if (!schema_select_lex->add_table_to_list(thd, table_ident, 0, 0, TL_READ)) + if (!schema_select_lex->add_table_to_list(thd, table_ident, 0, 0, TL_READ, + MDL_SHARED_READ)) DBUG_RETURN(1); lex->query_tables_last= query_tables_last; break; @@ -2544,7 +2545,7 @@ case SQLCOM_PREPARE: /* Set strategies: reset default or 'prepared' values. */ create_table->open_strategy= TABLE_LIST::OPEN_IF_EXISTS; - create_table->lock_strategy= TABLE_LIST::EXCLUSIVE_DOWNGRADABLE_MDL; + create_table->lock_strategy= TABLE_LIST::OTLS_DOWNGRADE_IF_EXISTS; /* Close any open handlers for the table @@ -3502,16 +3503,13 @@ end_with_restore_list: thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) goto error; - init_mdl_requests(all_tables); - thd->variables.option_bits|= OPTION_TABLE_LOCK; thd->in_lock_tables=1; { Lock_tables_prelocking_strategy lock_tables_prelocking_strategy; - res= (open_and_lock_tables(thd, all_tables, FALSE, - MYSQL_OPEN_TAKE_UPGRADABLE_MDL, + res= (open_and_lock_tables(thd, all_tables, FALSE, 0, &lock_tables_prelocking_strategy) || thd->locked_tables_list.init_locked_tables(thd)); } @@ -6014,6 +6012,7 @@ bool add_to_list(THD *thd, SQL_LIST &list,Item *item,bool asc) - TL_OPTION_FORCE_INDEX : Force usage of index - TL_OPTION_ALIAS : an alias in multi table DELETE @param lock_type How table should be locked + @param mdl_type Type of metadata lock to acquire on the table. @param use_index List of indexed used in USE INDEX @param ignore_index List of indexed used in IGNORE INDEX @@ -6028,6 +6027,7 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, LEX_STRING *alias, ulong table_options, thr_lock_type lock_type, + enum_mdl_type mdl_type, List<Index_hint> *index_hints_arg, LEX_STRING *option) { @@ -6175,9 +6175,7 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->next_name_resolution_table= NULL; /* Link table in global list (all used tables) */ lex->add_to_query_tables(ptr); - ptr->mdl_request.init(MDL_key::TABLE, ptr->db, ptr->table_name, - (ptr->lock_type >= TL_WRITE_ALLOW_WRITE) ? - MDL_SHARED_WRITE : MDL_SHARED_READ); + ptr->mdl_request.init(MDL_key::TABLE, ptr->db, ptr->table_name, mdl_type); DBUG_RETURN(ptr); } diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index c2d3c595d95..e5d7514d9f5 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1690,7 +1690,7 @@ static bool mysql_test_create_table(Prepared_statement *stmt) for the prepare phase. */ create_table->open_strategy= TABLE_LIST::OPEN_IF_EXISTS; - create_table->lock_strategy= TABLE_LIST::SHARED_MDL; + create_table->lock_strategy= TABLE_LIST::OTLS_NONE; if (select_lex->item_list.elements) { diff --git a/sql/sql_show.cc b/sql/sql_show.cc index f1db513d0e2..a33bc5943da 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2358,7 +2358,7 @@ int make_table_list(THD *thd, SELECT_LEX *sel, Table_ident *table_ident; table_ident= new Table_ident(thd, *db_name, *table_name, 1); sel->init_query(); - if (!sel->add_table_to_list(thd, table_ident, 0, 0, TL_READ)) + if (!sel->add_table_to_list(thd, table_ident, 0, 0, TL_READ, MDL_SHARED_READ)) return 1; return 0; } @@ -6582,7 +6582,7 @@ int make_schema_select(THD *thd, SELECT_LEX *sel, strlen(schema_table->table_name), 0); if (schema_table->old_format(thd, schema_table) || /* Handle old syntax */ !sel->add_table_to_list(thd, new Table_ident(thd, db, table, 0), - 0, 0, TL_READ)) + 0, 0, TL_READ, MDL_SHARED_READ)) { DBUG_RETURN(1); } diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 2b8e7de3a60..b2a950ca4b0 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4656,6 +4656,14 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, strxmov(table_name, db, ".", table->table_name, NullS); thd->open_options|= extra_open_options; table->lock_type= lock_type; + /* + To make code safe for re-execution we need to reset type of MDL + request as code below may change it. + To allow concurrent execution of read-only operations we acquire + weak metadata lock for them. + */ + table->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_READ) ? + MDL_SHARED_NO_READ_WRITE : MDL_SHARED_READ); /* open only one table from local list of command */ { TABLE_LIST *save_next_global, *save_next_local; @@ -4677,8 +4685,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, if (view_operator_func == NULL) table->required_type=FRMTYPE_TABLE; - open_error= open_and_lock_tables(thd, table, TRUE, - MYSQL_OPEN_TAKE_UPGRADABLE_MDL); + open_error= open_and_lock_tables(thd, table, TRUE, 0); thd->no_warnings_for_error= 0; table->next_global= save_next_global; table->next_local= save_next_local; @@ -5024,6 +5031,7 @@ send_result_message: /* Clear the ticket released in close_thread_tables(). */ table->mdl_request.ticket= NULL; DEBUG_SYNC(thd, "ha_admin_open_ltable"); + table->mdl_request.set_type(MDL_SHARED_WRITE); if ((table->table= open_ltable(thd, table, lock_type, 0))) { result_code= table->table->file->ha_analyze(thd, check_opt); @@ -5461,6 +5469,7 @@ mysql_discard_or_import_tablespace(THD *thd, not complain when we lock the table */ thd->tablespace_op= TRUE; + table_list->mdl_request.set_type(MDL_SHARED_WRITE); if (!(table=open_ltable(thd, table_list, TL_WRITE, 0))) { thd->tablespace_op=FALSE; @@ -6568,6 +6577,12 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) DBUG_RETURN(TRUE); + + /* + TODO/FIXME: Get rid of this code branch if possible. To add insult + to injury it breaks locking protocol. + */ + table_list->mdl_request.set_type(MDL_EXCLUSIVE); if (lock_table_names(thd, table_list)) { error= 1; @@ -6608,8 +6623,7 @@ view_err: Alter_table_prelocking_strategy alter_prelocking_strategy(alter_info); - error= open_and_lock_tables(thd, table_list, FALSE, - MYSQL_OPEN_TAKE_UPGRADABLE_MDL, + error= open_and_lock_tables(thd, table_list, FALSE, 0, &alter_prelocking_strategy); if (error) @@ -7904,6 +7918,10 @@ bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list) table_list->table= NULL; /* Same applies to MDL ticket. */ table_list->mdl_request.ticket= NULL; + /* Set lock type which is appropriate for ALTER TABLE. */ + table_list->lock_type= TL_WRITE_ALLOW_READ; + /* Same applies to MDL request. */ + table_list->mdl_request.set_type(MDL_SHARED_NO_WRITE); bzero((char*) &create_info, sizeof(create_info)); create_info.row_type=ROW_TYPE_NOT_USED; diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 9ce62d9f2a4..ae09898ada2 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -489,8 +489,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) else { tables->table= open_n_lock_single_table(thd, tables, - TL_WRITE_ALLOW_READ, - MYSQL_OPEN_TAKE_UPGRADABLE_MDL); + TL_WRITE_ALLOW_READ, 0); if (! tables->table) goto end; tables->table->use_all_columns(); @@ -1667,7 +1666,8 @@ bool add_table_for_trigger(THD *thd, DBUG_RETURN(TRUE); *table= sp_add_to_query_tables(thd, lex, trg_name->m_db.str, - tbl_name.str, TL_IGNORE); + tbl_name.str, TL_IGNORE, + MDL_SHARED_NO_WRITE); DBUG_RETURN(*table ? FALSE : TRUE); } diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 3a6866f4a7e..4eee9502177 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -433,7 +433,7 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, lex->link_first_table_back(view, link_to_local); view->open_strategy= TABLE_LIST::OPEN_STUB; - view->lock_strategy= TABLE_LIST::EXCLUSIVE_MDL; + view->lock_strategy= TABLE_LIST::OTLS_NONE; view->open_type= OT_BASE_ONLY; if (open_and_lock_tables(thd, lex->query_tables, TRUE, 0)) diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index aa336f3c072..e5875663d4e 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -697,7 +697,8 @@ static bool add_create_index_prepare (LEX *lex, Table_ident *table) lex->sql_command= SQLCOM_CREATE_INDEX; if (!lex->current_select->add_table_to_list(lex->thd, table, NULL, TL_OPTION_UPDATING, - TL_WRITE_ALLOW_READ)) + TL_WRITE_ALLOW_READ, + MDL_SHARED_NO_WRITE)) return TRUE; lex->alter_info.reset(); lex->alter_info.flags= ALTER_ADD_INDEX; @@ -2023,7 +2024,7 @@ create: lex->sql_command= SQLCOM_CREATE_TABLE; if (!lex->select_lex.add_table_to_list(thd, $5, NULL, TL_OPTION_UPDATING, - TL_WRITE)) + TL_WRITE, MDL_EXCLUSIVE)) MYSQL_YYABORT; lex->alter_info.reset(); lex->col_list.empty(); @@ -4213,7 +4214,8 @@ create2: lex->create_info.options|= HA_LEX_CREATE_TABLE_LIKE; src_table= lex->select_lex.add_table_to_list(thd, $2, NULL, 0, - TL_READ); + TL_READ, + MDL_SHARED_READ); if (! src_table) MYSQL_YYABORT; /* CREATE TABLE ... LIKE is not allowed for views. */ @@ -4227,7 +4229,8 @@ create2: lex->create_info.options|= HA_LEX_CREATE_TABLE_LIKE; src_table= lex->select_lex.add_table_to_list(thd, $3, NULL, 0, - TL_READ); + TL_READ, + MDL_SHARED_READ); if (! src_table) MYSQL_YYABORT; /* CREATE TABLE ... LIKE is not allowed for views. */ @@ -6154,7 +6157,8 @@ alter: lex->duplicates= DUP_ERROR; if (!lex->select_lex.add_table_to_list(thd, $4, NULL, TL_OPTION_UPDATING, - TL_WRITE_ALLOW_READ)) + TL_WRITE_ALLOW_READ, + MDL_SHARED_NO_WRITE)) MYSQL_YYABORT; lex->col_list.empty(); lex->select_lex.init_order(); @@ -6847,6 +6851,8 @@ checksum: { LEX *lex=Lex; lex->sql_command = SQLCOM_CHECKSUM; + /* Will be overriden during execution. */ + YYPS->m_lock_type= TL_UNLOCK; } table_list opt_checksum_type {} @@ -6866,6 +6872,8 @@ repair: lex->no_write_to_binlog= $2; lex->check_opt.init(); lex->alter_info.reset(); + /* Will be overriden during execution. */ + YYPS->m_lock_type= TL_UNLOCK; } table_list opt_mi_repair_type {} @@ -6895,6 +6903,8 @@ analyze: lex->no_write_to_binlog= $2; lex->check_opt.init(); lex->alter_info.reset(); + /* Will be overriden during execution. */ + YYPS->m_lock_type= TL_UNLOCK; } table_list {} @@ -6921,6 +6931,8 @@ check: lex->sql_command = SQLCOM_CHECK; lex->check_opt.init(); lex->alter_info.reset(); + /* Will be overriden during execution. */ + YYPS->m_lock_type= TL_UNLOCK; } table_list opt_mi_check_type {} @@ -6953,6 +6965,8 @@ optimize: lex->no_write_to_binlog= $2; lex->check_opt.init(); lex->alter_info.reset(); + /* Will be overriden during execution. */ + YYPS->m_lock_type= TL_UNLOCK; } table_list {} @@ -7001,9 +7015,9 @@ table_to_table: LEX *lex=Lex; SELECT_LEX *sl= lex->current_select; if (!sl->add_table_to_list(lex->thd, $1,NULL,TL_OPTION_UPDATING, - TL_IGNORE) || + TL_IGNORE, MDL_EXCLUSIVE) || !sl->add_table_to_list(lex->thd, $3,NULL,TL_OPTION_UPDATING, - TL_IGNORE)) + TL_IGNORE, MDL_EXCLUSIVE)) MYSQL_YYABORT; } ; @@ -7034,7 +7048,8 @@ keycache_list: assign_to_keycache: table_ident cache_keys_spec { - if (!Select->add_table_to_list(YYTHD, $1, NULL, 0, TL_READ, + if (!Select->add_table_to_list(YYTHD, $1, NULL, 0, TL_READ, + MDL_SHARED_READ, Select->pop_index_hints())) MYSQL_YYABORT; } @@ -7044,6 +7059,7 @@ assign_to_keycache_parts: table_ident adm_partition cache_keys_spec { if (!Select->add_table_to_list(YYTHD, $1, NULL, 0, TL_READ, + MDL_SHARED_READ, Select->pop_index_hints())) MYSQL_YYABORT; } @@ -7079,6 +7095,7 @@ preload_keys: table_ident cache_keys_spec opt_ignore_leaves { if (!Select->add_table_to_list(YYTHD, $1, NULL, $3, TL_READ, + MDL_SHARED_READ, Select->pop_index_hints())) MYSQL_YYABORT; } @@ -7088,6 +7105,7 @@ preload_keys_parts: table_ident adm_partition cache_keys_spec opt_ignore_leaves { if (!Select->add_table_to_list(YYTHD, $1, NULL, $4, TL_READ, + MDL_SHARED_READ, Select->pop_index_hints())) MYSQL_YYABORT; } @@ -9220,6 +9238,7 @@ table_factor: if (!($$= Select->add_table_to_list(YYTHD, $2, $3, Select->get_table_join_options(), YYPS->m_lock_type, + YYPS->m_mdl_type, Select->pop_index_hints()))) MYSQL_YYABORT; Select->add_joined_table($$); @@ -9291,7 +9310,7 @@ table_factor: MYSQL_YYABORT; if (!($$= sel->add_table_to_list(lex->thd, new Table_ident(unit), $5, 0, - TL_READ))) + TL_READ, MDL_SHARED_READ))) MYSQL_YYABORT; sel->add_joined_table($$); @@ -10126,13 +10145,17 @@ do: */ drop: - DROP opt_temporary table_or_tables if_exists table_list opt_restrict + DROP opt_temporary table_or_tables if_exists { LEX *lex=Lex; lex->sql_command = SQLCOM_DROP_TABLE; lex->drop_temporary= $2; lex->drop_if_exists= $4; + YYPS->m_lock_type= TL_IGNORE; + YYPS->m_mdl_type= MDL_EXCLUSIVE; } + table_list opt_restrict + {} | DROP INDEX_SYM ident ON table_ident {} { LEX *lex=Lex; @@ -10145,7 +10168,8 @@ drop: lex->alter_info.drop_list.push_back(ad); if (!lex->current_select->add_table_to_list(lex->thd, $5, NULL, TL_OPTION_UPDATING, - TL_WRITE_ALLOW_READ)) + TL_WRITE_ALLOW_READ, + MDL_SHARED_NO_WRITE)) MYSQL_YYABORT; } | DROP DATABASE if_exists ident @@ -10215,12 +10239,16 @@ drop: { Lex->sql_command = SQLCOM_DROP_USER; } - | DROP VIEW_SYM if_exists table_list opt_restrict + | DROP VIEW_SYM if_exists { LEX *lex= Lex; lex->sql_command= SQLCOM_DROP_VIEW; lex->drop_if_exists= $3; + YYPS->m_lock_type= TL_IGNORE; + YYPS->m_mdl_type= MDL_EXCLUSIVE; } + table_list opt_restrict + {} | DROP EVENT_SYM if_exists sp_name { Lex->drop_if_exists= $3; @@ -10261,7 +10289,10 @@ table_list: table_name: table_ident { - if (!Select->add_table_to_list(YYTHD, $1, NULL, TL_OPTION_UPDATING)) + if (!Select->add_table_to_list(YYTHD, $1, NULL, + TL_OPTION_UPDATING, + YYPS->m_lock_type, + YYPS->m_mdl_type)) MYSQL_YYABORT; } ; @@ -10276,7 +10307,8 @@ table_alias_ref: { if (!Select->add_table_to_list(YYTHD, $1, NULL, TL_OPTION_UPDATING | TL_OPTION_ALIAS, - YYPS->m_lock_type)) + YYPS->m_lock_type, + YYPS->m_mdl_type)) MYSQL_YYABORT; } ; @@ -10558,6 +10590,8 @@ delete: lex->sql_command= SQLCOM_DELETE; mysql_init_select(lex); YYPS->m_lock_type= TL_WRITE_DEFAULT; + YYPS->m_mdl_type= MDL_SHARED_WRITE; + lex->ignore= 0; lex->select_lex.init_order(); } @@ -10568,9 +10602,11 @@ single_multi: FROM table_ident { if (!Select->add_table_to_list(YYTHD, $2, NULL, TL_OPTION_UPDATING, - YYPS->m_lock_type)) + YYPS->m_lock_type, + YYPS->m_mdl_type)) MYSQL_YYABORT; YYPS->m_lock_type= TL_READ_DEFAULT; + YYPS->m_mdl_type= MDL_SHARED_READ; } where_clause opt_order_clause delete_limit_clause {} @@ -10578,6 +10614,7 @@ single_multi: { mysql_init_multi_delete(Lex); YYPS->m_lock_type= TL_READ_DEFAULT; + YYPS->m_mdl_type= MDL_SHARED_READ; } FROM join_table_list where_clause { @@ -10588,6 +10625,7 @@ single_multi: { mysql_init_multi_delete(Lex); YYPS->m_lock_type= TL_READ_DEFAULT; + YYPS->m_mdl_type= MDL_SHARED_READ; } USING join_table_list where_clause { @@ -10611,7 +10649,8 @@ table_wild_one: ti, NULL, TL_OPTION_UPDATING | TL_OPTION_ALIAS, - YYPS->m_lock_type)) + YYPS->m_lock_type, + YYPS->m_mdl_type)) MYSQL_YYABORT; } | ident '.' ident opt_wild @@ -10623,7 +10662,8 @@ table_wild_one: ti, NULL, TL_OPTION_UPDATING | TL_OPTION_ALIAS, - YYPS->m_lock_type)) + YYPS->m_lock_type, + YYPS->m_mdl_type)) MYSQL_YYABORT; } ; @@ -11137,7 +11177,15 @@ flush: flush_options: table_or_tables - { Lex->type|= REFRESH_TABLES; } + { + Lex->type|= REFRESH_TABLES; + /* + Set type of metadata and table locks for + FLUSH TABLES table_list WITH READ LOCK. + */ + YYPS->m_lock_type= TL_READ_NO_INSERT; + YYPS->m_mdl_type= MDL_EXCLUSIVE; + } opt_table_list {} opt_with_read_lock {} | flush_options_list @@ -11301,7 +11349,7 @@ load: { LEX *lex=Lex; if (!Select->add_table_to_list(YYTHD, $12, NULL, TL_OPTION_UPDATING, - $4)) + $4, MDL_SHARED_WRITE)) MYSQL_YYABORT; lex->field_list.empty(); lex->update_list.empty(); @@ -13007,10 +13055,14 @@ table_lock: table_ident opt_table_alias lock_option { thr_lock_type lock_type= (thr_lock_type) $3; - if (!Select->add_table_to_list(YYTHD, $1, $2, 0, lock_type)) + bool lock_for_write= (lock_type >= TL_WRITE_ALLOW_WRITE); + if (!Select->add_table_to_list(YYTHD, $1, $2, 0, lock_type, + (lock_for_write ? + MDL_SHARED_NO_READ_WRITE : + MDL_SHARED_READ))) MYSQL_YYABORT; /* If table is to be write locked, protect from a impending GRL. */ - if (lock_type >= TL_WRITE_ALLOW_WRITE) + if (lock_for_write) Lex->protect_against_global_read_lock= TRUE; } ; @@ -13765,6 +13817,7 @@ query_expression_option: if (check_simple_select()) MYSQL_YYABORT; YYPS->m_lock_type= TL_READ_HIGH_PRIORITY; + YYPS->m_mdl_type= MDL_SHARED_READ; Select->options|= SELECT_HIGH_PRIORITY; } | DISTINCT { Select->options|= SELECT_DISTINCT; } @@ -13894,7 +13947,10 @@ view_tail: LEX *lex= thd->lex; lex->sql_command= SQLCOM_CREATE_VIEW; /* first table in list is target VIEW name */ - if (!lex->select_lex.add_table_to_list(thd, $3, NULL, TL_OPTION_UPDATING)) + if (!lex->select_lex.add_table_to_list(thd, $3, NULL, + TL_OPTION_UPDATING, + TL_IGNORE, + MDL_EXCLUSIVE)) MYSQL_YYABORT; } view_list_opt AS view_select @@ -14034,7 +14090,8 @@ trigger_tail: if (!lex->select_lex.add_table_to_list(YYTHD, $9, (LEX_STRING*) 0, TL_OPTION_UPDATING, - TL_IGNORE)) + TL_WRITE_ALLOW_READ, + MDL_SHARED_NO_WRITE)) MYSQL_YYABORT; } ; diff --git a/sql/table.cc b/sql/table.cc index 65918dd58f9..1e7cb747bc6 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -4659,13 +4659,6 @@ void TABLE_LIST::reinit_before_use(THD *thd) parent_embedding->nested_join->join_list.head() == embedded); mdl_request.ticket= NULL; - /* - Since we manipulate with the metadata lock type in open_table(), - we need to reset it to the parser default, to restore things back - to first-execution state. - */ - mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ? - MDL_SHARED_WRITE : MDL_SHARED_READ); } /* diff --git a/sql/table.h b/sql/table.h index fcff0cfb0d5..ea585208b83 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1581,20 +1581,21 @@ struct TABLE_LIST OPEN_STUB } open_strategy; /** - Indicates the locking strategy for the object being opened: - whether the associated metadata lock is shared or exclusive. + Indicates the locking strategy for the object being opened. */ enum { - /* Take a shared metadata lock before the object is opened. */ - SHARED_MDL= 0, /* - Take a exclusive metadata lock before the object is opened. - If opening is successful, downgrade to a shared lock. + Take metadata lock specified by 'mdl_request' member before + the object is opened. Do nothing after that. */ - EXCLUSIVE_DOWNGRADABLE_MDL, - /* Take a exclusive metadata lock before the object is opened. */ - EXCLUSIVE_MDL + OTLS_NONE= 0, + /* + Take (exclusive) metadata lock specified by 'mdl_request' member + before object is opened. If opening is successful, downgrade to + a shared lock. + */ + OTLS_DOWNGRADE_IF_EXISTS } lock_strategy; /* For transactional locking. */ int lock_timeout; /* NOWAIT or WAIT [X] */ |