diff --git a/mysql-test/main/backup_lock.result b/mysql-test/main/backup_lock.result index 40072aa0684..95b2f520d90 100644 --- a/mysql-test/main/backup_lock.result +++ b/mysql-test/main/backup_lock.result @@ -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; diff --git a/mysql-test/main/backup_lock.test b/mysql-test/main/backup_lock.test index aafeb3a2d4b..d6db7a6364e 100644 --- a/mysql-test/main/backup_lock.test +++ b/mysql-test/main/backup_lock.test @@ -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 diff --git a/mysql-test/main/backup_lock_debug.result b/mysql-test/main/backup_lock_debug.result new file mode 100644 index 00000000000..8832d9cd3e7 --- /dev/null +++ b/mysql-test/main/backup_lock_debug.result @@ -0,0 +1,28 @@ +# +# 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'; diff --git a/mysql-test/main/backup_lock_debug.test b/mysql-test/main/backup_lock_debug.test new file mode 100644 index 00000000000..8cf492b3404 --- /dev/null +++ b/mysql-test/main/backup_lock_debug.test @@ -0,0 +1,40 @@ +######################################################################## +# 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'; diff --git a/mysql-test/main/create_drop_binlog.result b/mysql-test/main/create_drop_binlog.result index be40fcc140a..b8f8b61c359 100644 --- a/mysql-test/main/create_drop_binlog.result +++ b/mysql-test/main/create_drop_binlog.result @@ -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) diff --git a/mysql-test/main/flush_read_lock.result b/mysql-test/main/flush_read_lock.result index aa67ccdce55..33dc1092190 100644 --- a/mysql-test/main/flush_read_lock.result +++ b/mysql-test/main/flush_read_lock.result @@ -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; # connection con1; UNLOCK TABLES; disconnect con1; # connection default; -UNLOCK TABLES; -HANDLER t1 CLOSE; DROP TABLE t1; SET DEBUG_SYNC= 'RESET'; diff --git a/mysql-test/main/flush_read_lock.test b/mysql-test/main/flush_read_lock.test index 4fd79f42990..f39dbecf4a9 100644 --- a/mysql-test/main/flush_read_lock.test +++ b/mysql-test/main/flush_read_lock.test @@ -2037,7 +2037,43 @@ disconnect con2; --echo # connection default; ---send FLUSH TABLES WITH READ LOCK +FLUSH TABLES WITH READ LOCK; +UNLOCK TABLES; +HANDLER t1 CLOSE; + +--echo # +connection con1; +reap; +UNLOCK TABLES; +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; @@ -2047,8 +2083,5 @@ disconnect con1; --echo # connection default; -reap; -UNLOCK TABLES; -HANDLER t1 CLOSE; DROP TABLE t1; SET DEBUG_SYNC= 'RESET'; diff --git a/mysql-test/main/mdl_sync.result b/mysql-test/main/mdl_sync.result index 917b97d0295..5203fdddb2d 100644 --- a/mysql-test/main/mdl_sync.result +++ b/mysql-test/main/mdl_sync.result @@ -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; diff --git a/mysql-test/main/mdl_sync.test b/mysql-test/main/mdl_sync.test index a794dbf4a7a..185aedc0e9c 100644 --- a/mysql-test/main/mdl_sync.test +++ b/mysql-test/main/mdl_sync.test @@ -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 diff --git a/mysql-test/suite/rpl/r/rpl_create_if_not_exists.result b/mysql-test/suite/rpl/r/rpl_create_if_not_exists.result index d74fd07189c..b31eacfc236 100644 --- a/mysql-test/suite/rpl/r/rpl_create_if_not_exists.result +++ b/mysql-test/suite/rpl/r/rpl_create_if_not_exists.result @@ -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 diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 45dea349af4..ccff8568973 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -3883,6 +3883,39 @@ end: } +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 (;;) + /* Protect this statement against concurrent BACKUP STAGE or FTWRL. */ + if (thd->has_read_only_protection()) + DBUG_RETURN(true); + + global_request.init(MDL_key::BACKUP, "", "", MDL_BACKUP_DDL, MDL_STATEMENT); + mdl_savepoint= thd->mdl_context.mdl_savepoint(); + + 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 (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 - - if (!create_table) - DBUG_RETURN(TRUE); // Return original error - - /* - 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)) + 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); } diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 3f416bc3445..fa8c15ac7e1 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -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(); /* diff --git a/sql/sql_yacc_ora.yy b/sql/sql_yacc_ora.yy index f3401f7e42d..27d3132be0d 100644 --- a/sql/sql_yacc_ora.yy +++ b/sql/sql_yacc_ora.yy @@ -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(); /*