1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-30 16:24:05 +03:00

Backport of:

------------------------------------------------------------
revno: 2630.4.11
committer: Dmitry Lenev <dlenev@mysql.com>
branch nick: mysql-6.0-3726-w
timestamp: Tue 2008-05-27 21:31:53 +0400
message:
  WL#3726 "DDL locking for all metadata objects".

  After review fixes in progress.

  Changed mysql_lock_tables() to be no longer responsible for
  reopening table if waiting for the lock on it was aborted.
  This allows to get rid of several annoying functions.


sql/ha_ndbcluster_binlog.cc:
  lock_tables() now also accepts set of options to be passed to
  mysql_lock_tables().
sql/lock.cc:
  Changed mysql_lock_tables() always requests caller to reopen
  table instead doing this on its own when waiting for lock was
  aborted. This allows us to get rid of several functions which
  were used in rare cases and significantly complicated our life.
sql/log_event_old.cc:
  lock_tables() now also accepts set of options to be passed
  to mysql_lock_tables().
sql/mysql_priv.h:
  Now mysql_lock_tables() always requests caller to reopen
  table instead doing this on its own when waiting for lock was
  aborted. So we no longer need wait_for_tables() and
  table_is_used() functions and MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN
  flag.
  open_and_lock_table_derived() and lock_tables() now accept
  options to be passed to open_tables() and mysql_lock_tables()
  calls.
sql/sql_base.cc:
  Since now mysql_lock_tables() always requests caller to
  reopen table instead doing this on its own when waiting for
  lock was aborted we no longer need wait_for_tables(),
  table_is_used() and close_old_data_files() functions.
  open_and_lock_table_derived() and lock_tables() now accept
  options to be passed to open_tables() and mysql_lock_tables()
  calls. This was needed in order to get rid of redundant code
  in open_system_tables_for_read() function.
sql/sql_handler.cc:
  mysql_lock_tables() is now always requests reopen if waiting
  for lock is aborted. MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN flag
  was removed.
sql/sql_insert.cc:
  handle_delayed_insert():
  Since mysql_lock_tables() is no longer responsible for
  reopening tables when waiting for lock was aborted we have
  to handle such situation outside of this function. To simplify
  this extracted code opening table for delayed insert thread to
  separate method of Delayed_insert class.
sql/sql_update.cc:
  lock_tables() now also accepts set of options to be passed
  to mysql_lock_tables().
This commit is contained in:
Konstantin Osipov
2009-11-30 22:03:37 +03:00
parent de1979d3d6
commit aeebede195
8 changed files with 152 additions and 283 deletions

View File

@ -127,8 +127,6 @@ static bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias,
uint db_stat, uint prgflag,
uint ha_open_flags, TABLE *outparam,
TABLE_LIST *table_desc, MEM_ROOT *mem_root);
static void close_old_data_files(THD *thd, TABLE *table, bool morph_locks,
bool send_refresh);
static bool tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context);
static bool
has_write_table_with_auto_increment(TABLE_LIST *tables);
@ -3439,9 +3437,6 @@ bool reopen_tables(THD *thd, bool get_locks)
TABLE *err_tables= NULL, *err_tab_tmp;
bool error=0, not_used;
bool merge_table_found= FALSE;
const uint flags= MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN |
MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK |
MYSQL_LOCK_IGNORE_FLUSH;
DBUG_ENTER("reopen_tables");
@ -3534,10 +3529,14 @@ bool reopen_tables(THD *thd, bool get_locks)
if (tables != tables_ptr) // Should we get back old locks
{
MYSQL_LOCK *lock;
const uint flags= MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK |
MYSQL_LOCK_IGNORE_FLUSH;
/*
We should always get these locks. Anyway, we must not go into
wait_for_tables() as it tries to acquire LOCK_open, which is
already locked.
Since we have exclusive metadata locks on tables which we
are reopening we should always get these locks (We won't
wait on table level locks so can't get aborted and we ignore
other threads that set THD::some_tables_deleted by using
MYSQL_LOCK_IGNORE_FLUSH flag).
*/
thd->some_tables_deleted=0;
if ((lock= mysql_lock_tables(thd, tables, (uint) (tables_ptr - tables),
@ -3565,165 +3564,6 @@ bool reopen_tables(THD *thd, bool get_locks)
}
/**
Close handlers for tables in list, but leave the TABLE structure
intact so that we can re-open these quickly.
@param thd Thread context
@param table Head of the list of TABLE objects
@param morph_locks TRUE - remove locks which we have on tables being closed
but ensure that no DML or DDL will sneak in before
we will re-open the table (i.e. temporarily morph
our table-level locks into name-locks).
FALSE - otherwise
@param send_refresh Should we awake waiters even if we didn't close any tables?
*/
static void close_old_data_files(THD *thd, TABLE *table, bool morph_locks,
bool send_refresh)
{
bool found= send_refresh;
DBUG_ENTER("close_old_data_files");
for (; table ; table=table->next)
{
DBUG_PRINT("tcache", ("checking table: '%s'.'%s' 0x%lx",
table->s->db.str, table->s->table_name.str,
(long) table));
DBUG_PRINT("tcache", ("needs refresh: %d is open: %u",
table->needs_reopen_or_name_lock(), table->db_stat));
/*
Reopen marked for flush.
*/
if (table->needs_reopen_or_name_lock())
{
found=1;
if (table->db_stat)
{
if (morph_locks)
{
/*
Forward lock handling to MERGE parent. But unlock parent
once only.
*/
TABLE *ulcktbl= table->parent ? table->parent : table;
if (ulcktbl->lock_count)
{
/*
Wake up threads waiting for table-level lock on this table
so they won't sneak in when we will temporarily remove our
lock on it. This will also give them a chance to close their
instances of this table.
*/
mysql_lock_abort(thd, ulcktbl, TRUE);
mysql_lock_remove(thd, thd->locked_tables, ulcktbl, TRUE);
ulcktbl->lock_count= 0;
}
if ((ulcktbl != table) && ulcktbl->db_stat)
{
/*
Close the parent too. Note that parent can come later in
the list of tables. It will then be noticed as closed and
as a placeholder. When this happens, do not clear the
placeholder flag. See the branch below ("***").
*/
ulcktbl->open_placeholder= 1;
close_handle_and_leave_table_as_lock(ulcktbl);
}
/*
We want to protect the table from concurrent DDL operations
(like RENAME TABLE) until we will re-open and re-lock it.
*/
table->open_placeholder= 1;
}
close_handle_and_leave_table_as_lock(table);
}
else if (table->open_placeholder && !morph_locks)
{
/*
We come here only in close-for-back-off scenario. So we have to
"close" create placeholder here to avoid deadlocks (for example,
in case of concurrent execution of CREATE TABLE t1 SELECT * FROM t2
and RENAME TABLE t2 TO t1). In close-for-re-open scenario we will
probably want to let it stay.
Note "***": We must not enter this branch if the placeholder
flag has been set because of a former close through a child.
See above the comment that refers to this note.
*/
table->open_placeholder= 0;
}
}
}
if (found)
broadcast_refresh();
DBUG_VOID_RETURN;
}
/*
Wait until all threads has closed the tables in the list
We have also to wait if there is thread that has a lock on this table even
if the table is closed
*/
bool table_is_used(TABLE *table, bool wait_for_name_lock)
{
DBUG_ENTER("table_is_used");
do
{
char *key= table->s->table_cache_key.str;
uint key_length= table->s->table_cache_key.length;
/* Note that 'table' can use artificial TABLE_SHARE object. */
TABLE_SHARE *share= (TABLE_SHARE*)my_hash_search(&table_def_cache,
(uchar*) key, key_length);
if (share && !share->used_tables.is_empty() &&
share->version != refresh_version)
DBUG_RETURN(1);
} while ((table=table->next));
DBUG_RETURN(0);
}
/*
Wait until all used tables are refreshed.
FIXME We should remove this function since for several functions which
are invoked by it new scenarios of usage are introduced, while
this function implements optimization useful only in rare cases.
*/
bool wait_for_tables(THD *thd)
{
bool result;
DBUG_ENTER("wait_for_tables");
thd_proc_info(thd, "Waiting for tables");
pthread_mutex_lock(&LOCK_open);
while (!thd->killed)
{
thd->some_tables_deleted=0;
close_old_data_files(thd,thd->open_tables,0,dropping_tables != 0);
mysql_ha_flush(thd);
if (!table_is_used(thd->open_tables,1))
break;
(void) pthread_cond_wait(&COND_refresh,&LOCK_open);
}
if (thd->killed)
result= 1; // aborted
else
{
/* Now we can open all tables without any interference */
thd_proc_info(thd, "Reopen tables");
thd->version= refresh_version;
result=reopen_tables(thd, 0);
}
pthread_mutex_unlock(&LOCK_open);
thd_proc_info(thd, 0);
DBUG_RETURN(result);
}
/**
Unlock and close tables open and locked by LOCK TABLES statement.
@ -5190,14 +5030,8 @@ retry:
DBUG_ASSERT(thd->lock == 0); // You must lock everything at once
if ((table->reginfo.lock_type= lock_type) != TL_UNLOCK)
if (! (thd->lock= mysql_lock_tables(thd, &table_list->table, 1,
(lock_flags |
MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN),
&refresh)))
lock_flags, &refresh)))
{
/*
FIXME: Actually we should get rid of MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN option
as all reopening should happen outside of mysql_lock_tables() code.
*/
if (refresh)
{
close_thread_tables(thd);
@ -5222,6 +5056,8 @@ retry:
open_and_lock_tables_derived()
thd - thread handler
tables - list of tables for open&locking
flags - set of options to be used to open and lock tables (see
open_tables() and mysql_lock_tables() for details).
derived - if to handle derived tables
RETURN
@ -5239,7 +5075,8 @@ retry:
the third argument set appropriately.
*/
int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived)
int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived,
uint flags)
{
uint counter;
bool need_reopen;
@ -5248,7 +5085,7 @@ int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived)
for ( ; ; )
{
if (open_tables(thd, &tables, &counter, 0))
if (open_tables(thd, &tables, &counter, flags))
DBUG_RETURN(-1);
DBUG_EXECUTE_IF("sleep_open_and_lock_after_open", {
@ -5257,7 +5094,8 @@ int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived)
my_sleep(6000000);
thd->proc_info= old_proc_info;});
if (!lock_tables(thd, tables, counter, &need_reopen))
if (!lock_tables(thd, tables, counter, flags,
&need_reopen))
break;
if (!need_reopen)
DBUG_RETURN(-1);
@ -5492,6 +5330,7 @@ int decide_logging_format(THD *thd, TABLE_LIST *tables)
thd Thread handler
tables Tables to lock
count Number of opened tables
flags Options (see mysql_lock_tables() for details)
need_reopen Out parameter which if TRUE indicates that some
tables were dropped or altered during this call
and therefore invoker should reopen tables and
@ -5512,7 +5351,8 @@ int decide_logging_format(THD *thd, TABLE_LIST *tables)
-1 Error
*/
int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen)
int lock_tables(THD *thd, TABLE_LIST *tables, uint count,
uint flags, bool *need_reopen)
{
TABLE_LIST *table;
@ -5540,7 +5380,6 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen)
{
DBUG_ASSERT(thd->lock == 0); // You must lock everything at once
TABLE **start,**ptr;
uint lock_flag= MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN;
if (!(ptr=start=(TABLE**) thd->alloc(sizeof(TABLE*)*count)))
DBUG_RETURN(-1);
@ -5573,7 +5412,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen)
DEBUG_SYNC(thd, "before_lock_tables_takes_lock");
if (! (thd->lock= mysql_lock_tables(thd, start, (uint) (ptr - start),
lock_flag, need_reopen)))
flags, need_reopen)))
{
if (thd->lex->requires_prelocking())
{
@ -9190,41 +9029,38 @@ bool
open_system_tables_for_read(THD *thd, TABLE_LIST *table_list,
Open_tables_state *backup)
{
Query_tables_list query_tables_list_backup;
LEX *lex= thd->lex;
DBUG_ENTER("open_system_tables_for_read");
alloc_mdl_locks(table_list, thd->mem_root);
/*
Besides using new Open_tables_state for opening system tables,
we also have to backup and reset/and then restore part of LEX
which is accessed by open_tables() in order to determine if
prelocking is needed and what tables should be added for it.
close_system_tables() doesn't require such treatment.
*/
lex->reset_n_backup_query_tables_list(&query_tables_list_backup);
thd->reset_n_backup_open_tables_state(backup);
uint count= 0;
enum_open_table_action not_used;
bool not_used_2;
if (open_and_lock_tables_derived(thd, table_list, FALSE,
MYSQL_LOCK_IGNORE_FLUSH))
{
lex->restore_backup_query_tables_list(&query_tables_list_backup);
goto error;
}
for (TABLE_LIST *tables= table_list; tables; tables= tables->next_global)
{
TABLE *table= open_table(thd, tables, thd->mem_root, &not_used,
MYSQL_LOCK_IGNORE_FLUSH);
if (!table)
goto error;
DBUG_ASSERT(table->s->table_category == TABLE_CATEGORY_SYSTEM);
table->use_all_columns();
table->reginfo.lock_type= tables->lock_type;
tables->table= table;
count++;
DBUG_ASSERT(tables->table->s->table_category == TABLE_CATEGORY_SYSTEM);
tables->table->use_all_columns();
}
lex->restore_backup_query_tables_list(&query_tables_list_backup);
{
TABLE **list= (TABLE**) thd->alloc(sizeof(TABLE*) * count);
TABLE **ptr= list;
for (TABLE_LIST *tables= table_list; tables; tables= tables->next_global)
*(ptr++)= tables->table;
thd->lock= mysql_lock_tables(thd, list, count,
MYSQL_LOCK_IGNORE_FLUSH, &not_used_2);
}
if (thd->lock)
DBUG_RETURN(FALSE);
DBUG_RETURN(FALSE);
error:
close_system_tables(thd, backup);