Commit 2f265740 authored by Konstantin Osipov's avatar Konstantin Osipov

Backport of:

------------------------------------------------------------
revno: 2617.68.7
committer: Dmitry Lenev <dlenev@mysql.com>
branch nick: mysql-next-bg46044
timestamp: Thu 2009-08-27 10:22:17 +0400
message:
  Fix for bug #46044 "MDL deadlock on LOCK TABLE + CREATE TABLE HIGH_PRIORITY
  FOR UPDATE".

  Deadlock occured when during execution of query to I_S we tried to open
  a table or its .FRM in order to get information about it and had to wait
  because we have encountered exclusive metadata lock on this table held by
  a DDL operation from another connection which in its turn waited for some
  resource currently owned by connection executing this I_S query.
  For example, this might have happened if one under LOCK TABLES executed I_S
  query targeted to particular table (which was not among locked) and also
  concurrently tried to create this table using CREATE TABLE SELECT which
  had to wait for one of tables locked by the first connection.
  Another situation in which deadlock might have occured is when I_S query,
  which was executed as part of transaction, tried to get information about
  table which just has been dropped by concurrent DROP TABLES executed under
  LOCK TABLES and this DROP TABLES for its completion also had to wait
  transaction from the first connection.

  This problem stemmed from the fact that opening of tables/.FRMs for I_S
  filling is happening outside of connection's main MDL_context so code
  which tries to detect deadlocks due to conflicting metadata locks doesn't
  work in this case. Indeed, this led to deadlocks when during I_S filling
  we tried to wait for conflicting metadata lock to go away, while its owner
  was waiting for some resource held by connection executing I_S query.

  This patch solves this problem by avoiding waiting in such situation.
  Instead we skip this table and produce warning that information about
  it was omitted from I_S due to concurrent DDL operation. We still wait
  for conflicting metadata lock to go away when it is known that deadlock
  is not possible (i.e. when connection executing I_S query does not hold
  any metadata or table-level locks).
  Basically, we apply our standard deadlock avoidance technique for metadata
  locks to the process of filling of I_S tables but replace ER_LOCK_DEADLOCK
  error with a warning.
  Note that this change is supposed to be safe for 'mysqldump' since the
  only its mode which is affected by this change is --single-transaction mode
  is not safe in the presence of concurrent DDL anyway (and this fact is
  documented). Other modes are unaffected because they either use
  SHOW TABLES/SELECT * FROM I_S.TABLE_NAMES which do not take any metadata
  locks in the process of I_S table filling and thus cannot skip tables or
  execute I_S queries for tables which were previously locked by LOCK TABLES
  (or in the presence of global read lock) which excludes possibility of
  encountering conflicting metadata lock.


mysql-test/r/mdl_sync.result:
  Added test for bug #46044 "MDL deadlock on LOCK TABLE + CREATE TABLE
  HIGH_PRIORITY FOR UPDATE".
mysql-test/t/mdl_sync.test:
  Added test for bug #46044 "MDL deadlock on LOCK TABLE + CREATE TABLE
  HIGH_PRIORITY FOR UPDATE".
sql/mysql_priv.h:
  Added a new flag for open_table() call which allows it to fail
  with an error in cases when conflicting metadata lock is discovered
  instead of waiting until this lock goes away.
sql/share/errmsg-utf8.txt:
  Added error/warning message to be generated in cases when information
  about table is omitted from I_S since there is conflicting metadata lock
  on the table.
sql/share/errmsg.txt:
  Added error/warning message to be generated in cases when information
  about table is omitted from I_S since there is conflicting metadata lock
  on the table.
sql/sql_base.cc:
  Added a new flag for open_table() call which allows it to fail
  with an error in cases when conflicting metadata lock is discovered
  instead of waiting until this lock goes away.
sql/sql_show.cc:
  When we are opening a table (or just .FRM) in order to fill I_S with
  information about this table and encounter conflicting metadata lock
  waiting for this lock to go away can lead to a deadlock in some
  situations (under LOCK TABLES, within transaction, etc.). To avoid
  these deadlocks we detect such situations and don't do waiting.
  Instead, we skip table for which we have conflicting metadata lock,
  thus omitting information about it from I_S table, and produce an
  appropriate warning.
parent d6aaa5fd
...@@ -62,3 +62,114 @@ unlock tables; ...@@ -62,3 +62,114 @@ unlock tables;
# Reap INSERT. # Reap INSERT.
# Clean-up. # Clean-up.
drop tables t1, t2, t3, t5; drop tables t1, t2, t3, t5;
#
# Bug #46044 "MDL deadlock on LOCK TABLE + CREATE TABLE HIGH_PRIORITY
# FOR UPDATE"
#
drop tables if exists t1, t2;
create table t1 (i int);
# Let us check that we won't deadlock if during filling
# of I_S table we encounter conflicting metadata lock
# which owner is in its turn waiting for our connection.
lock tables t1 write;
# Switching to connection 'con46044'.
# Sending:
create table t2 select * from t1;;
# Switching to connection 'default'.
# Waiting until CREATE TABLE ... SELECT ... is blocked.
# First let us check that SHOW FIELDS/DESCRIBE doesn't
# gets blocked and emits and error.
show fields from t2;
ERROR HY000: Table 'test'.'t2' was skipped since its definition is being modified by concurrent DDL statement
# Now test for I_S query which reads only .FRMs.
#
# Query below should only emit a warning.
select column_name from information_schema.columns
where table_schema='test' and table_name='t2';
column_name
Warnings:
Warning 1652 Table 'test'.'t2' was skipped since its definition is being modified by concurrent DDL statement
# Finally, test for I_S query which does full-blown table open.
#
# Query below should not be blocked. Warning message should be
# stored in the 'table_comment' column.
select table_name, table_type, auto_increment, table_comment
from information_schema.tables where table_schema='test' and table_name='t2';
table_name table_type auto_increment table_comment
t2 BASE TABLE NULL Table 'test'.'t2' was skipped since its definition is being modified by concurre
# Switching to connection 'default'.
unlock tables;
# Switching to connection 'con46044'.
# Reaping CREATE TABLE ... SELECT ... .
drop table t2;
#
# Let us also check that queries to I_S wait for conflicting metadata
# locks to go away instead of skipping table with a warning in cases
# when deadlock is not possible. This is a nice thing from compatibility
# and ease of use points of view.
#
# We check same three queries to I_S in this new situation.
# Switching to connection 'con46044_2'.
lock tables t1 write;
# Switching to connection 'con46044'.
# Sending:
create table t2 select * from t1;;
# Switching to connection 'default'.
# Waiting until CREATE TABLE ... SELECT ... is blocked.
# Let us check that SHOW FIELDS/DESCRIBE gets blocked.
# Sending:
show fields from t2;;
# Switching to connection 'con46044_2'.
# Wait until SHOW FIELDS gets blocked.
unlock tables;
# Switching to connection 'con46044'.
# Reaping CREATE TABLE ... SELECT ... .
# Switching to connection 'default'.
# Reaping SHOW FIELDS ...
Field Type Null Key Default Extra
i int(11) YES NULL
drop table t2;
# Switching to connection 'con46044_2'.
lock tables t1 write;
# Switching to connection 'con46044'.
# Sending:
create table t2 select * from t1;;
# Switching to connection 'default'.
# Waiting until CREATE TABLE ... SELECT ... is blocked.
# Check that I_S query which reads only .FRMs gets blocked.
# Sending:
select column_name from information_schema.columns where table_schema='test' and table_name='t2';;
# Switching to connection 'con46044_2'.
# Wait until SELECT COLUMN_NAME FROM I_S.COLUMNS gets blocked.
unlock tables;
# Switching to connection 'con46044'.
# Reaping CREATE TABLE ... SELECT ... .
# Switching to connection 'default'.
# Reaping SELECT COLUMN_NAME FROM I_S.COLUMNS
column_name
i
drop table t2;
# Switching to connection 'con46044_2'.
lock tables t1 write;
# Switching to connection 'con46044'.
# Sending:
create table t2 select * from t1;;
# Switching to connection 'default'.
# Waiting until CREATE TABLE ... SELECT ... is blocked.
# Finally, check that I_S query which does full-blown table open
# also gets blocked.
# Sending:
select table_name, table_type, auto_increment, table_comment from information_schema.tables where table_schema='test' and table_name='t2';;
# Switching to connection 'con46044_2'.
# Wait until SELECT ... FROM I_S.TABLES gets blocked.
unlock tables;
# Switching to connection 'con46044'.
# Reaping CREATE TABLE ... SELECT ... .
# Switching to connection 'default'.
# Reaping SELECT ... FROM I_S.TABLES
table_name table_type auto_increment table_comment
t2 BASE TABLE NULL
drop table t2;
# Switching to connection 'default'.
# Clean-up.
drop table t1;
...@@ -141,6 +141,208 @@ disconnect con2root; ...@@ -141,6 +141,208 @@ disconnect con2root;
drop tables t1, t2, t3, t5; drop tables t1, t2, t3, t5;
--echo #
--echo # Bug #46044 "MDL deadlock on LOCK TABLE + CREATE TABLE HIGH_PRIORITY
--echo # FOR UPDATE"
--echo #
--disable_warnings
drop tables if exists t1, t2;
--enable_warnings
connect (con46044, localhost, root,,);
connect (con46044_2, localhost, root,,);
connection default;
create table t1 (i int);
--echo # Let us check that we won't deadlock if during filling
--echo # of I_S table we encounter conflicting metadata lock
--echo # which owner is in its turn waiting for our connection.
lock tables t1 write;
--echo # Switching to connection 'con46044'.
connection con46044;
--echo # Sending:
--send create table t2 select * from t1;
--echo # Switching to connection 'default'.
connection default;
--echo # Waiting until CREATE TABLE ... SELECT ... is blocked.
let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Table lock" and info = "create table t2 select * from t1";
--source include/wait_condition.inc
--echo # First let us check that SHOW FIELDS/DESCRIBE doesn't
--echo # gets blocked and emits and error.
--error ER_WARN_I_S_SKIPPED_TABLE
show fields from t2;
--echo # Now test for I_S query which reads only .FRMs.
--echo #
--echo # Query below should only emit a warning.
select column_name from information_schema.columns
where table_schema='test' and table_name='t2';
--echo # Finally, test for I_S query which does full-blown table open.
--echo #
--echo # Query below should not be blocked. Warning message should be
--echo # stored in the 'table_comment' column.
select table_name, table_type, auto_increment, table_comment
from information_schema.tables where table_schema='test' and table_name='t2';
--echo # Switching to connection 'default'.
connection default;
unlock tables;
--echo # Switching to connection 'con46044'.
connection con46044;
--echo # Reaping CREATE TABLE ... SELECT ... .
--reap
drop table t2;
--echo #
--echo # Let us also check that queries to I_S wait for conflicting metadata
--echo # locks to go away instead of skipping table with a warning in cases
--echo # when deadlock is not possible. This is a nice thing from compatibility
--echo # and ease of use points of view.
--echo #
--echo # We check same three queries to I_S in this new situation.
--echo # Switching to connection 'con46044_2'.
connection con46044_2;
lock tables t1 write;
--echo # Switching to connection 'con46044'.
connection con46044;
--echo # Sending:
--send create table t2 select * from t1;
--echo # Switching to connection 'default'.
connection default;
--echo # Waiting until CREATE TABLE ... SELECT ... is blocked.
let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Table lock" and info = "create table t2 select * from t1";
--source include/wait_condition.inc
--echo # Let us check that SHOW FIELDS/DESCRIBE gets blocked.
--echo # Sending:
--send show fields from t2;
--echo # Switching to connection 'con46044_2'.
connection con46044_2;
--echo # Wait until SHOW FIELDS gets blocked.
let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Waiting for table" and info = "show fields from t2";
--source include/wait_condition.inc
unlock tables;
--echo # Switching to connection 'con46044'.
connection con46044;
--echo # Reaping CREATE TABLE ... SELECT ... .
--reap
--echo # Switching to connection 'default'.
connection default;
--echo # Reaping SHOW FIELDS ...
--reap
drop table t2;
--echo # Switching to connection 'con46044_2'.
connection con46044_2;
lock tables t1 write;
--echo # Switching to connection 'con46044'.
connection con46044;
--echo # Sending:
--send create table t2 select * from t1;
--echo # Switching to connection 'default'.
connection default;
--echo # Waiting until CREATE TABLE ... SELECT ... is blocked.
let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Table lock" and info = "create table t2 select * from t1";
--source include/wait_condition.inc
--echo # Check that I_S query which reads only .FRMs gets blocked.
--echo # Sending:
--send select column_name from information_schema.columns where table_schema='test' and table_name='t2';
--echo # Switching to connection 'con46044_2'.
connection con46044_2;
--echo # Wait until SELECT COLUMN_NAME FROM I_S.COLUMNS gets blocked.
let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Waiting for table" and
info like "select column_name from information_schema.columns%";
--source include/wait_condition.inc
unlock tables;
--echo # Switching to connection 'con46044'.
connection con46044;
--echo # Reaping CREATE TABLE ... SELECT ... .
--reap
--echo # Switching to connection 'default'.
connection default;
--echo # Reaping SELECT COLUMN_NAME FROM I_S.COLUMNS
--reap
drop table t2;
--echo # Switching to connection 'con46044_2'.
connection con46044_2;
lock tables t1 write;
--echo # Switching to connection 'con46044'.
connection con46044;
--echo # Sending:
--send create table t2 select * from t1;
--echo # Switching to connection 'default'.
connection default;
--echo # Waiting until CREATE TABLE ... SELECT ... is blocked.
let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Table lock" and info = "create table t2 select * from t1";
--source include/wait_condition.inc
--echo # Finally, check that I_S query which does full-blown table open
--echo # also gets blocked.
--echo # Sending:
--send select table_name, table_type, auto_increment, table_comment from information_schema.tables where table_schema='test' and table_name='t2';
--echo # Switching to connection 'con46044_2'.
connection con46044_2;
--echo # Wait until SELECT ... FROM I_S.TABLES gets blocked.
let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Waiting for table" and
info like "select table_name, table_type, auto_increment, table_comment from information_schema.tables%";
--source include/wait_condition.inc
unlock tables;
--echo # Switching to connection 'con46044'.
connection con46044;
--echo # Reaping CREATE TABLE ... SELECT ... .
--reap
--echo # Switching to connection 'default'.
connection default;
--echo # Reaping SELECT ... FROM I_S.TABLES
--reap
drop table t2;
--echo # Switching to connection 'default'.
connection default;
--echo # Clean-up.
disconnect con46044;
disconnect con46044_2;
drop table t1;
# Check that all connections opened by test cases in this file are really # Check that all connections opened by test cases in this file are really
# gone so execution of other tests won't be affected by their presence. # gone so execution of other tests won't be affected by their presence.
--source include/wait_until_count_sessions.inc --source include/wait_until_count_sessions.inc
...@@ -2071,6 +2071,8 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count, ...@@ -2071,6 +2071,8 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count,
#define MYSQL_OPEN_GET_NEW_TABLE 0x0080 #define MYSQL_OPEN_GET_NEW_TABLE 0x0080
/** Don't look up the table in the list of temporary tables. */ /** Don't look up the table in the list of temporary tables. */
#define MYSQL_OPEN_SKIP_TEMPORARY 0x0100 #define MYSQL_OPEN_SKIP_TEMPORARY 0x0100
/** Fail instead of waiting when conficting metadata lock is discovered. */
#define MYSQL_OPEN_FAIL_ON_MDL_CONFLICT 0x0200
/** Please refer to the internals manual. */ /** Please refer to the internals manual. */
#define MYSQL_OPEN_REOPEN (MYSQL_LOCK_IGNORE_FLUSH |\ #define MYSQL_OPEN_REOPEN (MYSQL_LOCK_IGNORE_FLUSH |\
......
...@@ -6236,12 +6236,13 @@ ER_COND_ITEM_TOO_LONG ...@@ -6236,12 +6236,13 @@ ER_COND_ITEM_TOO_LONG
eng "Data too long for condition item '%s'" eng "Data too long for condition item '%s'"
ER_UNKNOWN_LOCALE ER_UNKNOWN_LOCALE
eng "Unknown locale: '%-.64s'" eng "Unknown locale: '%-.64s'"
ER_SLAVE_IGNORE_SERVER_IDS ER_SLAVE_IGNORE_SERVER_IDS
eng "The requested server id %d clashes with the slave startup option --replicate-same-server-id" eng "The requested server id %d clashes with the slave startup option --replicate-same-server-id"
ER_QUERY_CACHE_DISABLED ER_QUERY_CACHE_DISABLED
eng "Query cache is disabled; restart the server with query_cache_type=1 to enable it" eng "Query cache is disabled; restart the server with query_cache_type=1 to enable it"
ER_WARN_I_S_SKIPPED_TABLE
eng "Table '%s'.'%s' was skipped since its definition is being modified by concurrent DDL statement"
ER_SAME_NAME_PARTITION_FIELD ER_SAME_NAME_PARTITION_FIELD
eng "Duplicate partition field name '%-.192s'" eng "Duplicate partition field name '%-.192s'"
ER_PARTITION_COLUMN_LIST_ERROR ER_PARTITION_COLUMN_LIST_ERROR
......
...@@ -6242,6 +6242,8 @@ ER_UNKNOWN_LOCALE ...@@ -6242,6 +6242,8 @@ ER_UNKNOWN_LOCALE
ER_SLAVE_IGNORE_SERVER_IDS ER_SLAVE_IGNORE_SERVER_IDS
eng "The requested server id %d clashes with the slave startup option --replicate-same-server-id" eng "The requested server id %d clashes with the slave startup option --replicate-same-server-id"
ER_WARN_I_S_SKIPPED_TABLE
eng "Table '%s'.'%s' was skipped since its definition is being modified by concurrent DDL statement"
ER_SAME_NAME_PARTITION_FIELD ER_SAME_NAME_PARTITION_FIELD
eng "Duplicate partition field name '%-.192s'" eng "Duplicate partition field name '%-.192s'"
ER_PARTITION_COLUMN_LIST_ERROR ER_PARTITION_COLUMN_LIST_ERROR
......
...@@ -2350,7 +2350,10 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, ...@@ -2350,7 +2350,10 @@ open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list,
return 1; return 1;
if (mdl_request->ticket == NULL) if (mdl_request->ticket == NULL)
{ {
(void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT); 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
(void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT);
return 1; return 1;
} }
} }
......
...@@ -2867,6 +2867,10 @@ make_table_name_list(THD *thd, List<LEX_STRING> *table_names, LEX *lex, ...@@ -2867,6 +2867,10 @@ make_table_name_list(THD *thd, List<LEX_STRING> *table_names, LEX *lex,
@param[in] thd thread handler @param[in] thd thread handler
@param[in] tables TABLE_LIST for I_S table @param[in] tables TABLE_LIST for I_S table
@param[in] schema_table pointer to I_S structure @param[in] schema_table pointer to I_S structure
@param[in] can_deadlock Indicates that deadlocks are possible
due to metadata locks, so to avoid
them we should not wait in case if
conflicting lock is present.
@param[in] open_tables_state_backup pointer to Open_tables_state object @param[in] open_tables_state_backup pointer to Open_tables_state object
which is used to save|restore original which is used to save|restore original
status of variables related to status of variables related to
...@@ -2880,6 +2884,7 @@ make_table_name_list(THD *thd, List<LEX_STRING> *table_names, LEX *lex, ...@@ -2880,6 +2884,7 @@ make_table_name_list(THD *thd, List<LEX_STRING> *table_names, LEX *lex,
static int static int
fill_schema_show_cols_or_idxs(THD *thd, TABLE_LIST *tables, fill_schema_show_cols_or_idxs(THD *thd, TABLE_LIST *tables,
ST_SCHEMA_TABLE *schema_table, ST_SCHEMA_TABLE *schema_table,
bool can_deadlock,
Open_tables_state *open_tables_state_backup) Open_tables_state *open_tables_state_backup)
{ {
LEX *lex= thd->lex; LEX *lex= thd->lex;
...@@ -2908,7 +2913,9 @@ fill_schema_show_cols_or_idxs(THD *thd, TABLE_LIST *tables, ...@@ -2908,7 +2913,9 @@ fill_schema_show_cols_or_idxs(THD *thd, TABLE_LIST *tables,
*/ */
lex->sql_command= SQLCOM_SHOW_FIELDS; lex->sql_command= SQLCOM_SHOW_FIELDS;
res= open_normal_and_derived_tables(thd, show_table_list, res= open_normal_and_derived_tables(thd, show_table_list,
MYSQL_LOCK_IGNORE_FLUSH); (MYSQL_LOCK_IGNORE_FLUSH |
(can_deadlock ?
MYSQL_OPEN_FAIL_ON_MDL_CONFLICT : 0)));
lex->sql_command= save_sql_command; lex->sql_command= save_sql_command;
/* /*
get_all_tables() returns 1 on failure and 0 on success thus get_all_tables() returns 1 on failure and 0 on success thus
...@@ -3047,12 +3054,16 @@ uint get_table_open_method(TABLE_LIST *tables, ...@@ -3047,12 +3054,16 @@ uint get_table_open_method(TABLE_LIST *tables,
/** /**
Acquire high priority share metadata lock on a table. Try acquire high priority share metadata lock on a table (with
optional wait for conflicting locks to go away).
@param thd Thread context. @param thd Thread context.
@param mdl_request Pointer to memory to be used for MDL_request @param mdl_request Pointer to memory to be used for MDL_request
object for a lock request. object for a lock request.
@param table Table list element for the table @param table Table list element for the table
@param can_deadlock Indicates that deadlocks are possible due to
metadata locks, so to avoid them we should not
wait in case if conflicting lock is present.
@note This is an auxiliary function to be used in cases when we want to @note This is an auxiliary function to be used in cases when we want to
access table's description by looking up info in TABLE_SHARE without access table's description by looking up info in TABLE_SHARE without
...@@ -3060,19 +3071,21 @@ uint get_table_open_method(TABLE_LIST *tables, ...@@ -3060,19 +3071,21 @@ uint get_table_open_method(TABLE_LIST *tables,
@note This function assumes that there are no other metadata lock requests @note This function assumes that there are no other metadata lock requests
in the current metadata locking context. in the current metadata locking context.
@retval FALSE Success @retval FALSE No error, if lock was obtained TABLE_LIST::mdl_request::ticket
is set to non-NULL value.
@retval TRUE Some error occured (probably thread was killed). @retval TRUE Some error occured (probably thread was killed).
*/ */
static bool static bool
acquire_high_prio_shared_mdl_lock(THD *thd, TABLE_LIST *table) try_acquire_high_prio_shared_mdl_lock(THD *thd, TABLE_LIST *table,
bool can_deadlock)
{ {
bool error; bool error;
table->mdl_request.init(MDL_TABLE, table->db, table->table_name, table->mdl_request.init(MDL_TABLE, table->db, table->table_name,
MDL_SHARED_HIGH_PRIO); MDL_SHARED_HIGH_PRIO);
while (!(error= while (!(error=
thd->mdl_context.try_acquire_shared_lock(&table->mdl_request)) && thd->mdl_context.try_acquire_shared_lock(&table->mdl_request)) &&
!table->mdl_request.ticket) !table->mdl_request.ticket && !can_deadlock)
{ {
MDL_request_list mdl_requests; MDL_request_list mdl_requests;
mdl_requests.push_front(&table->mdl_request); mdl_requests.push_front(&table->mdl_request);
...@@ -3092,6 +3105,10 @@ acquire_high_prio_shared_mdl_lock(THD *thd, TABLE_LIST *table) ...@@ -3092,6 +3105,10 @@ acquire_high_prio_shared_mdl_lock(THD *thd, TABLE_LIST *table)
@param[in] db_name database name @param[in] db_name database name
@param[in] table_name table name @param[in] table_name table name
@param[in] schema_table_idx I_S table index @param[in] schema_table_idx I_S table index
@param[in] can_deadlock Indicates that deadlocks are possible
due to metadata locks, so to avoid
them we should not wait in case if
conflicting lock is present.
@return Operation status @return Operation status
@retval 0 Table is processed and we can continue @retval 0 Table is processed and we can continue
...@@ -3104,13 +3121,14 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, ...@@ -3104,13 +3121,14 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table,
ST_SCHEMA_TABLE *schema_table, ST_SCHEMA_TABLE *schema_table,
LEX_STRING *db_name, LEX_STRING *db_name,
LEX_STRING *table_name, LEX_STRING *table_name,
enum enum_schema_tables schema_table_idx) enum enum_schema_tables schema_table_idx,
bool can_deadlock)
{ {
TABLE_SHARE *share; TABLE_SHARE *share;
TABLE tbl; TABLE tbl;
TABLE_LIST table_list; TABLE_LIST table_list;
uint res= 0; uint res= 0;
int error; int not_used;
char key[MAX_DBKEY_LENGTH]; char key[MAX_DBKEY_LENGTH];
uint key_length; uint key_length;
char db_name_buff[NAME_LEN + 1], table_name_buff[NAME_LEN + 1]; char db_name_buff[NAME_LEN + 1], table_name_buff[NAME_LEN + 1];
...@@ -3143,7 +3161,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, ...@@ -3143,7 +3161,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table,
simply obtaining internal lock of data-dictionary (ATM it simply obtaining internal lock of data-dictionary (ATM it
is LOCK_open) instead of obtaning full-blown metadata lock. is LOCK_open) instead of obtaning full-blown metadata lock.
*/ */
if (acquire_high_prio_shared_mdl_lock(thd, &table_list)) if (try_acquire_high_prio_shared_mdl_lock(thd, &table_list, can_deadlock))
{ {
/* /*
Some error occured (most probably we have been killed while Some error occured (most probably we have been killed while
...@@ -3153,14 +3171,30 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, ...@@ -3153,14 +3171,30 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table,
return 1; return 1;
} }
if (! table_list.mdl_request.ticket)
{
/*
We are in situation when we have encountered conflicting metadata
lock and deadlocks can occur due to waiting for it to go away.
So instead of waiting skip this table with an appropriate warning.
*/
DBUG_ASSERT(can_deadlock);
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
ER_WARN_I_S_SKIPPED_TABLE,
ER(ER_WARN_I_S_SKIPPED_TABLE),
table_list.db, table_list.table_name);
return 0;
}
key_length= create_table_def_key(thd, key, &table_list, 0); key_length= create_table_def_key(thd, key, &table_list, 0);
pthread_mutex_lock(&LOCK_open); pthread_mutex_lock(&LOCK_open);
share= get_table_share(thd, &table_list, key, share= get_table_share(thd, &table_list, key,
key_length, OPEN_VIEW, &error); key_length, OPEN_VIEW, &not_used);
if (!share) if (!share)
{ {
res= 0; res= 0;
goto err_unlock; goto end_unlock;
} }
if (share->is_view) if (share->is_view)
...@@ -3169,7 +3203,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, ...@@ -3169,7 +3203,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table,
{ {
/* skip view processing */ /* skip view processing */
res= 0; res= 0;
goto err_share; goto end_share;
} }
else if (schema_table->i_s_requested_object & OPEN_VIEW_FULL) else if (schema_table->i_s_requested_object & OPEN_VIEW_FULL)
{ {
...@@ -3178,7 +3212,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, ...@@ -3178,7 +3212,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table,
open_normal_and_derived_tables() open_normal_and_derived_tables()
*/ */
res= 1; res= 1;
goto err_share; goto end_share;
} }
} }
...@@ -3194,15 +3228,16 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, ...@@ -3194,15 +3228,16 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table,
res= schema_table->process_table(thd, &table_list, table, res= schema_table->process_table(thd, &table_list, table,
res, db_name, table_name); res, db_name, table_name);
closefrm(&tbl, true); closefrm(&tbl, true);
goto err_unlock; goto end_unlock;
} }
err_share: end_share:
release_table_share(share); release_table_share(share);
err_unlock: end_unlock:
pthread_mutex_unlock(&LOCK_open); pthread_mutex_unlock(&LOCK_open);
end:
thd->mdl_context.release_lock(table_list.mdl_request.ticket); thd->mdl_context.release_lock(table_list.mdl_request.ticket);
thd->clear_error(); thd->clear_error();
return res; return res;
...@@ -3254,8 +3289,20 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) ...@@ -3254,8 +3289,20 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
Security_context *sctx= thd->security_ctx; Security_context *sctx= thd->security_ctx;
#endif #endif
uint table_open_method; uint table_open_method;
bool can_deadlock;
DBUG_ENTER("get_all_tables"); DBUG_ENTER("get_all_tables");
/*
In cases when SELECT from I_S table being filled by this call is
part of statement which also uses other tables or is being executed
under LOCK TABLES or is part of transaction which also uses other
tables waiting for metadata locks which happens below might result
in deadlocks.
To avoid them we don't wait if conflicting metadata lock is
encountered and skip table with emitting an appropriate warning.
*/
can_deadlock= thd->mdl_context.has_locks();
lex->view_prepare_mode= TRUE; lex->view_prepare_mode= TRUE;
lex->reset_n_backup_query_tables_list(&query_tables_list_backup); lex->reset_n_backup_query_tables_list(&query_tables_list_backup);
...@@ -3275,6 +3322,7 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) ...@@ -3275,6 +3322,7 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
if (lsel && lsel->table_list.first) if (lsel && lsel->table_list.first)
{ {
error= fill_schema_show_cols_or_idxs(thd, tables, schema_table, error= fill_schema_show_cols_or_idxs(thd, tables, schema_table,
can_deadlock,
&open_tables_state_backup); &open_tables_state_backup);
goto err; goto err;
} }
...@@ -3390,7 +3438,8 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) ...@@ -3390,7 +3438,8 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
!with_i_schema) !with_i_schema)
{ {
if (!fill_schema_table_from_frm(thd, table, schema_table, db_name, if (!fill_schema_table_from_frm(thd, table, schema_table, db_name,
table_name, schema_table_idx)) table_name, schema_table_idx,
can_deadlock))
continue; continue;
} }
...@@ -3415,7 +3464,8 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) ...@@ -3415,7 +3464,8 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
show_table_list->i_s_requested_object= show_table_list->i_s_requested_object=
schema_table->i_s_requested_object; schema_table->i_s_requested_object;
res= open_normal_and_derived_tables(thd, show_table_list, res= open_normal_and_derived_tables(thd, show_table_list,
MYSQL_LOCK_IGNORE_FLUSH); (MYSQL_LOCK_IGNORE_FLUSH |
(can_deadlock ? MYSQL_OPEN_FAIL_ON_MDL_CONFLICT : 0)));
lex->sql_command= save_sql_command; lex->sql_command= save_sql_command;
/* /*
XXX: show_table_list has a flag i_is_requested, XXX: show_table_list has a flag i_is_requested,
......
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