Skip to content
Permalink
Browse files
MDEV-21175 follow-up: Remove redundant locking; rely on MDL
Before entering DML or DDL execution in the storage engine, the SQL layer
will have acquired metadata lock (MDL) on the current table name as well
as the names of FOREIGN KEY (grand)child tables (that is,
tables whose REFERENCES clauses point to the current table).
The MDL prevents any metadata changes to these tables, such as
RENAME, TRUNCATE, DROP, ALTER.

While the MDL on the current table prevents dict_table_t::foreign_set
from being modified, it does not prevent the table metadata that the
stored pointers are pointing to from being modified.

The MDL on the child tables will prevent both dict_table_t::referenced_set
as well as the pointed child table metadata from being modified.

wsrep_row_upd_index_is_foreign(): Do not unnecessarily acquire the
data dictionary latch if Galera replication is not enabled.

ha_innobase::can_switch_engines(): Rely on MDL. We are not dereferencing
any pointers stored in the sets.

row_mysql_freeze_data_dictionary(), row_mysql_unfreeze_data_dictionary():
Remove.

row_update_for_mysql(): Call init_fts_doc_id_for_ref() only once.

In ALTER TABLE...IMPORT TABLESPACE and FLUSH TABLES...FOR EXPORT
the SQL layer is protecting the current table with MDL. We do not
need InnoDB latches.
  • Loading branch information
dr-m committed Jul 29, 2021
1 parent 86a1428 commit e305493
Show file tree
Hide file tree
Showing 8 changed files with 39 additions and 223 deletions.
@@ -15289,32 +15289,13 @@ struct table_list_item {
const char* name;
};

/*****************************************************************//**
Checks if ALTER TABLE may change the storage engine of the table.
Changing storage engines is not allowed for tables for which there
are foreign key constraints (parent or child tables).
@return TRUE if can switch engines */

bool
ha_innobase::can_switch_engines(void)
/*=================================*/
/** @return whether ALTER TABLE may change the storage engine of the table */
bool ha_innobase::can_switch_engines()
{
DBUG_ENTER("ha_innobase::can_switch_engines");

update_thd();

m_prebuilt->trx->op_info =
"determining if there are foreign key constraints";

row_mysql_freeze_data_dictionary(m_prebuilt->trx);

bool can_switch = m_prebuilt->table->referenced_set.empty()
&& m_prebuilt->table->foreign_set.empty();

row_mysql_unfreeze_data_dictionary(m_prebuilt->trx);
m_prebuilt->trx->op_info = "";

DBUG_RETURN(can_switch);
DBUG_ENTER("ha_innobase::can_switch_engines");
update_thd();
return m_prebuilt->table->foreign_set.empty() &&
m_prebuilt->table->referenced_set.empty();
}

/*******************************************************************//**
@@ -1524,24 +1524,6 @@ struct dict_foreign_with_index {
const dict_index_t* m_index;
};

#ifdef WITH_WSREP
/** A function object to find a foreign key with the given index as the
foreign index. Return the foreign key with matching criteria or NULL */
struct dict_foreign_with_foreign_index {

dict_foreign_with_foreign_index(const dict_index_t* index)
: m_index(index)
{}

bool operator()(const dict_foreign_t* foreign) const
{
return(foreign->foreign_index == m_index);
}

const dict_index_t* m_index;
};
#endif

/* A function object to check if the foreign constraint is between different
tables. Returns true if foreign key constraint is between different tables,
false otherwise. */
@@ -330,17 +330,6 @@ row_mysql_unlock_data_dictionary(
/*=============================*/
trx_t* trx); /*!< in/out: transaction */
/*********************************************************************//**
Locks the data dictionary in shared mode from modifications, for performing
foreign key check, rollback, or other operation invisible to MySQL. */
void row_mysql_freeze_data_dictionary(trx_t *trx);

/*********************************************************************//**
Unlocks the data dictionary shared lock. */
void
row_mysql_unfreeze_data_dictionary(
/*===============================*/
trx_t* trx); /*!< in/out: transaction */
/*********************************************************************//**
Creates a table for MySQL. On failure the transaction will be rolled back
and the 'table' object will be freed.
@return error code or DB_SUCCESS */
@@ -1703,8 +1703,7 @@ dberr_t lock_wait(que_thr_t *thr)
const ulong innodb_lock_wait_timeout= trx_lock_wait_timeout_get(trx);
const bool no_timeout= innodb_lock_wait_timeout >= 100000000;
const my_hrtime_t suspend_time= my_hrtime_coarse();
ut_ad(!trx->dict_operation_lock_mode ||
trx->dict_operation_lock_mode == RW_S_LATCH);
ut_ad(!trx->dict_operation_lock_mode);

/* The wait_lock can be cleared by another thread in lock_grant(),
lock_rec_cancel(), or lock_cancel_waiting_and_release(). But, a wait
@@ -1739,9 +1738,7 @@ dberr_t lock_wait(que_thr_t *thr)

trx->lock.suspend_time= suspend_time;

const auto had_dict_lock= trx->dict_operation_lock_mode;
if (had_dict_lock) /* Release foreign key check latch */
row_mysql_unfreeze_data_dictionary(trx);
ut_ad(!trx->dict_operation_lock_mode);

IF_WSREP(if (trx->is_wsrep()) lock_wait_wsrep(trx),);

@@ -1837,9 +1834,6 @@ dberr_t lock_wait(que_thr_t *thr)
mysql_mutex_unlock(&lock_sys.wait_mutex);
thd_wait_end(trx->mysql_thd);

if (had_dict_lock)
row_mysql_freeze_data_dictionary(trx);

trx->error_state= error_state;
return error_state;
}
@@ -3960,10 +3960,6 @@ row_import_for_mysql(

prebuilt->trx->op_info = "read meta-data file";

/* Prevent DDL operations while we are checking. */

dict_sys.freeze();

row_import cfg;

err = row_import_read_cfg(table, trx->mysql_thd, cfg);
@@ -3987,15 +3983,10 @@ row_import_for_mysql(
autoinc = cfg.m_autoinc;
}

dict_sys.unfreeze();

DBUG_EXECUTE_IF("ib_import_set_index_root_failure",
err = DB_TOO_MANY_CONCURRENT_TRXS;);

} else if (cfg.m_missing) {

dict_sys.unfreeze();

/* We don't have a schema file, we will have to discover
the index root pages from the .ibd file and skip the schema
matching step. */
@@ -4022,8 +4013,6 @@ row_import_for_mysql(
err = cfg.set_root_by_heuristic();
}
}
} else {
dict_sys.unfreeze();
}

if (err != DB_SUCCESS) {
@@ -1342,16 +1342,6 @@ row_ins_foreign_check_on_constraint(
err = row_update_cascade_for_mysql(thr, cascade,
foreign->foreign_table);

/* Release the data dictionary latch for a while, so that we do not
starve other threads from doing CREATE TABLE etc. if we have a huge
cascaded operation running. */

row_mysql_unfreeze_data_dictionary(thr_get_trx(thr));

DEBUG_SYNC_C("innodb_dml_cascade_dict_unfreeze");

row_mysql_freeze_data_dictionary(thr_get_trx(thr));

mtr_start(mtr);

/* Restore pcur position */
@@ -1626,33 +1626,24 @@ init_fts_doc_id_for_ref(
dict_table_t* table, /*!< in: table */
ulint* depth) /*!< in: recusive call depth */
{
dict_foreign_t* foreign;

table->fk_max_recusive_level = 0;

(*depth)++;

/* Limit on tables involved in cascading delete/update */
if (*depth > FK_MAX_CASCADE_DEL) {
if (++*depth > FK_MAX_CASCADE_DEL) {
return;
}

/* Loop through this table's referenced list and also
recursively traverse each table's foreign table list */
for (dict_foreign_set::iterator it = table->referenced_set.begin();
it != table->referenced_set.end();
++it) {

foreign = *it;
for (dict_foreign_t* foreign : table->referenced_set) {
ut_ad(foreign->foreign_table);

ut_ad(foreign->foreign_table != NULL);

if (foreign->foreign_table->fts != NULL) {
if (foreign->foreign_table->fts) {
fts_init_doc_id(foreign->foreign_table);
}

if (!foreign->foreign_table->referenced_set.empty()
&& foreign->foreign_table != table) {
if (foreign->foreign_table != table
&& !foreign->foreign_table->referenced_set.empty()) {
init_fts_doc_id_for_ref(
foreign->foreign_table, depth);
}
@@ -1673,7 +1664,6 @@ row_update_for_mysql(row_prebuilt_t* prebuilt)
dict_table_t* table = prebuilt->table;
trx_t* trx = prebuilt->trx;
ulint fk_depth = 0;
bool got_s_lock = false;

DBUG_ENTER("row_update_for_mysql");

@@ -1703,18 +1693,6 @@ row_update_for_mysql(row_prebuilt_t* prebuilt)
trx_start_if_not_started_xa(trx, true);
}

if (dict_table_is_referenced_by_foreign_key(table)) {
/* Share lock the data dictionary to prevent any
table dictionary (for foreign constraint) change.
This is similar to row_ins_check_foreign_constraint
check protect by the dictionary lock as well.
In the future, this can be removed once the Foreign
key MDL is implemented */
row_mysql_freeze_data_dictionary(trx);
init_fts_doc_id_for_ref(table, &fk_depth);
row_mysql_unfreeze_data_dictionary(trx);
}

node = prebuilt->upd_node;
const bool is_delete = node->is_delete == PLAIN_DELETE;
ut_ad(node->table == table);
@@ -1795,10 +1773,6 @@ row_update_for_mysql(row_prebuilt_t* prebuilt)
}

/* Completed cascading operations (if any) */
if (got_s_lock) {
row_mysql_unfreeze_data_dictionary(trx);
}

bool update_statistics;
ut_ad(is_delete == (node->is_delete == PLAIN_DELETE));

@@ -1834,16 +1808,8 @@ row_update_for_mysql(row_prebuilt_t* prebuilt)
prebuilt->table->stat_modified_counter++;
}

trx->op_info = "";

DBUG_RETURN(err);

error:
trx->op_info = "";
if (got_s_lock) {
row_mysql_unfreeze_data_dictionary(trx);
}

DBUG_RETURN(err);
}

@@ -1970,29 +1936,6 @@ row_unlock_for_mysql(
trx->op_info = "";
}

/*********************************************************************//**
Locks the data dictionary in shared mode from modifications, for performing
foreign key check, rollback, or other operation invisible to MySQL. */
void row_mysql_freeze_data_dictionary(trx_t *trx)
{
ut_a(trx->dict_operation_lock_mode == 0);
trx->dict_operation_lock_mode = RW_S_LATCH;
dict_sys.freeze();
}

/*********************************************************************//**
Unlocks the data dictionary shared lock. */
void
row_mysql_unfreeze_data_dictionary(
/*===============================*/
trx_t* trx) /*!< in/out: transaction */
{
ut_ad(!lock_trx_has_sys_table_locks(trx));
ut_ad(trx->dict_operation_lock_mode == RW_S_LATCH);
dict_sys.unfreeze();
trx->dict_operation_lock_mode = 0;
}

/** Write query start time as SQL field data to a buffer. Needed by InnoDB.
@param thd Thread object
@param buf Buffer to hold start time data */

0 comments on commit e305493

Please sign in to comment.