Commit 8cf7e345 authored by Sergey Vojtovich's avatar Sergey Vojtovich Committed by Monty

Moved early check for table existance to mysql_execute_command()

MDEV-17772 - 3 way lock : ALTER, MDL, BACKUP STAGE BLOCK_DDL

While waiting for a (potentially long) RO transaction or SELECT, DDL and
LOCK TABLES ... WRITE hold protection against FTWRL and BACKUP STAGE.

This effectively makes FTWRL/BACKUP STAGE indirectly wait for this RO
transaction or SELECT to finish. Which is not great, as otherwise we
could do something useful meanwhile.

With this patch BACKUP lock is attempted to be acquired after TABLE/SCHEMA
locks. If this attempt fails, TABLE/SCHEMA locks gets released and we
start waiting for BACKUP lock. When wait finishes, BACKUP lock is released
(to avoid deadlocks) and we attempt to acquire all locks once again.

Other changes:
- Take MDL lock before testing if table exists as part of
  CREATE TABLE ... IF EXISTS.  This change was an effect of changes in
  lock_table_name and removes an inconsistency where one could get
  different error messages from CREATE TABLE .. IF EXISTS depending on
  active mdl locks.
  One effect of this change is that we don't binary log CREATE TABLE IF
  EXISTS if the table exists.  This was done because old code was sometimes
  behaving inconsistenly (it was logged some time and not other times)
  and sending the query to the slave could make the slave even more
  inconsistent as there is not guarantee that the new table will have
  the same definition as the old table on the master.
parent c0ca164b
......@@ -107,12 +107,10 @@ backup stage start;
backup stage flush;
SET STATEMENT lock_wait_timeout=0 FOR SELECT * FROM t1;
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
SET STATEMENT lock_wait_timeout=0 FOR backup stage block_ddl;
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
backup stage block_ddl;
SELECT LOCK_MODE, LOCK_TYPE, TABLE_SCHEMA, TABLE_NAME FROM information_schema.metadata_lock_info;
LOCK_MODE LOCK_TYPE TABLE_SCHEMA TABLE_NAME
MDL_BACKUP_DDL Backup lock
MDL_BACKUP_FLUSH Backup lock
MDL_BACKUP_WAIT_DDL Backup lock
MDL_SHARED_WRITE Table metadata lock test t1
MDL_INTENTION_EXCLUSIVE Schema metadata lock test
backup stage end;
......
......@@ -142,8 +142,7 @@ let $wait_condition=
--error ER_LOCK_WAIT_TIMEOUT
SET STATEMENT lock_wait_timeout=0 FOR SELECT * FROM t1;
--error ER_LOCK_WAIT_TIMEOUT
SET STATEMENT lock_wait_timeout=0 FOR backup stage block_ddl;
backup stage block_ddl;
SELECT LOCK_MODE, LOCK_TYPE, TABLE_SCHEMA, TABLE_NAME FROM information_schema.metadata_lock_info;
backup stage end;
......@@ -195,15 +194,10 @@ SET STATEMENT lock_wait_timeout=0 FOR DROP TABLE t1;
connection con2;
backup stage start;
backup stage flush;
--send backup stage block_ddl
backup stage block_ddl;
connection default;
let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Waiting for backup lock";
--source include/wait_condition.inc
commit;
connection con2;
--reap
backup stage end;
connection con1;
--reap # DROP TABLE
......
#
# Make sure pending LOCK TABLES doesn't block BACKUP STAGE
#
CREATE TABLE t1(a INT);
LOCK TABLE t1 READ;
#
connect con1,localhost,root,,;
SET DEBUG_SYNC= 'mdl_acquire_lock_wait SIGNAL ready';
LOCK TABLE t1 WRITE;
#
connect con2,localhost,root,,;
SET DEBUG_SYNC= 'now WAIT_FOR ready';
BACKUP STAGE START;
BACKUP STAGE FLUSH;
BACKUP STAGE BLOCK_DDL;
BACKUP STAGE END;
disconnect con2;
#
connection default;
UNLOCK TABLES;
#
connection con1;
UNLOCK TABLES;
disconnect con1;
#
connection default;
DROP TABLE t1;
SET DEBUG_SYNC= 'RESET';
########################################################################
# Tests for BACKUP STAGE locking that requires debug.
########################################################################
--source include/have_debug_sync.inc
--echo #
--echo # Make sure pending LOCK TABLES doesn't block BACKUP STAGE
--echo #
CREATE TABLE t1(a INT);
LOCK TABLE t1 READ;
--echo #
connect (con1,localhost,root,,);
SET DEBUG_SYNC= 'mdl_acquire_lock_wait SIGNAL ready';
--send LOCK TABLE t1 WRITE
--echo #
connect (con2,localhost,root,,);
SET DEBUG_SYNC= 'now WAIT_FOR ready';
BACKUP STAGE START;
BACKUP STAGE FLUSH;
BACKUP STAGE BLOCK_DDL;
BACKUP STAGE END;
disconnect con2;
--echo #
connection default;
UNLOCK TABLES;
--echo #
connection con1;
reap;
UNLOCK TABLES;
disconnect con1;
--echo #
connection default;
DROP TABLE t1;
SET DEBUG_SYNC= 'RESET';
......@@ -322,8 +322,6 @@ Log_name Pos Event_type Server_id End_log_pos Info
# # Gtid 1 # GTID #-#-#
# # Query 1 # use `test`; CREATE TABLE t1(a INT, b INT)
# # Gtid 1 # GTID #-#-#
# # Query 1 # use `test`; CREATE TABLE IF NOT EXISTS t1(a INT, b INT)
# # Gtid 1 # GTID #-#-#
# # Query 1 # use `test`; CREATE OR REPLACE INDEX i1 ON t1(a)
# # Gtid 1 # GTID #-#-#
# # Query 1 # use `test`; CREATE OR REPLACE INDEX i1 ON t1(a)
......@@ -377,8 +375,6 @@ Log_name Pos Event_type Server_id End_log_pos Info
# # Gtid 1 # GTID #-#-#
# # Query 1 # use `test`; CREATE TABLE t1(a INT, b INT)
# # Gtid 1 # GTID #-#-#
# # Query 1 # use `test`; CREATE TABLE IF NOT EXISTS t1(a INT, b INT)
# # Gtid 1 # GTID #-#-#
# # Query 1 # use `test`; CREATE INDEX IF NOT EXISTS i1 ON t1(a)
# # Gtid 1 # GTID #-#-#
# # Query 1 # use `test`; CREATE INDEX IF NOT EXISTS i1 ON t1(a)
......
......@@ -1693,13 +1693,39 @@ disconnect con2;
#
connection default;
FLUSH TABLES WITH READ LOCK;
UNLOCK TABLES;
HANDLER t1 CLOSE;
#
connection con1;
UNLOCK TABLES;
disconnect con1;
#
connection default;
DROP TABLE t1;
SET DEBUG_SYNC= 'RESET';
#
# Make sure pending LOCK TABLES doesn't block FTWRL
#
CREATE TABLE t1(a INT);
LOCK TABLE t1 READ;
#
connect con1,localhost,root,,;
SET DEBUG_SYNC= 'mdl_acquire_lock_wait SIGNAL ready';
LOCK TABLE t1 WRITE;
#
connect con2,localhost,root,,;
SET DEBUG_SYNC= 'now WAIT_FOR ready';
FLUSH TABLES WITH READ LOCK;
UNLOCK TABLES;
disconnect con2;
#
connection default;
UNLOCK TABLES;
HANDLER t1 CLOSE;
#
connection con1;
UNLOCK TABLES;
disconnect con1;
#
connection default;
DROP TABLE t1;
SET DEBUG_SYNC= 'RESET';
......@@ -2037,7 +2037,9 @@ disconnect con2;
--echo #
connection default;
--send FLUSH TABLES WITH READ LOCK
FLUSH TABLES WITH READ LOCK;
UNLOCK TABLES;
HANDLER t1 CLOSE;
--echo #
connection con1;
......@@ -2047,8 +2049,39 @@ disconnect con1;
--echo #
connection default;
DROP TABLE t1;
SET DEBUG_SYNC= 'RESET';
--echo #
--echo # Make sure pending LOCK TABLES doesn't block FTWRL
--echo #
CREATE TABLE t1(a INT);
LOCK TABLE t1 READ;
--echo #
connect (con1,localhost,root,,);
SET DEBUG_SYNC= 'mdl_acquire_lock_wait SIGNAL ready';
--send LOCK TABLE t1 WRITE
--echo #
connect (con2,localhost,root,,);
SET DEBUG_SYNC= 'now WAIT_FOR ready';
FLUSH TABLES WITH READ LOCK;
UNLOCK TABLES;
disconnect con2;
--echo #
connection default;
UNLOCK TABLES;
--echo #
connection con1;
reap;
UNLOCK TABLES;
HANDLER t1 CLOSE;
disconnect con1;
--echo #
connection default;
DROP TABLE t1;
SET DEBUG_SYNC= 'RESET';
......@@ -3124,72 +3124,3 @@ connection default;
SET debug_sync='RESET';
DROP TABLE t1;
disconnect con1;
#
# MDEV-5336 - Implement LOCK FOR BACKUP
#
# Make sure deadlock detector prefers FTWRL connection as a victim
# and FTWRL retries lock attempt. This deadlock was present before
# MDEV-5336.
CREATE TABLE t1(a INT) ENGINE=InnoDB;
CREATE TABLE t2(a INT) ENGINE=InnoDB;
BEGIN;
SELECT * FROM t2;
a
#
connect con1,localhost,root,,;
SET DEBUG_SYNC='mdl_acquire_lock_wait SIGNAL waiting';
LOCK TABLES t2 WRITE;
#
connect con2,localhost,root,,;
SET DEBUG_SYNC='now WAIT_FOR waiting';
SET DEBUG_SYNC='mdl_acquire_lock_wait SIGNAL waiting';
FLUSH TABLES WITH READ LOCK;
#
connection default;
SET DEBUG_SYNC='now WAIT_FOR waiting';
INSERT INTO t1 VALUES(1);
COMMIT;
connection con1;
UNLOCK TABLES;
connection con2;
UNLOCK TABLES;
connection default;
DROP TABLE t1, t2;
SET DEBUG_SYNC='RESET';
disconnect con1;
disconnect con2;
# Make sure deadlock detector prefers FTWRL connection as a victim
# and FTWRL retries lock attempt. This deadlock was found during
# MDEV-5336 review.
CREATE TABLE t1(a INT) ENGINE=InnoDB;
CREATE TABLE t2(a INT) ENGINE=InnoDB;
BEGIN;
INSERT INTO t2 VALUES(1);
SET DEBUG_SYNC='after_open_table_mdl_shared SIGNAL table_opened WAIT_FOR go';
INSERT INTO t1 VALUES(1);
#
connect con1,localhost,root,,;
SET DEBUG_SYNC='now WAIT_FOR table_opened';
SET DEBUG_SYNC='mdl_acquire_lock_wait SIGNAL waiting';
LOCK TABLES t1 WRITE;
#
connect con2,localhost,root,,;
SET DEBUG_SYNC='now WAIT_FOR waiting';
SET DEBUG_SYNC='mdl_acquire_lock_wait SIGNAL waiting';
FLUSH TABLES WITH READ LOCK;
#
connect con3,localhost,root,,;
SET DEBUG_SYNC='now WAIT_FOR waiting';
SET DEBUG_SYNC='now SIGNAL go';
connection default;
COMMIT;
connection con1;
UNLOCK TABLES;
connection con2;
UNLOCK TABLES;
connection default;
DROP TABLE t1, t2;
SET DEBUG_SYNC='RESET';
disconnect con1;
disconnect con2;
disconnect con3;
......@@ -4168,91 +4168,6 @@ DROP TABLE t1;
disconnect con1;
--echo #
--echo # MDEV-5336 - Implement LOCK FOR BACKUP
--echo #
--echo # Make sure deadlock detector prefers FTWRL connection as a victim
--echo # and FTWRL retries lock attempt. This deadlock was present before
--echo # MDEV-5336.
CREATE TABLE t1(a INT) ENGINE=InnoDB;
CREATE TABLE t2(a INT) ENGINE=InnoDB;
BEGIN;
SELECT * FROM t2;
--echo #
connect(con1,localhost,root,,);
SET DEBUG_SYNC='mdl_acquire_lock_wait SIGNAL waiting';
send LOCK TABLES t2 WRITE;
--echo #
connect(con2,localhost,root,,);
SET DEBUG_SYNC='now WAIT_FOR waiting';
SET DEBUG_SYNC='mdl_acquire_lock_wait SIGNAL waiting';
send FLUSH TABLES WITH READ LOCK;
--echo #
connection default;
SET DEBUG_SYNC='now WAIT_FOR waiting';
INSERT INTO t1 VALUES(1);
COMMIT;
connection con1;
reap;
UNLOCK TABLES;
connection con2;
reap;
UNLOCK TABLES;
connection default;
DROP TABLE t1, t2;
SET DEBUG_SYNC='RESET';
disconnect con1;
disconnect con2;
--echo # Make sure deadlock detector prefers FTWRL connection as a victim
--echo # and FTWRL retries lock attempt. This deadlock was found during
--echo # MDEV-5336 review.
CREATE TABLE t1(a INT) ENGINE=InnoDB;
CREATE TABLE t2(a INT) ENGINE=InnoDB;
BEGIN;
INSERT INTO t2 VALUES(1);
SET DEBUG_SYNC='after_open_table_mdl_shared SIGNAL table_opened WAIT_FOR go';
send INSERT INTO t1 VALUES(1);
--echo #
connect(con1,localhost,root,,);
SET DEBUG_SYNC='now WAIT_FOR table_opened';
SET DEBUG_SYNC='mdl_acquire_lock_wait SIGNAL waiting';
send LOCK TABLES t1 WRITE;
--echo #
connect(con2,localhost,root,,);
SET DEBUG_SYNC='now WAIT_FOR waiting';
SET DEBUG_SYNC='mdl_acquire_lock_wait SIGNAL waiting';
send FLUSH TABLES WITH READ LOCK;
--echo #
connect(con3,localhost,root,,);
SET DEBUG_SYNC='now WAIT_FOR waiting';
SET DEBUG_SYNC='now SIGNAL go';
connection default;
reap;
COMMIT;
connection con1;
reap;
UNLOCK TABLES;
connection con2;
reap;
UNLOCK TABLES;
connection default;
DROP TABLE t1, t2;
SET DEBUG_SYNC='RESET';
disconnect con1;
disconnect con2;
disconnect con3;
# 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.
--source include/wait_until_count_sessions.inc
......@@ -25,8 +25,6 @@ connection slave;
connection slave;
SHOW TABLES in mysqltest;
Tables_in_mysqltest
t
t1
SHOW EVENTS in mysqltest;
Db Name Definer Time zone Type Execute at Interval value Interval field Starts Ends Status Originator character_set_client collation_connection Database Collation
mysqltest e root@localhost SYSTEM ONE TIME # NULL NULL NULL NULL SLAVESIDE_DISABLED 1 latin1 latin1_swedish_ci latin1_swedish_ci
......
......@@ -3883,6 +3883,39 @@ open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables,
}
static bool upgrade_lock_if_not_exists(THD *thd,
const DDL_options_st &create_info,
TABLE_LIST *create_table,
ulong lock_wait_timeout)
{
DBUG_ENTER("upgrade_lock_if_not_exists");
if (thd->lex->sql_command == SQLCOM_CREATE_TABLE ||
thd->lex->sql_command == SQLCOM_CREATE_SEQUENCE)
{
if (!create_info.or_replace() &&
ha_table_exists(thd, &create_table->db, &create_table->table_name))
{
if (create_info.if_not_exists())
{
push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
ER_TABLE_EXISTS_ERROR,
ER_THD(thd, ER_TABLE_EXISTS_ERROR),
create_table->table_name.str);
}
else
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), create_table->table_name.str);
DBUG_RETURN(true);
}
DBUG_RETURN(thd->mdl_context.upgrade_shared_lock(
create_table->mdl_request.ticket,
MDL_EXCLUSIVE,
lock_wait_timeout));
}
DBUG_RETURN(false);
}
/**
Acquire upgradable (SNW, SNRW) metadata locks on tables used by
LOCK TABLES or by a DDL statement. Under LOCK TABLES, we can't take
......@@ -3920,10 +3953,7 @@ lock_table_names(THD *thd, const DDL_options_st &options,
MDL_request_list mdl_requests;
TABLE_LIST *table;
MDL_request global_request;
ulong org_lock_wait_timeout= lock_wait_timeout;
/* Check if we are using CREATE TABLE ... IF NOT EXISTS */
bool create_table;
Dummy_error_handler error_handler;
MDL_savepoint mdl_savepoint;
DBUG_ENTER("lock_table_names");
DBUG_ASSERT(!thd->locked_tables_mode);
......@@ -3966,75 +3996,48 @@ lock_table_names(THD *thd, const DDL_options_st &options,
if (mdl_requests.is_empty())
DBUG_RETURN(FALSE);
/* Check if CREATE TABLE without REPLACE was used */
create_table= ((thd->lex->sql_command == SQLCOM_CREATE_TABLE ||
thd->lex->sql_command == SQLCOM_CREATE_SEQUENCE) &&
!options.or_replace());
if (!(flags & MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK))
if (flags & MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK)
{
/*
Protect this statement against concurrent global read lock
by acquiring global intention exclusive lock with statement
duration.
*/
if (thd->has_read_only_protection())
DBUG_RETURN(TRUE);
global_request.init(MDL_key::BACKUP, "", "", MDL_BACKUP_DDL,
MDL_STATEMENT);
mdl_requests.push_front(&global_request);
if (create_table)
#ifdef WITH_WSREP
if (thd->lex->sql_command != SQLCOM_CREATE_TABLE &&
thd->wsrep_exec_mode != REPL_RECV)
#endif
lock_wait_timeout= 0; // Don't wait for timeout
DBUG_RETURN(thd->mdl_context.acquire_locks(&mdl_requests,
lock_wait_timeout) ||
upgrade_lock_if_not_exists(thd, options, tables_start,
lock_wait_timeout));
}
for (;;)
{
if (create_table)
thd->push_internal_handler(&error_handler); // Avoid warnings & errors
bool res= thd->mdl_context.acquire_locks(&mdl_requests, lock_wait_timeout);
if (!(flags & MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK))
thd->mdl_backup_ticket= global_request.ticket;
if (create_table)
thd->pop_internal_handler();
if (!res)
DBUG_RETURN(FALSE); // Got locks
/* Protect this statement against concurrent BACKUP STAGE or FTWRL. */
if (thd->has_read_only_protection())
DBUG_RETURN(true);
if (!create_table)
DBUG_RETURN(TRUE); // Return original error
global_request.init(MDL_key::BACKUP, "", "", MDL_BACKUP_DDL, MDL_STATEMENT);
mdl_savepoint= thd->mdl_context.mdl_savepoint();
/*
We come here in the case of lock timeout when executing CREATE TABLE.
Verify that table does exist (it usually does, as we got a lock conflict)
*/
if (ha_table_exists(thd, &tables_start->db, &tables_start->table_name))
while (!thd->mdl_context.acquire_locks(&mdl_requests, lock_wait_timeout) &&
!upgrade_lock_if_not_exists(thd, options, tables_start,
lock_wait_timeout) &&
!thd->mdl_context.try_acquire_lock(&global_request))
{
if (global_request.ticket)
{
if (options.if_not_exists())
{
push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
ER_TABLE_EXISTS_ERROR,
ER_THD(thd, ER_TABLE_EXISTS_ERROR),
tables_start->table_name.str);
}
else
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), tables_start->table_name.str);
DBUG_RETURN(TRUE);
thd->mdl_backup_ticket= global_request.ticket;
DBUG_RETURN(false);
}
/*
We got error from acquire_locks, but the table didn't exists.
This could happen if another connection runs a statement
involving this non-existent table, and this statement took the mdl,
but didn't error out with ER_NO_SUCH_TABLE yet (yes, a race condition).
We play safe and restart the original acquire_locks with the
original timeout.
There is ongoing or pending BACKUP STAGE or FTWRL.
Wait until it finishes and re-try.
*/
create_table= 0;
lock_wait_timeout= org_lock_wait_timeout;
thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
if (thd->mdl_context.acquire_lock(&global_request, lock_wait_timeout))
break;
thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
/* Reset tickets for all acquired locks */
global_request.ticket= 0;
MDL_request_list::Iterator it(mdl_requests);
while (auto mdl_request= it++)
mdl_request->ticket= 0;
}
DBUG_RETURN(true);
}
......
......@@ -2648,7 +2648,7 @@ create:
LEX *lex= thd->lex;
if (!lex->first_select_lex()->
add_table_to_list(thd, $6, NULL, TL_OPTION_UPDATING,
TL_WRITE, MDL_EXCLUSIVE))
TL_WRITE, MDL_SHARED_UPGRADABLE))
MYSQL_YYABORT;
lex->alter_info.reset();
/*
......
......@@ -2165,7 +2165,7 @@ create:
LEX *lex= thd->lex;
if (!lex->first_select_lex()->
add_table_to_list(thd, $6, NULL, TL_OPTION_UPDATING,
TL_WRITE, MDL_EXCLUSIVE))
TL_WRITE, MDL_SHARED_UPGRADABLE))
MYSQL_YYABORT;
lex->alter_info.reset();
/*
......
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