1
0
mirror of https://github.com/MariaDB/server.git synced 2025-08-08 11:22:35 +03:00

MDEV-32899 InnoDB is holding shared dict_sys.latch while waiting for FOREIGN KEY child table lock on DDL

lock_table_children(): A new function to lock all child tables of a table.
We will only hold dict_sys.latch while traversing
dict_table_t::referenced_set. To prevent a race condition with
std::set::erase() we will copy the pointers to the child tables to a
local vector. Once we have acquired MDL and references to all child tables,
we can safely release dict_sys.latch, wait for the locks, and finally
release the references.

dict_acquire_mdl_shared(): A new variant that takes mdl_context as a
parameter.

lock_table_for_trx(): Assert that we are not holding dict_sys.latch.

ha_innobase::truncate(): When foreign_key_checks=ON, assert that
no child tables exist (other than the current table).
In any case, we will invoke lock_table_children()
so that the child table metadata can be safely updated.
(It is possible that a child table is being created concurrently
with TRUNCATE TABLE.)

ha_innobase::delete_table(): Before and after acquiring exclusive
locks on the current table as well as all child tables,
check that FOREIGN KEY constraints will not be violated.
In this way, we can reject impossible DROP TABLE without having to
wait for locks first.

This fixes up commit 2ca1123464 (MDEV-26217)
and commit c3c53926c4 (MDEV-26554).
This commit is contained in:
Marko Mäkelä
2024-02-01 15:48:46 +02:00
committed by Daniel Black
parent 5f2dcd112b
commit b2654ba826
8 changed files with 248 additions and 94 deletions

View File

@@ -1035,9 +1035,22 @@ BEGIN;
INSERT INTO child SET a=1; INSERT INTO child SET a=1;
ERROR 23000: Cannot add or update a child row: a foreign key constraint fails (`test`.`child`, CONSTRAINT `child_ibfk_1` FOREIGN KEY (`a`) REFERENCES `parent` (`a`)) ERROR 23000: Cannot add or update a child row: a foreign key constraint fails (`test`.`child`, CONSTRAINT `child_ibfk_1` FOREIGN KEY (`a`) REFERENCES `parent` (`a`))
connection default; connection default;
TRUNCATE TABLE parent;
ERROR 42000: Cannot truncate a table referenced in a foreign key constraint (`test`.`child`, CONSTRAINT `child_ibfk_1` FOREIGN KEY (`a`) REFERENCES `test`.`parent` (`a`))
DROP TABLE parent;
ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails
SET innodb_lock_wait_timeout=0;
RENAME TABLE parent TO transparent;
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
ALTER TABLE parent FORCE, ALGORITHM=COPY;
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
ALTER TABLE parent FORCE, ALGORITHM=INPLACE;
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
SET innodb_lock_wait_timeout=0, foreign_key_checks=0; SET innodb_lock_wait_timeout=0, foreign_key_checks=0;
TRUNCATE TABLE parent; TRUNCATE TABLE parent;
ERROR HY000: Lock wait timeout exceeded; try restarting transaction ERROR HY000: Lock wait timeout exceeded; try restarting transaction
DROP TABLE parent;
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
ALTER TABLE parent FORCE, ALGORITHM=COPY; ALTER TABLE parent FORCE, ALGORITHM=COPY;
ERROR HY000: Lock wait timeout exceeded; try restarting transaction ERROR HY000: Lock wait timeout exceeded; try restarting transaction
ALTER TABLE parent FORCE, ALGORITHM=INPLACE; ALTER TABLE parent FORCE, ALGORITHM=INPLACE;
@@ -1052,7 +1065,13 @@ TRUNCATE TABLE parent;
ALTER TABLE parent FORCE, ALGORITHM=COPY; ALTER TABLE parent FORCE, ALGORITHM=COPY;
ALTER TABLE parent FORCE, ALGORITHM=INPLACE; ALTER TABLE parent FORCE, ALGORITHM=INPLACE;
ALTER TABLE parent ADD COLUMN b INT, ALGORITHM=INSTANT; ALTER TABLE parent ADD COLUMN b INT, ALGORITHM=INSTANT;
DROP TABLE child, parent; SET foreign_key_checks=ON;
TRUNCATE TABLE parent;
ERROR 42000: Cannot truncate a table referenced in a foreign key constraint (`test`.`child`, CONSTRAINT `child_ibfk_1` FOREIGN KEY (`a`) REFERENCES `test`.`parent` (`a`))
ALTER TABLE parent FORCE, ALGORITHM=COPY;
ALTER TABLE parent FORCE, ALGORITHM=INPLACE;
RENAME TABLE parent TO transparent;
DROP TABLE child, transparent;
# #
# MDEV-26217 Failing assertion: list.count > 0 in ut_list_remove # MDEV-26217 Failing assertion: list.count > 0 in ut_list_remove
# or Assertion `lock->trx == this' failed in dberr_t trx_t::drop_table # or Assertion `lock->trx == this' failed in dberr_t trx_t::drop_table

View File

@@ -1077,10 +1077,23 @@ BEGIN;
--error ER_NO_REFERENCED_ROW_2 --error ER_NO_REFERENCED_ROW_2
INSERT INTO child SET a=1; INSERT INTO child SET a=1;
connection default; connection default;
--error ER_TRUNCATE_ILLEGAL_FK
TRUNCATE TABLE parent;
--error ER_ROW_IS_REFERENCED_2
DROP TABLE parent;
SET innodb_lock_wait_timeout=0;
--error ER_LOCK_WAIT_TIMEOUT
RENAME TABLE parent TO transparent;
--error ER_LOCK_WAIT_TIMEOUT
ALTER TABLE parent FORCE, ALGORITHM=COPY;
--error ER_LOCK_WAIT_TIMEOUT
ALTER TABLE parent FORCE, ALGORITHM=INPLACE;
SET innodb_lock_wait_timeout=0, foreign_key_checks=0; SET innodb_lock_wait_timeout=0, foreign_key_checks=0;
--error ER_LOCK_WAIT_TIMEOUT --error ER_LOCK_WAIT_TIMEOUT
TRUNCATE TABLE parent; TRUNCATE TABLE parent;
--error ER_LOCK_WAIT_TIMEOUT --error ER_LOCK_WAIT_TIMEOUT
DROP TABLE parent;
--error ER_LOCK_WAIT_TIMEOUT
ALTER TABLE parent FORCE, ALGORITHM=COPY; ALTER TABLE parent FORCE, ALGORITHM=COPY;
--error ER_LOCK_WAIT_TIMEOUT --error ER_LOCK_WAIT_TIMEOUT
ALTER TABLE parent FORCE, ALGORITHM=INPLACE; ALTER TABLE parent FORCE, ALGORITHM=INPLACE;
@@ -1095,7 +1108,13 @@ TRUNCATE TABLE parent;
ALTER TABLE parent FORCE, ALGORITHM=COPY; ALTER TABLE parent FORCE, ALGORITHM=COPY;
ALTER TABLE parent FORCE, ALGORITHM=INPLACE; ALTER TABLE parent FORCE, ALGORITHM=INPLACE;
ALTER TABLE parent ADD COLUMN b INT, ALGORITHM=INSTANT; ALTER TABLE parent ADD COLUMN b INT, ALGORITHM=INSTANT;
DROP TABLE child, parent; SET foreign_key_checks=ON;
--error ER_TRUNCATE_ILLEGAL_FK
TRUNCATE TABLE parent;
ALTER TABLE parent FORCE, ALGORITHM=COPY;
ALTER TABLE parent FORCE, ALGORITHM=INPLACE;
RENAME TABLE parent TO transparent;
DROP TABLE child, transparent;
--echo # --echo #
--echo # MDEV-26217 Failing assertion: list.count > 0 in ut_list_remove --echo # MDEV-26217 Failing assertion: list.count > 0 in ut_list_remove

View File

@@ -657,47 +657,22 @@ dict_table_t::parse_name<>(char(&)[NAME_LEN + 1], char(&)[NAME_LEN + 1],
/** Acquire MDL shared for the table name. /** Acquire MDL shared for the table name.
@tparam trylock whether to use non-blocking operation @tparam trylock whether to use non-blocking operation
@param[in,out] table table object @param[in,out] table table object
@param[in,out] thd background thread @param[in,out] mdl_context MDL context
@param[out] mdl mdl ticket @param[out] mdl MDL ticket
@param[in] table_op operation to perform when opening @param[in] table_op operation to perform when opening
@return table object after locking MDL shared @return table object after locking MDL shared
@retval nullptr if the table is not readable, or if trylock && MDL blocked */ @retval nullptr if the table is not readable, or if trylock && MDL blocked */
template<bool trylock> template<bool trylock>
__attribute__((nonnull, warn_unused_result))
dict_table_t* dict_table_t*
dict_acquire_mdl_shared(dict_table_t *table, dict_acquire_mdl_shared(dict_table_t *table,
THD *thd, MDL_context *mdl_context, MDL_ticket **mdl,
MDL_ticket **mdl,
dict_table_op_t table_op) dict_table_op_t table_op)
{ {
if (!table || !mdl)
return table;
MDL_context *mdl_context= static_cast<MDL_context*>(thd_mdl_context(thd));
size_t db_len;
dict_table_t *not_found= nullptr;
if (trylock)
{
dict_sys.freeze(SRW_LOCK_CALL);
db_len= dict_get_db_name_len(table->name.m_name);
dict_sys.unfreeze();
}
else
{
ut_ad(dict_sys.frozen_not_locked());
db_len= dict_get_db_name_len(table->name.m_name);
}
if (db_len == 0)
return table; /* InnoDB system tables are not covered by MDL */
if (!mdl_context)
return nullptr;
table_id_t table_id= table->id; table_id_t table_id= table->id;
char db_buf[NAME_LEN + 1], db_buf1[NAME_LEN + 1]; char db_buf[NAME_LEN + 1], db_buf1[NAME_LEN + 1];
char tbl_buf[NAME_LEN + 1], tbl_buf1[NAME_LEN + 1]; char tbl_buf[NAME_LEN + 1], tbl_buf1[NAME_LEN + 1];
size_t tbl_len; size_t db_len, tbl_len;
bool unaccessible= false; bool unaccessible= false;
if (!table->parse_name<!trylock>(db_buf, tbl_buf, &db_len, &tbl_len)) if (!table->parse_name<!trylock>(db_buf, tbl_buf, &db_len, &tbl_len))
@@ -768,7 +743,6 @@ retry:
if (!table || !table->is_accessible()) if (!table || !table->is_accessible())
{ {
table= nullptr;
return_without_mdl: return_without_mdl:
if (trylock) if (trylock)
dict_sys.unfreeze(); dict_sys.unfreeze();
@@ -777,7 +751,7 @@ return_without_mdl:
mdl_context->release_lock(*mdl); mdl_context->release_lock(*mdl);
*mdl= nullptr; *mdl= nullptr;
} }
return not_found; return nullptr;
} }
size_t db1_len, tbl1_len; size_t db1_len, tbl1_len;
@@ -814,6 +788,50 @@ return_without_mdl:
goto retry; goto retry;
} }
template dict_table_t* dict_acquire_mdl_shared<false>
(dict_table_t*,MDL_context*,MDL_ticket**,dict_table_op_t);
/** Acquire MDL shared for the table name.
@tparam trylock whether to use non-blocking operation
@param[in,out] table table object
@param[in,out] thd background thread
@param[out] mdl mdl ticket
@param[in] table_op operation to perform when opening
@return table object after locking MDL shared
@retval nullptr if the table is not readable, or if trylock && MDL blocked */
template<bool trylock>
dict_table_t*
dict_acquire_mdl_shared(dict_table_t *table,
THD *thd,
MDL_ticket **mdl,
dict_table_op_t table_op)
{
if (!table || !mdl)
return table;
MDL_context *mdl_context= static_cast<MDL_context*>(thd_mdl_context(thd));
size_t db_len;
if (trylock)
{
dict_sys.freeze(SRW_LOCK_CALL);
db_len= dict_get_db_name_len(table->name.m_name);
dict_sys.unfreeze();
}
else
{
ut_ad(dict_sys.frozen_not_locked());
db_len= dict_get_db_name_len(table->name.m_name);
}
if (db_len == 0)
return table; /* InnoDB system tables are not covered by MDL */
return mdl_context
? dict_acquire_mdl_shared<trylock>(table, mdl_context, mdl, table_op)
: nullptr;
}
template dict_table_t* dict_acquire_mdl_shared<false> template dict_table_t* dict_acquire_mdl_shared<false>
(dict_table_t*,THD*,MDL_ticket**,dict_table_op_t); (dict_table_t*,THD*,MDL_ticket**,dict_table_op_t);
template dict_table_t* dict_acquire_mdl_shared<true> template dict_table_t* dict_acquire_mdl_shared<true>

View File

@@ -13395,6 +13395,49 @@ ha_innobase::discard_or_import_tablespace(
DBUG_RETURN(0); DBUG_RETURN(0);
} }
/** Report a DROP TABLE failure due to a FOREIGN KEY constraint.
@param name table name
@param foreign constraint */
ATTRIBUTE_COLD
static void delete_table_cannot_drop_foreign(const table_name_t &name,
const dict_foreign_t &foreign)
{
mysql_mutex_lock(&dict_foreign_err_mutex);
rewind(dict_foreign_err_file);
ut_print_timestamp(dict_foreign_err_file);
fputs(" Cannot drop table ", dict_foreign_err_file);
ut_print_name(dict_foreign_err_file, nullptr, name.m_name);
fputs("\nbecause it is referenced by ", dict_foreign_err_file);
ut_print_name(dict_foreign_err_file, nullptr, foreign.foreign_table_name);
putc('\n', dict_foreign_err_file);
mysql_mutex_unlock(&dict_foreign_err_mutex);
}
/** Check if DROP TABLE would fail due to a FOREIGN KEY constraint.
@param table table to be dropped
@param sqlcom thd_sql_command(current_thd)
@return whether child tables that refer to this table exist */
static bool delete_table_check_foreigns(const dict_table_t &table,
enum_sql_command sqlcom)
{
const bool drop_db{sqlcom == SQLCOM_DROP_DB};
for (const auto foreign : table.referenced_set)
{
/* We should allow dropping a referenced table if creating
that referenced table has failed for some reason. For example
if referenced table is created but it column types that are
referenced do not match. */
if (foreign->foreign_table == &table ||
(drop_db &&
dict_tables_have_same_db(table.name.m_name,
foreign->foreign_table_name_lookup)))
continue;
delete_table_cannot_drop_foreign(table.name, *foreign);
return true;
}
return false;
}
/** DROP TABLE (possibly as part of DROP DATABASE, CREATE/ALTER TABLE) /** DROP TABLE (possibly as part of DROP DATABASE, CREATE/ALTER TABLE)
@param name table name @param name table name
@@ -13410,6 +13453,7 @@ int ha_innobase::delete_table(const char *name)
DBUG_EXECUTE_IF("test_normalize_table_name_low", DBUG_EXECUTE_IF("test_normalize_table_name_low",
test_normalize_table_name_low();); test_normalize_table_name_low(););
const enum_sql_command sqlcom= enum_sql_command(thd_sql_command(thd));
trx_t *parent_trx= check_trx_exists(thd); trx_t *parent_trx= check_trx_exists(thd);
dict_table_t *table; dict_table_t *table;
@@ -13446,6 +13490,13 @@ int ha_innobase::delete_table(const char *name)
DBUG_RETURN(0); DBUG_RETURN(0);
} }
if (parent_trx->check_foreigns &&
delete_table_check_foreigns(*table, sqlcom))
{
dict_sys.unlock();
DBUG_RETURN(HA_ERR_ROW_IS_REFERENCED);
}
table->acquire(); table->acquire();
dict_sys.unlock(); dict_sys.unlock();
@@ -13478,14 +13529,7 @@ int ha_innobase::delete_table(const char *name)
/* FOREIGN KEY constraints cannot exist on partitioned tables. */; /* FOREIGN KEY constraints cannot exist on partitioned tables. */;
#endif #endif
else else
{ err= lock_table_children(table, trx);
dict_sys.freeze(SRW_LOCK_CALL);
for (const dict_foreign_t* f : table->referenced_set)
if (dict_table_t* child= f->foreign_table)
if ((err= lock_table_for_trx(child, trx, LOCK_X)) != DB_SUCCESS)
break;
dict_sys.unfreeze();
}
} }
dict_table_t *table_stats= nullptr, *index_stats= nullptr; dict_table_t *table_stats= nullptr, *index_stats= nullptr;
@@ -13495,7 +13539,6 @@ int ha_innobase::delete_table(const char *name)
const bool fts= err == DB_SUCCESS && const bool fts= err == DB_SUCCESS &&
(table->flags2 & (DICT_TF2_FTS_HAS_DOC_ID | DICT_TF2_FTS)); (table->flags2 & (DICT_TF2_FTS_HAS_DOC_ID | DICT_TF2_FTS));
const enum_sql_command sqlcom= enum_sql_command(thd_sql_command(thd));
if (fts) if (fts)
{ {
@@ -13653,36 +13696,16 @@ err_exit:
DBUG_RETURN(convert_error_code_to_mysql(err, 0, NULL)); DBUG_RETURN(convert_error_code_to_mysql(err, 0, NULL));
} }
if (!table->no_rollback() && trx->check_foreigns) if (!table->no_rollback())
{ {
const bool drop_db= sqlcom == SQLCOM_DROP_DB; if (trx->check_foreigns && delete_table_check_foreigns(*table, sqlcom))
for (auto foreign : table->referenced_set)
{ {
/* We should allow dropping a referenced table if creating
that referenced table has failed for some reason. For example
if referenced table is created but it column types that are
referenced do not match. */
if (foreign->foreign_table == table ||
(drop_db &&
dict_tables_have_same_db(table->name.m_name,
foreign->foreign_table_name_lookup)))
continue;
mysql_mutex_lock(&dict_foreign_err_mutex);
rewind(dict_foreign_err_file);
ut_print_timestamp(dict_foreign_err_file);
fputs(" Cannot drop table ", dict_foreign_err_file);
ut_print_name(dict_foreign_err_file, trx, table->name.m_name);
fputs("\nbecause it is referenced by ", dict_foreign_err_file);
ut_print_name(dict_foreign_err_file, trx, foreign->foreign_table_name);
putc('\n', dict_foreign_err_file);
mysql_mutex_unlock(&dict_foreign_err_mutex);
err= DB_CANNOT_DROP_CONSTRAINT; err= DB_CANNOT_DROP_CONSTRAINT;
goto err_exit; goto err_exit;
} }
}
if (!table->no_rollback())
err= trx->drop_table_foreign(table->name); err= trx->drop_table_foreign(table->name);
}
if (err == DB_SUCCESS && table_stats && index_stats) if (err == DB_SUCCESS && table_stats && index_stats)
err= trx->drop_table_statistics(table->name); err= trx->drop_table_statistics(table->name);
@@ -13801,6 +13824,19 @@ int ha_innobase::truncate()
update_thd(); update_thd();
#ifdef UNIV_DEBUG
if (!thd_test_options(m_user_thd, OPTION_NO_FOREIGN_KEY_CHECKS))
{
/* fk_truncate_illegal_if_parent() should have failed in
Sql_cmd_truncate_table::handler_truncate() if foreign_key_checks=ON
and child tables exist. */
dict_sys.freeze(SRW_LOCK_CALL);
for (const auto foreign : m_prebuilt->table->referenced_set)
ut_ad(foreign->foreign_table == m_prebuilt->table);
dict_sys.unfreeze();
}
#endif
if (is_read_only()) if (is_read_only())
DBUG_RETURN(HA_ERR_TABLE_READONLY); DBUG_RETURN(HA_ERR_TABLE_READONLY);
@@ -13883,14 +13919,7 @@ int ha_innobase::truncate()
dict_table_t *table_stats = nullptr, *index_stats = nullptr; dict_table_t *table_stats = nullptr, *index_stats = nullptr;
MDL_ticket *mdl_table = nullptr, *mdl_index = nullptr; MDL_ticket *mdl_table = nullptr, *mdl_index = nullptr;
dberr_t error= DB_SUCCESS; dberr_t error= lock_table_children(ib_table, trx);
dict_sys.freeze(SRW_LOCK_CALL);
for (const dict_foreign_t *f : ib_table->referenced_set)
if (dict_table_t *child= f->foreign_table)
if ((error= lock_table_for_trx(child, trx, LOCK_X)) != DB_SUCCESS)
break;
dict_sys.unfreeze();
if (error == DB_SUCCESS) if (error == DB_SUCCESS)
error= lock_table_for_trx(ib_table, trx, LOCK_X); error= lock_table_for_trx(ib_table, trx, LOCK_X);
@@ -14081,16 +14110,7 @@ ha_innobase::rename_table(
/* There is no need to lock any FOREIGN KEY child tables. */ /* There is no need to lock any FOREIGN KEY child tables. */
} else if (dict_table_t *table = dict_table_open_on_name( } else if (dict_table_t *table = dict_table_open_on_name(
norm_from, false, DICT_ERR_IGNORE_FK_NOKEY)) { norm_from, false, DICT_ERR_IGNORE_FK_NOKEY)) {
dict_sys.freeze(SRW_LOCK_CALL); error = lock_table_children(table, trx);
for (const dict_foreign_t* f : table->referenced_set) {
if (dict_table_t* child = f->foreign_table) {
error = lock_table_for_trx(child, trx, LOCK_X);
if (error != DB_SUCCESS) {
break;
}
}
}
dict_sys.unfreeze();
if (error == DB_SUCCESS) { if (error == DB_SUCCESS) {
error = lock_table_for_trx(table, trx, LOCK_X); error = lock_table_for_trx(table, trx, LOCK_X);
} }

View File

@@ -11203,16 +11203,7 @@ ha_innobase::commit_inplace_alter_table(
fts_optimize_remove_table(ctx->old_table); fts_optimize_remove_table(ctx->old_table);
} }
dict_sys.freeze(SRW_LOCK_CALL); error = lock_table_children(ctx->old_table, trx);
for (auto f : ctx->old_table->referenced_set) {
if (dict_table_t* child = f->foreign_table) {
error = lock_table_for_trx(child, trx, LOCK_X);
if (error != DB_SUCCESS) {
break;
}
}
}
dict_sys.unfreeze();
if (ctx->new_table->fts) { if (ctx->new_table->fts) {
ut_ad(!ctx->new_table->fts->add_wq); ut_ad(!ctx->new_table->fts->add_wq);

View File

@@ -35,6 +35,7 @@ Created 1/8/1996 Heikki Tuuri
#include <my_sys.h> #include <my_sys.h>
#include <deque> #include <deque>
class MDL_context;
class MDL_ticket; class MDL_ticket;
/** the first table or index ID for other than hard-coded system tables */ /** the first table or index ID for other than hard-coded system tables */
@@ -139,6 +140,21 @@ dict_acquire_mdl_shared(dict_table_t *table,
MDL_ticket **mdl, MDL_ticket **mdl,
dict_table_op_t table_op= DICT_TABLE_OP_NORMAL); dict_table_op_t table_op= DICT_TABLE_OP_NORMAL);
/** Acquire MDL shared for the table name.
@tparam trylock whether to use non-blocking operation
@param[in,out] table table object
@param[in,out] mdl_context MDL context
@param[out] mdl MDL ticket
@param[in] table_op operation to perform when opening
@return table object after locking MDL shared
@retval nullptr if the table is not readable, or if trylock && MDL blocked */
template<bool trylock>
__attribute__((nonnull, warn_unused_result))
dict_table_t*
dict_acquire_mdl_shared(dict_table_t *table,
MDL_context *mdl_context, MDL_ticket **mdl,
dict_table_op_t table_op);
/** Look up a table by numeric identifier. /** Look up a table by numeric identifier.
@param[in] table_id table identifier @param[in] table_id table identifier
@param[in] dict_locked data dictionary locked @param[in] dict_locked data dictionary locked

View File

@@ -438,6 +438,13 @@ dberr_t lock_table_for_trx(dict_table_t *table, trx_t *trx, lock_mode mode,
bool no_wait= false) bool no_wait= false)
MY_ATTRIBUTE((nonnull, warn_unused_result)); MY_ATTRIBUTE((nonnull, warn_unused_result));
/** Lock the child tables of a table.
@param table parent table
@param trx transaction
@return error code */
dberr_t lock_table_children(dict_table_t *table, trx_t *trx)
MY_ATTRIBUTE((nonnull, warn_unused_result));
/** Exclusively lock the data dictionary tables. /** Exclusively lock the data dictionary tables.
@param trx dictionary transaction @param trx dictionary transaction
@return error code @return error code

View File

@@ -47,6 +47,7 @@ Created 5/7/1996 Heikki Tuuri
#include "que0que.h" #include "que0que.h"
#include "scope.h" #include "scope.h"
#include <debug_sync.h> #include <debug_sync.h>
#include <mysql/service_thd_mdl.h>
#include <set> #include <set>
@@ -3933,6 +3934,8 @@ static void lock_table_dequeue(lock_t *in_lock, bool owns_wait_mutex)
dberr_t lock_table_for_trx(dict_table_t *table, trx_t *trx, lock_mode mode, dberr_t lock_table_for_trx(dict_table_t *table, trx_t *trx, lock_mode mode,
bool no_wait) bool no_wait)
{ {
ut_ad(!dict_sys.frozen());
mem_heap_t *heap= mem_heap_create(512); mem_heap_t *heap= mem_heap_create(512);
sel_node_t *node= sel_node_create(heap); sel_node_t *node= sel_node_create(heap);
que_thr_t *thr= pars_complete_graph_for_exec(node, trx, heap, nullptr); que_thr_t *thr= pars_complete_graph_for_exec(node, trx, heap, nullptr);
@@ -3969,6 +3972,67 @@ run_again:
return err; return err;
} }
/** Lock the child tables of a table.
@param table parent table
@param trx transaction
@return error code */
dberr_t lock_table_children(dict_table_t *table, trx_t *trx)
{
MDL_context *mdl_context=
static_cast<MDL_context*>(thd_mdl_context(trx->mysql_thd));
ut_ad(mdl_context);
struct table_mdl{dict_table_t* table; MDL_ticket *mdl;};
std::vector<table_mdl> children;
children.emplace_back(table_mdl{table, nullptr});
dberr_t err= DB_SUCCESS;
dict_sys.freeze(SRW_LOCK_CALL);
rescan:
for (auto f : table->referenced_set)
if (dict_table_t *child= f->foreign_table)
{
if (std::find_if(children.begin(), children.end(),
[&](const table_mdl &c){ return c.table == child; }) !=
children.end())
continue; /* We already acquired MDL on this child table. */
MDL_ticket *mdl= nullptr;
child->acquire();
child= dict_acquire_mdl_shared<false>(child, mdl_context, &mdl,
DICT_TABLE_OP_NORMAL);
if (child)
{
if (!mdl)
child->release();
children.emplace_back(table_mdl{child, mdl});
goto rescan;
}
err= DB_LOCK_WAIT_TIMEOUT;
break;
}
dict_sys.unfreeze();
if (err == DB_SUCCESS)
for (const table_mdl &child : children)
if (child.mdl)
if ((err= lock_table_for_trx(child.table, trx, LOCK_X)) != DB_SUCCESS)
break;
dict_sys.freeze(SRW_LOCK_CALL);
for (table_mdl &child : children)
{
if (child.mdl)
{
child.table->release();
mdl_context->release_lock(child.mdl);
}
}
dict_sys.unfreeze();
return err;
}
/** Exclusively lock the data dictionary tables. /** Exclusively lock the data dictionary tables.
@param trx dictionary transaction @param trx dictionary transaction
@return error code @return error code