diff --git a/mysql-test/main/backup_lock.result b/mysql-test/main/backup_lock.result index 6e2ccad5091ea..fbd04325ede46 100644 --- a/mysql-test/main/backup_lock.result +++ b/mysql-test/main/backup_lock.result @@ -4,27 +4,27 @@ InnoDB 0 transactions not purged BACKUP STAGE START; SELECT LOCK_MODE, LOCK_TYPE, TABLE_SCHEMA, TABLE_NAME FROM information_schema.metadata_lock_info -WHERE TABLE_NAME NOT LIKE 'innodb_%_stats'; +WHERE TABLE_NAME NOT LIKE 'innodb_%_stats' ORDER BY LOCK_MODE; LOCK_MODE LOCK_TYPE TABLE_SCHEMA TABLE_NAME MDL_BACKUP_START Backup lock BACKUP STAGE FLUSH; SELECT LOCK_MODE, LOCK_TYPE, TABLE_SCHEMA, TABLE_NAME FROM information_schema.metadata_lock_info -WHERE TABLE_NAME NOT LIKE 'innodb_%_stats'; +WHERE TABLE_NAME NOT LIKE 'innodb_%_stats' ORDER BY LOCK_MODE; LOCK_MODE LOCK_TYPE TABLE_SCHEMA TABLE_NAME MDL_BACKUP_FLUSH Backup lock BACKUP STAGE BLOCK_DDL; SELECT LOCK_MODE, LOCK_TYPE, TABLE_SCHEMA, TABLE_NAME FROM information_schema.metadata_lock_info -WHERE TABLE_NAME NOT LIKE 'innodb_%_stats'; +WHERE TABLE_NAME NOT LIKE 'innodb_%_stats' ORDER BY LOCK_MODE; LOCK_MODE LOCK_TYPE TABLE_SCHEMA TABLE_NAME MDL_BACKUP_WAIT_DDL Backup lock BACKUP STAGE BLOCK_COMMIT; SELECT LOCK_MODE, LOCK_TYPE, TABLE_SCHEMA, TABLE_NAME FROM information_schema.metadata_lock_info -WHERE TABLE_NAME NOT LIKE 'innodb_%_stats'; +WHERE TABLE_NAME NOT LIKE 'innodb_%_stats' ORDER BY LOCK_MODE; LOCK_MODE LOCK_TYPE TABLE_SCHEMA TABLE_NAME MDL_BACKUP_WAIT_COMMIT Backup lock BACKUP STAGE END; SELECT LOCK_MODE, LOCK_TYPE, TABLE_SCHEMA, TABLE_NAME FROM information_schema.metadata_lock_info -WHERE TABLE_NAME NOT LIKE 'innodb_%_stats'; +WHERE TABLE_NAME NOT LIKE 'innodb_%_stats' ORDER BY LOCK_MODE; LOCK_MODE LOCK_TYPE TABLE_SCHEMA TABLE_NAME # # testing BACKUP STAGE LOCK's @@ -47,13 +47,14 @@ alter table t1 add column (j int), algorithm copy, lock shared; connection con2; backup stage flush; SELECT LOCK_MODE, LOCK_TYPE, TABLE_SCHEMA, TABLE_NAME FROM information_schema.metadata_lock_info -WHERE TABLE_NAME NOT LIKE 'innodb_%_stats'; +WHERE TABLE_NAME NOT LIKE 'innodb_%_stats' ORDER BY LOCK_MODE; LOCK_MODE LOCK_TYPE TABLE_SCHEMA TABLE_NAME MDL_BACKUP_DDL Backup lock MDL_BACKUP_FLUSH Backup lock -MDL_SHARED_WRITE Table metadata lock test t1 -MDL_SHARED_UPGRADABLE Table metadata lock test t1 +MDL_EXCLUSIVE Table metadata lock test #sql-alter MDL_INTENTION_EXCLUSIVE Schema metadata lock test +MDL_SHARED_UPGRADABLE Table metadata lock test t1 +MDL_SHARED_WRITE Table metadata lock test t1 SET STATEMENT max_statement_time=1 FOR backup stage block_ddl; ERROR 70100: Query execution was interrupted (max_statement_time exceeded) backup stage block_ddl; @@ -84,13 +85,14 @@ connection con2; backup stage start; backup stage flush; SELECT LOCK_MODE, LOCK_TYPE, TABLE_SCHEMA, TABLE_NAME FROM information_schema.metadata_lock_info -WHERE TABLE_NAME NOT LIKE 'innodb_%_stats'; +WHERE TABLE_NAME NOT LIKE 'innodb_%_stats' ORDER BY LOCK_MODE; LOCK_MODE LOCK_TYPE TABLE_SCHEMA TABLE_NAME MDL_BACKUP_ALTER_COPY Backup lock MDL_BACKUP_FLUSH Backup lock -MDL_SHARED_WRITE Table metadata lock test t1 -MDL_SHARED_UPGRADABLE Table metadata lock test t1 +MDL_EXCLUSIVE Table metadata lock test #sql-alter MDL_INTENTION_EXCLUSIVE Schema metadata lock test +MDL_SHARED_UPGRADABLE Table metadata lock test t1 +MDL_SHARED_WRITE Table metadata lock test t1 backup stage block_ddl; backup stage block_commit; connection default; @@ -121,11 +123,11 @@ SET STATEMENT lock_wait_timeout=0 FOR SELECT * FROM t1; 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 -WHERE TABLE_NAME NOT LIKE 'innodb_%_stats'; +WHERE TABLE_NAME NOT LIKE 'innodb_%_stats' ORDER BY LOCK_MODE; LOCK_MODE LOCK_TYPE TABLE_SCHEMA TABLE_NAME MDL_BACKUP_WAIT_DDL Backup lock -MDL_SHARED_WRITE Table metadata lock test t1 MDL_INTENTION_EXCLUSIVE Schema metadata lock test +MDL_SHARED_WRITE Table metadata lock test t1 backup stage end; connection default; commit; @@ -143,7 +145,7 @@ DROP TABLE t1; connection con2; connection con2; SELECT LOCK_MODE, LOCK_TYPE, TABLE_SCHEMA, TABLE_NAME FROM information_schema.metadata_lock_info -WHERE TABLE_NAME NOT LIKE 'innodb_%_stats'; +WHERE TABLE_NAME NOT LIKE 'innodb_%_stats' ORDER BY LOCK_MODE; LOCK_MODE LOCK_TYPE TABLE_SCHEMA TABLE_NAME MDL_BACKUP_WAIT_DDL Backup lock SELECT * FROM t1; diff --git a/mysql-test/main/backup_lock.test b/mysql-test/main/backup_lock.test index eec6c2f01ce6b..1eba9881ff1e7 100644 --- a/mysql-test/main/backup_lock.test +++ b/mysql-test/main/backup_lock.test @@ -13,7 +13,7 @@ --echo # let $mdl= LOCK_MODE, LOCK_TYPE, TABLE_SCHEMA, TABLE_NAME FROM information_schema.metadata_lock_info -WHERE TABLE_NAME NOT LIKE 'innodb_%_stats'; +WHERE TABLE_NAME NOT LIKE 'innodb_%_stats' ORDER BY LOCK_MODE; --source ../suite/innodb/include/wait_all_purged.inc @@ -62,6 +62,7 @@ let $wait_condition= where state = "Waiting for table metadata lock"; --source include/wait_condition.inc backup stage flush; +--replace_regex /#sql-alter-[0-9a-f_\-]*/#sql-alter/ eval SELECT $mdl; # # Do first test with max_statement_time, other tests later are done with @@ -111,6 +112,7 @@ let $wait_condition= --source include/wait_condition.inc backup stage start; backup stage flush; +--replace_regex /#sql-alter-[0-9a-f_\-]*/#sql-alter/ eval SELECT $mdl; backup stage block_ddl; backup stage block_commit; diff --git a/sql/sql_partition_admin.cc b/sql/sql_partition_admin.cc index 6697b4f580e78..8679e1ff042c3 100644 --- a/sql/sql_partition_admin.cc +++ b/sql/sql_partition_admin.cc @@ -716,6 +716,14 @@ bool Sql_cmd_alter_table_exchange_partition:: DEBUG_SYNC(thd, "swap_partition_before_rename"); + { + MDL_request mdl_request; + MDL_REQUEST_INIT(&mdl_request, MDL_key::TABLE, + table_list->next_local->db.str, + temp_name, MDL_EXCLUSIVE, MDL_TRANSACTION); + thd->mdl_context.acquire_lock(&mdl_request, 0); + } + if (unlikely(exchange_name_with_ddl_log(thd, swap_file_name, part_file_name, temp_file_name, table_hton))) goto err; diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 13ed810cac2dd..f060360c673e5 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -11588,6 +11588,19 @@ do_continue:; */ alter_info->original_table= table; + { + /* + Create an exclusive lock on the temporary table name. + Needed by InnoDB storage engine to avoid MDL on source + table during purge. + */ + MDL_request mdl_request; + MDL_REQUEST_INIT(&mdl_request, MDL_key::TABLE, + alter_ctx.new_db.str, alter_ctx.tmp_name.str, + MDL_EXCLUSIVE, MDL_TRANSACTION); + thd->mdl_context.acquire_lock(&mdl_request, 0); + } + /* Create the .frm file for the new table. Storage engine table will not be created at this stage. @@ -12160,6 +12173,16 @@ do_continue:; If we are changing to use another table handler, we don't have to do the rename as the table names will not interfer. */ + /* + Create an exclusive lock on the backup table name. + Needed by InnoDB storage engine to avoid MDL conflicts + during backup table operations. + */ + MDL_request mdl_request; + MDL_REQUEST_INIT(&mdl_request, MDL_key::TABLE, + alter_ctx.db.str, backup_name.str, + MDL_EXCLUSIVE, MDL_TRANSACTION); + thd->mdl_context.acquire_lock(&mdl_request, 0); if (mysql_rename_table(old_db_type, &alter_ctx.db, &alter_ctx.table_name, &alter_ctx.db, &backup_name, &alter_ctx.id, FN_TO_IS_TMP | diff --git a/storage/innobase/dict/dict0dict.cc b/storage/innobase/dict/dict0dict.cc index 2edc7068f17e3..6b253793bdc5e 100644 --- a/storage/innobase/dict/dict0dict.cc +++ b/storage/innobase/dict/dict0dict.cc @@ -519,59 +519,109 @@ void mdl_release(THD *thd, MDL_ticket *mdl) noexcept thd->mdl_context.release_lock(mdl); } -/** Parse the table file name into table name and database name. -@tparam dict_frozen whether the caller holds dict_sys.latch -@param[in,out] db_name database name buffer -@param[in,out] tbl_name table name buffer -@param[out] db_name_len database name length -@param[out] tbl_name_len table name length -@return whether the table name is visible to SQL */ -template -bool dict_table_t::parse_name(char (&db_name)[NAME_LEN + 1], - char (&tbl_name)[NAME_LEN + 1], - size_t *db_name_len, size_t *tbl_name_len) const +void dict_table_t::parse_tbl_name( + const table_name_t &table_name, + char (&db_name)[NAME_LEN + 1], + char (&tbl_name)[NAME_LEN + 1], + size_t *db_name_len, size_t *tbl_name_len) noexcept { char db_buf[MAX_DATABASE_NAME_LEN + 1]; char tbl_buf[MAX_TABLE_NAME_LEN + 1]; - if (!dict_frozen) - dict_sys.freeze(SRW_LOCK_CALL); /* protect against renaming */ - ut_ad(dict_sys.frozen()); - const size_t db_len= name.dblen(); - ut_ad(db_len <= MAX_DATABASE_NAME_LEN); + const size_t db_len= table_name.dblen(); + const char* tbl_start= table_name.basename(); - memcpy(db_buf, mdl_name.m_name, db_len); - db_buf[db_len]= 0; + if (db_len == 0 || !tbl_start) + return; - size_t tbl_len= strlen(mdl_name.m_name + db_len + 1); - const bool is_temp= mdl_name.is_temporary(); + memcpy(db_buf, table_name.m_name, db_len); + db_buf[db_len]= '\0'; - if (is_temp); - else if (const char *is_part= static_cast - (memchr(mdl_name.m_name + db_len + 1, '#', tbl_len))) - tbl_len= static_cast(is_part - &mdl_name.m_name[db_len + 1]); + size_t tbl_len= strlen(tbl_start); + const bool is_temp= table_name.is_temporary(); - memcpy(tbl_buf, mdl_name.m_name + db_len + 1, tbl_len); - tbl_buf[tbl_len]= 0; + /* For partition tables, find the base table name by removing partition suffix */ + if (const char *is_part = static_cast + (memchr(tbl_start, '#', tbl_len))) + { + /* For temporary tables, we need to find the partition marker after the temp name */ + if (is_temp) + { + /* Look for partition markers like #P# after the temporary table name */ + const char *part_marker= strstr(tbl_start, "#P#"); + if (part_marker) + tbl_len= static_cast(part_marker - tbl_start); + else if (const char *vec_marker= strstr(tbl_start, "#i#")) + tbl_len= static_cast(vec_marker - tbl_start); + } + else + { + /* For regular tables, use first # as partition boundary */ + tbl_len= static_cast(is_part - tbl_start); + } + } - if (!dict_frozen) - dict_sys.unfreeze(); + memcpy(tbl_buf, tbl_start, tbl_len); + tbl_buf[tbl_len]= '\0'; + /* Convert internal names to SQL names */ *db_name_len= filename_to_tablename(db_buf, db_name, MAX_DATABASE_NAME_LEN + 1, true); - if (is_temp) - return false; - + { + memcpy(tbl_name, tbl_buf, tbl_len); + tbl_name[tbl_len]= '\0'; + *tbl_name_len= tbl_len; + return; + } *tbl_name_len= filename_to_tablename(tbl_buf, tbl_name, MAX_TABLE_NAME_LEN + 1, true); - return true; + return; } -template bool +/** Parse the table file name into table name and database name. +@tparam dict_frozen whether the caller holds dict_sys.latch +@param[in,out] db_name database name buffer +@param[in,out] tbl_name table name buffer +@param[out] db_name_len database name length +@param[out] tbl_name_len table name length +@return DB_CORRUPTION if table name contains #sql-ib, +@return DB_SUCCESS_LOCKED_REC if db_len == 0, DB_SUCCESS otherwise */ +template +dberr_t dict_table_t::parse_name(char (&db_name)[NAME_LEN + 1], + char (&tbl_name)[NAME_LEN + 1], + size_t *db_name_len, size_t *tbl_name_len) const +{ + if (!dict_frozen) + dict_sys.freeze(SRW_LOCK_CALL); /* protect against renaming */ + ut_ad(dict_sys.frozen()); + parse_tbl_name(name, db_name, tbl_name, db_name_len, tbl_name_len); + if (!dict_frozen) + dict_sys.unfreeze(); + + /* Check for specific error conditions */ + if (*db_name_len == 0) + return DB_SUCCESS_LOCKED_REC; + + /* Inplace intermediate table should not generate any + undo logs. In that case, throw error */ + if (strstr(tbl_name, "#sql-ib")) + { + sql_print_error("InnoDB: Trying to acquire MDL for inplace " + "intermediate table #sql-ib"); + return DB_CORRUPTION; + } + return DB_SUCCESS; +} + +template dberr_t dict_table_t::parse_name<>(char(&)[NAME_LEN + 1], char(&)[NAME_LEN + 1], size_t*, size_t*) const; +template dberr_t +dict_table_t::parse_name(char(&)[NAME_LEN + 1], char(&)[NAME_LEN + 1], + size_t*, size_t*) const; + dict_table_t *dict_sys_t::acquire_temporary_table(table_id_t id) const noexcept { ut_ad(frozen()); @@ -631,10 +681,13 @@ dict_acquire_mdl_shared(dict_table_t *table, char tbl_buf[NAME_LEN + 1], tbl_buf1[NAME_LEN + 1]; size_t db_len, tbl_len; - if (!table->parse_name(db_buf, tbl_buf, &db_len, &tbl_len)) - /* The name of an intermediate table starts with #sql */ + dberr_t err= table->parse_name( + db_buf, tbl_buf, &db_len, &tbl_len); + if (err == DB_SUCCESS_LOCKED_REC) return table; + if (err == DB_CORRUPTION) + return nullptr; retry: ut_ad(!trylock == dict_sys.frozen()); @@ -701,10 +754,11 @@ dict_acquire_mdl_shared(dict_table_t *table, if (trylock) table->acquire(); - if (!table->parse_name(db_buf1, tbl_buf1, &db1_len, &tbl1_len)) + dberr_t err= + table->parse_name(db_buf1, tbl_buf1, &db1_len, &tbl1_len); + if (err == DB_SUCCESS_LOCKED_REC || err == DB_CORRUPTION) { - /* The table was renamed to #sql prefix. - Release MDL (if any) for the old name and return. */ + /* Release MDL (if any) for the old name and return. */ goto unlock_and_return_without_mdl; } } @@ -1513,22 +1567,6 @@ dict_table_rename_in_cache( old_name_len)) ->remove(*table, &dict_table_t::name_hash); - bool keep_mdl_name = !table->name.is_temporary(); - - if (!keep_mdl_name) { - } else if (const char* s = static_cast - (memchr(new_name.data(), '/', new_name.size()))) { - keep_mdl_name = new_name.end() - s >= 5 - && !memcmp(s, "/#sql", 5); - } - - if (keep_mdl_name) { - /* Preserve the original table name for - dict_table_t::parse_name() and dict_acquire_mdl_shared(). */ - table->mdl_name.m_name = mem_heap_strdup(table->heap, - table->name.m_name); - } - if (new_name.size() > strlen(table->name.m_name)) { /* We allocate MAX_FULL_NAME_LEN + 1 bytes here to avoid memory fragmentation, we assume a repeated calls of @@ -1541,10 +1579,6 @@ dict_table_rename_in_cache( memcpy(table->name.m_name, new_name.data(), new_name.size()); table->name.m_name[new_name.size()] = '\0'; - if (!keep_mdl_name) { - table->mdl_name.m_name = table->name.m_name; - } - /* Add table to hash table of tables */ ut_ad(!table->name_hash); dict_table_t** after = reinterpret_cast( diff --git a/storage/innobase/dict/dict0mem.cc b/storage/innobase/dict/dict0mem.cc index abd51dea27006..2ea5a1260efca 100644 --- a/storage/innobase/dict/dict0mem.cc +++ b/storage/innobase/dict/dict0mem.cc @@ -148,7 +148,6 @@ dict_table_t *dict_table_t::create(const span &name, table->flags= static_cast(flags) & ((1U << DICT_TF_BITS) - 1); table->flags2= static_cast(flags2) & ((1U << DICT_TF2_BITS) - 1); table->name.m_name= mem_strdupl(name.data(), name.size()); - table->mdl_name.m_name= table->name.m_name; table->is_system_db= dict_mem_table_is_system(table->name.m_name); table->space= space; table->space_id= space ? space->id : UINT32_MAX; diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 858e2f3ce8683..bff47b63b2a0e 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -119,6 +119,7 @@ this program; if not, write to the Free Software Foundation, Inc., #include #include // TT_FOR_UPGRADE #include "sql_type_vector.h" +#include #define thd_get_query_id(thd) uint64_t((thd)->query_id) #define thd_in_lock_tables(thd) (thd)->in_lock_tables @@ -13468,6 +13469,55 @@ static bool delete_table_check_foreigns(const dict_table_t &table, return false; } +/** Check if the current thread has appropriate MDL for a table. +This function is used to verify MDL ownership before performing +table operations like DROP TABLE or RENAME TABLE to ensure +proper locking protocol. +@param[in] thd MySQL thread handle +@param[in] table_name Full table name to check MDL ownership for +@return true if MDL check should pass, false otherwise + +The function returns true for: + Table name contains "sql-ib" (internal temporary tables) + Table is an FTS auxiliary table (FTS__ format) + - Purge system is being stopped when we're making any DDL changes + to fulltext table. + Thread holds MDL_EXCLUSIVE lock on the base table name + Table name represents a partition + - InnoDB takes MDL on base table name when it deals + with one of the partitions of the table. But adding + new partition doesn't take MDL on base table. That's + why we're making exception for partition table +@return FALSE when: + Thread does not hold appropriate MDL lock on the table */ +bool innodb_has_mdl(THD *thd, const char *table_name) +{ + char db_buf[NAME_LEN + 1]; + char tbl_buf[NAME_LEN + 1]; + size_t tbl_len, db_len; + + dict_table_t::parse_tbl_name( + table_name_t(const_cast(table_name)), + db_buf, tbl_buf, &db_len, &tbl_len); + + if (strstr(tbl_buf, "sql-ib")) + return true; + + /* Check if this is an FTS auxiliary table */ + table_id_t table_id; + index_id_t index_id; + if (fts_check_aux_table(table_name, &table_id, &index_id)) + return true; + + MDL_key key; + key.mdl_key_init(MDL_key::TABLE, db_buf, tbl_buf); + if (thd->mdl_context.is_lock_owner(MDL_key::TABLE, db_buf, tbl_buf, + MDL_EXCLUSIVE)) + return true; + return dict_is_partition(table_name); +} + + /** DROP TABLE (possibly as part of DROP DATABASE, CREATE/ALTER TABLE) @param name table name @return error number */ @@ -13716,6 +13766,8 @@ int ha_innobase::delete_table(const char *name) if (err != DB_SUCCESS) goto err_exit; + ut_ad(!purge_sys.enabled() || + innodb_has_mdl(trx->mysql_thd, table->name.m_name)); err= trx->drop_table(*table); if (err != DB_SUCCESS) goto err_exit; @@ -20356,10 +20408,16 @@ static TABLE* innodb_find_table_for_vc(THD* thd, dict_table_t* table) char tbl_buf[NAME_LEN + 1]; ulint db_buf_len, tbl_buf_len; - if (!table->parse_name(db_buf, tbl_buf, &db_buf_len, &tbl_buf_len)) { + dberr_t err= table->parse_name( + db_buf, tbl_buf, &db_buf_len, &tbl_buf_len); + if (err == DB_SUCCESS_LOCKED_REC) { return NULL; } + if (err == DB_CORRUPTION) { + return nullptr; + } + if (bg_thread) { return open_purge_table(thd, db_buf, db_buf_len, tbl_buf, tbl_buf_len); diff --git a/storage/innobase/include/dict0mem.h b/storage/innobase/include/dict0mem.h index 0fa14f747c9f7..478b5332db013 100644 --- a/storage/innobase/include/dict0mem.h +++ b/storage/innobase/include/dict0mem.h @@ -2105,17 +2105,30 @@ struct dict_table_t { /** For overflow fields returns potential max length stored inline */ inline size_t get_overflow_field_local_len() const; + /** Parse the table file name into database namd and table name + @param table_name table file name + @param db_name database name buffer + @param tbl_name table name buffer + @param db_name_len database name length + @param tbl_name_len table name length */ + static void parse_tbl_name( + const table_name_t &table_name, + char (&db_name)[NAME_LEN + 1], + char (&tbl_name)[NAME_LEN + 1], + size_t *db_name_len, size_t *tbl_name_len) noexcept; + /** Parse the table file name into table name and database name. @tparam dict_frozen whether the caller holds dict_sys.latch @param[in,out] db_name database name buffer @param[in,out] tbl_name table name buffer @param[out] db_name_len database name length @param[out] tbl_name_len table name length - @return whether the table name is visible to SQL */ + @return DB_SUCCESS_LOCKED_REC if table name contains #sql-ib, + DB_CORRUPTION if db_len == 0, DB_SUCCESS otherwise */ template - bool parse_name(char (&db_name)[NAME_LEN + 1], - char (&tbl_name)[NAME_LEN + 1], - size_t *db_name_len, size_t *tbl_name_len) const; + dberr_t parse_name(char (&db_name)[NAME_LEN + 1], + char (&tbl_name)[NAME_LEN + 1], + size_t *db_name_len, size_t *tbl_name_len) const; /** Clear the table when rolling back TRX_UNDO_EMPTY @return error code */ @@ -2354,11 +2367,6 @@ struct dict_table_t { an empty leaf page), and an ahi_latch (if btr_search_enabled). */ Atomic_relaxed bulk_trx_id; - /** Original table name, for MDL acquisition in purge. Normally, - this points to the same as name. When is_temporary_name(name.m_name) holds, - this should be a copy of the original table name, allocated from heap. */ - table_name_t mdl_name; - /*!< set of foreign key constraints in the table; these refer to columns in other tables */ dict_foreign_set foreign_set; diff --git a/storage/innobase/include/row0mysql.h b/storage/innobase/include/row0mysql.h index 2f50aa1560eb2..a50632fc16da1 100644 --- a/storage/innobase/include/row0mysql.h +++ b/storage/innobase/include/row0mysql.h @@ -830,6 +830,29 @@ void innobase_rename_vc_templ( dict_table_t* table); + /** Check if the current thread has appropriate MDL for a table. +This function is used to verify MDL ownership before performing +table operations like DROP TABLE or RENAME TABLE to ensure +proper locking protocol. +@param[in] thd MySQL thread handle +@param[in] table_name Full table name to check MDL ownership for +@return true if MDL check should pass, false otherwise + +Function returns true for: + Table name contains "sql-ib" (internal temporary tables) + Table is an FTS auxiliary table (FTS__ format) + - Purge system is being stopped when we're making any DDL changes + to fulltext table. + Thread holds MDL_EXCLUSIVE lock on the base table name + Table name represents a partition + - InnoDB takes MDL on base table name when it deals + with one of the partitions of the table. But adding + new partition doesn't take MDL on base table. That's + why we're making exception for partition table +Function returns FALSE when: + Thread does not hold appropriate MDL lock on the table */ +bool innodb_has_mdl(THD *thd, const char *table_name); + #define ROW_PREBUILT_FETCH_MAGIC_N 465765687 #define ROW_MYSQL_WHOLE_ROW 0 diff --git a/storage/innobase/row/row0mysql.cc b/storage/innobase/row/row0mysql.cc index 6098d098c6478..78dfe72e9fe20 100644 --- a/storage/innobase/row/row0mysql.cc +++ b/storage/innobase/row/row0mysql.cc @@ -2621,6 +2621,11 @@ row_rename_table_for_mysql( } } + ut_ad(!purge_sys.enabled() + || innodb_has_mdl(trx->mysql_thd, table->name.m_name)); + ut_ad(!purge_sys.enabled() + || innodb_has_mdl(trx->mysql_thd, new_name)); + err = trx_undo_report_rename(trx, table); if (err != DB_SUCCESS) { diff --git a/storage/innobase/trx/trx0purge.cc b/storage/innobase/trx/trx0purge.cc index 9ba4a8f3e190f..6fa200ac60973 100644 --- a/storage/innobase/trx/trx0purge.cc +++ b/storage/innobase/trx/trx0purge.cc @@ -1103,8 +1103,10 @@ static dict_table_t *trx_purge_table_acquire(dict_table_t *table, char tbl_buf[NAME_LEN + 1]; size_t tbl_len; - if (!table->parse_name(db_buf, tbl_buf, &db_len, &tbl_len)) - /* The name of an intermediate table starts with #sql */ + dberr_t parse_result = table->parse_name( + db_buf, tbl_buf, &db_len, &tbl_len); + if (parse_result == DB_SUCCESS_LOCKED_REC || + parse_result == DB_CORRUPTION) goto got_table; {