1
0
mirror of https://github.com/MariaDB/server.git synced 2025-12-24 11:21:21 +03:00

A 5.5 version of the fix for Bug #54360 "Deadlock DROP/ALTER/CREATE

DATABASE with open HANDLER"

Remove LOCK_create_db, database name locks, and use metadata locks instead.
This exposes CREATE/DROP/ALTER DATABASE statements to the graph-based
deadlock detector in MDL, and paves the way for a safe, deadlock-free
implementation of RENAME DATABASE.

Database DDL statements will now take exclusive metadata locks on
the database name, while table/view/routine DDL statements take
intention exclusive locks on the database name. This prevents race
conditions between database DDL and table/view/routine DDL.
(e.g. DROP DATABASE with concurrent CREATE/ALTER/DROP TABLE)

By adding database name locks, this patch implements
WL#4450 "DDL locking: CREATE/DROP DATABASE must use database locks" and
WL#4985 "DDL locking: namespace/hierarchical locks".

The patch also changes code to use init_one_table() where appropriate.
The new lock_table_names() function requires TABLE_LIST::db_length to
be set correctly, and this is taken care of by init_one_table().

This patch also adds a simple template to help work with 
the mysys HASH data structure.

Most of the patch was written by Konstantin Osipov.
This commit is contained in:
Jon Olav Hauglid
2010-07-01 15:53:46 +02:00
parent 29e9130d60
commit 9ff272fbbd
28 changed files with 1300 additions and 652 deletions

View File

@@ -53,6 +53,7 @@
#include "rpl_filter.h"
#include "sql_table.h" // build_table_filename
#include "datadict.h" // dd_frm_type()
#include "sql_hset.h" // Hash_set
#ifdef __WIN__
#include <io.h>
#endif
@@ -4029,7 +4030,6 @@ end_unlock:
Open_table_context::Open_table_context(THD *thd, uint flags)
:m_failed_table(NULL),
m_start_of_statement_svp(thd->mdl_context.mdl_savepoint()),
m_global_mdl_request(NULL),
m_timeout(flags & MYSQL_LOCK_IGNORE_TIMEOUT ?
LONG_TIMEOUT : thd->variables.lock_wait_timeout),
m_flags(flags),
@@ -4040,26 +4040,6 @@ Open_table_context::Open_table_context(THD *thd, uint flags)
{}
/**
Get MDL_request object for global intention exclusive lock which
is acquired during opening tables for statements which take
upgradable shared metadata locks.
*/
MDL_request *Open_table_context::get_global_mdl_request(THD *thd)
{
if (! m_global_mdl_request)
{
if ((m_global_mdl_request= new (thd->mem_root) MDL_request()))
{
m_global_mdl_request->init(MDL_key::GLOBAL, "", "",
MDL_INTENTION_EXCLUSIVE);
}
}
return m_global_mdl_request;
}
/**
Check if we can back-off and set back off action if we can.
Otherwise report and return error.
@@ -4108,13 +4088,23 @@ request_backoff_action(enum_open_table_action action_arg,
my_error(ER_LOCK_DEADLOCK, MYF(0));
return TRUE;
}
m_action= action_arg;
/*
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;
if (table)
{
DBUG_ASSERT(action_arg == OT_DISCOVER || action_arg == OT_REPAIR);
m_failed_table= (TABLE_LIST*) current_thd->alloc(sizeof(TABLE_LIST));
if (m_failed_table == NULL)
return TRUE;
m_failed_table->init_one_table(table->db, table->db_length,
table->table_name,
table->table_name_length,
table->alias, TL_WRITE);
m_failed_table->mdl_request.set_type(MDL_EXCLUSIVE);
}
m_action= action_arg;
return FALSE;
}
@@ -4136,11 +4126,6 @@ Open_table_context::
recover_from_failed_open(THD *thd)
{
bool result= FALSE;
/*
Remove reference to released ticket from MDL_request.
*/
if (m_global_mdl_request)
m_global_mdl_request->ticket= NULL;
/* Execute the action. */
switch (m_action)
{
@@ -4152,19 +4137,9 @@ recover_from_failed_open(THD *thd)
break;
case OT_DISCOVER:
{
MDL_request mdl_global_request;
MDL_request mdl_xlock_request(&m_failed_table->mdl_request);
MDL_request_list mdl_requests;
mdl_global_request.init(MDL_key::GLOBAL, "", "",
MDL_INTENTION_EXCLUSIVE);
mdl_xlock_request.set_type(MDL_EXCLUSIVE);
mdl_requests.push_front(&mdl_xlock_request);
mdl_requests.push_front(&mdl_global_request);
if ((result=
thd->mdl_context.acquire_locks(&mdl_requests, get_timeout())))
if ((result= lock_table_names(thd, m_failed_table, NULL,
get_timeout(),
MYSQL_OPEN_SKIP_TEMPORARY)))
break;
mysql_mutex_lock(&LOCK_open);
@@ -4181,19 +4156,9 @@ recover_from_failed_open(THD *thd)
}
case OT_REPAIR:
{
MDL_request mdl_global_request;
MDL_request mdl_xlock_request(&m_failed_table->mdl_request);
MDL_request_list mdl_requests;
mdl_global_request.init(MDL_key::GLOBAL, "", "",
MDL_INTENTION_EXCLUSIVE);
mdl_xlock_request.set_type(MDL_EXCLUSIVE);
mdl_requests.push_front(&mdl_xlock_request);
mdl_requests.push_front(&mdl_global_request);
if ((result=
thd->mdl_context.acquire_locks(&mdl_requests, get_timeout())))
if ((result= lock_table_names(thd, m_failed_table, NULL,
get_timeout(),
MYSQL_OPEN_SKIP_TEMPORARY)))
break;
mysql_mutex_lock(&LOCK_open);
@@ -4688,32 +4653,40 @@ end:
DBUG_RETURN(error);
}
extern "C" uchar *schema_set_get_key(const uchar *record, size_t *length,
my_bool not_used __attribute__((unused)))
{
TABLE_LIST *table=(TABLE_LIST*) record;
*length= table->db_length;
return (uchar*) table->db;
}
/**
Acquire upgradable (SNW, SNRW) metadata locks on tables to be opened
for LOCK TABLES or a DDL statement. Under LOCK TABLES, we can't take
Acquire upgradable (SNW, SNRW) metadata locks on tables used by
LOCK TABLES or by a DDL statement. Under LOCK TABLES, we can't take
new locks, so use open_tables_check_upgradable_mdl() instead.
@param thd Thread context.
@param tables_start Start of list of tables on which upgradable locks
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.
@param thd Thread context.
@param tables_start Start of list of tables on which upgradable locks
should be acquired.
@param tables_end End of list of tables.
@param lock_wait_timeout Seconds to wait before timeout.
@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)
*/
static bool
open_tables_acquire_upgradable_mdl(THD *thd, TABLE_LIST *tables_start,
TABLE_LIST *tables_end,
Open_table_context *ot_ctx,
uint flags)
bool
lock_table_names(THD *thd,
TABLE_LIST *tables_start, TABLE_LIST *tables_end,
ulong lock_wait_timeout, uint flags)
{
MDL_request_list mdl_requests;
TABLE_LIST *table;
MDL_request global_request;
Hash_set<TABLE_LIST, schema_set_get_key> schema_set;
DBUG_ASSERT(!thd->locked_tables_mode);
@@ -4726,30 +4699,37 @@ open_tables_acquire_upgradable_mdl(THD *thd, TABLE_LIST *tables_start,
(table->open_type != OT_BASE_ONLY &&
! (flags & MYSQL_OPEN_SKIP_TEMPORARY) &&
find_temporary_table(thd, table))))
{
if (schema_set.insert(table))
return TRUE;
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)
return TRUE;
mdl_requests.push_front(global_request);
/*
Scoped locks: Take intention exclusive locks on all involved
schemas.
*/
Hash_set<TABLE_LIST, schema_set_get_key>::Iterator it(schema_set);
while ((table= it++))
{
MDL_request *schema_request= new (thd->mem_root) MDL_request;
if (schema_request == NULL)
return TRUE;
schema_request->init(MDL_key::SCHEMA, table->db, "",
MDL_INTENTION_EXCLUSIVE);
mdl_requests.push_front(schema_request);
}
/* Take the global intention exclusive lock. */
global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE);
mdl_requests.push_front(&global_request);
}
if (thd->mdl_context.acquire_locks(&mdl_requests, ot_ctx->get_timeout()))
if (thd->mdl_context.acquire_locks(&mdl_requests, lock_wait_timeout))
return TRUE;
for (table= tables_start; table && table != tables_end;
table= table->next_global)
{
if (table->mdl_request.type >= MDL_SHARED_NO_WRITE)
table->mdl_request.ticket= NULL;
}
return FALSE;
}
@@ -4921,12 +4901,21 @@ restart:
goto err;
}
}
else if (open_tables_acquire_upgradable_mdl(thd, *start,
thd->lex->first_not_own_table(),
&ot_ctx, flags))
else
{
error= TRUE;
goto err;
TABLE_LIST *table;
if (lock_table_names(thd, *start, thd->lex->first_not_own_table(),
ot_ctx.get_timeout(), flags))
{
error= TRUE;
goto err;
}
for (table= *start; table && table != thd->lex->first_not_own_table();
table= table->next_global)
{
if (table->mdl_request.type >= MDL_SHARED_NO_WRITE)
table->mdl_request.ticket= NULL;
}
}
}