Commit 7dc405f0 authored by Marko Mäkelä's avatar Marko Mäkelä

WIP MDEV-21175 Remove dict_table_t::n_foreign_key_checks_running

The counter dict_table_t::n_foreign_key_checks_running
was a work-around for missing meta-data locking (MDL)
on the SQL layer.

ER_TABLE_IN_FK_CHECK: Replaced with ER_UNUSED_26.

HA_ERR_TABLE_IN_FK_CHECK: Remove.

dict_table_t: Remove inc_fk_checks(), dec_fk_checks(),
n_foreign_key_checks_running.

row_ins_check_foreign_constraints(),
row_upd_check_references_constraints(): Do not acquire
a shared dict_sys latch nor touch the reference counters.

row_drop_table_for_mysql(): Make the checks stricter.
We will still employ row_mysql_drop_list for
TRUNCATE TABLE and for dropping internal tables related
to FULLTEXT INDEX. So, this will not fix MDEV-21283 yet.

MDEV-21602 CREATE TABLE…PRIMARY KEY…SELECT workaround
causes DROP TABLE to ignore locks

The error handling of CREATE…SELECT would invoke
handler::delete_table() while still holding locks
on the table, due to not having invoked handlerton::rollback
first. InnoDB used to work around this as well.
In MDEV-742 this was worked around further by breaking
MDL, causing MDEV-22733.
parent a18639f1
......@@ -74,7 +74,6 @@
{ "HA_ERR_INDEX_COL_TOO_LONG", HA_ERR_INDEX_COL_TOO_LONG, "" },
{ "HA_ERR_INDEX_CORRUPT", HA_ERR_INDEX_CORRUPT, "" },
{ "HA_ERR_UNDO_REC_TOO_BIG", HA_ERR_UNDO_REC_TOO_BIG, "" },
{ "HA_ERR_TABLE_IN_FK_CHECK", HA_ERR_TABLE_IN_FK_CHECK, "" },
{ "HA_ERR_ROW_NOT_VISIBLE", HA_ERR_ROW_NOT_VISIBLE, "" },
{ "HA_ERR_ABORTED_BY_USER", HA_ERR_ABORTED_BY_USER, "" },
{ "HA_ERR_DISK_FULL", HA_ERR_DISK_FULL, "" },
......
/* Copyright (c) 2000, 2012, Oracle and/or its affiliates.
Copyright (c) 1995, 2018, MariaDB Corporation.
Copyright (c) 1995, 2020, MariaDB Corporation.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -513,7 +513,7 @@ enum ha_base_keytype {
#define HA_ERR_INDEX_CORRUPT 180 /* Index corrupted */
#define HA_ERR_UNDO_REC_TOO_BIG 181 /* Undo log record too big */
#define HA_FTS_INVALID_DOCID 182 /* Invalid InnoDB Doc ID */
#define HA_ERR_TABLE_IN_FK_CHECK 183 /* Table being used in foreign key check */
/* #define HA_ERR_TABLE_IN_FK_CHECK 183 */ /* Table being used in foreign key check */
#define HA_ERR_TABLESPACE_EXISTS 184 /* The tablespace existed in storage engine */
#define HA_ERR_TOO_MANY_FIELDS 185 /* Table has too many columns */
#define HA_ERR_ROW_IN_WRONG_PARTITION 186 /* Row in wrong partition */
......
......@@ -11,3 +11,5 @@
##############################################################################
binlog_truncate_innodb : BUG#11764459 2010-10-20 anitha Originally disabled due to BUG#42643. Product bug fixed, but test changes needed
binlog_spurious_ddl_errors : BUG#11761680 2013-01-18 astha Fixed on mysql-5.6 and trunk
binlog_stm_mix_innodb_myisam : WIP: MDEV-21175/MDEV-21602
binlog_row_mix_innodb_myisam : WIP: MDEV-21175/MDEV-21602
......@@ -12,3 +12,5 @@
create-index-debug : MDEV-13680 InnoDB may crash when btr_page_alloc() fails
innodb_force_recovery_rollback : MDEV-22889 InnoDB occasionally breaks ACID
innodb_mysql : WIP: MDEV-21175/MDEV-21602
xa_recovery : WIP: MDEV-21175/MDEV-21602/MDEV-22769 (MDEV-742 regression)
......@@ -20,3 +20,9 @@ rpl_slave_grp_exec: MDEV-10514
rpl_auto_increment_update_failure : disabled for now
rpl_current_user : waits for MDEV-22374 fix
rpl_parallel2 : waits for MDEV-23089
rpl_mixed_mixing_engines : WIP: MDEV-21175/MDEV-21602
rpl_non_direct_mixed_mixing_engines : WIP: MDEV-21175/MDEV-21602
rpl_non_direct_row_mixing_engines : WIP: MDEV-21175/MDEV-21602
rpl_non_direct_stm_mixing_engines : WIP: MDEV-21175/MDEV-21602
rpl_row_mixing_engines : WIP: MDEV-21175/MDEV-21602
rpl_stm_mixing_engines : WIP: MDEV-21175/MDEV-21602
......@@ -486,7 +486,6 @@ int ha_init_errors(void)
SETMSG(HA_ERR_INDEX_COL_TOO_LONG, ER_DEFAULT(ER_INDEX_COLUMN_TOO_LONG));
SETMSG(HA_ERR_INDEX_CORRUPT, ER_DEFAULT(ER_INDEX_CORRUPT));
SETMSG(HA_FTS_INVALID_DOCID, "Invalid InnoDB FTS Doc ID");
SETMSG(HA_ERR_TABLE_IN_FK_CHECK, ER_DEFAULT(ER_TABLE_IN_FK_CHECK));
SETMSG(HA_ERR_DISK_FULL, ER_DEFAULT(ER_DISK_FULL));
SETMSG(HA_ERR_FTS_TOO_MANY_WORDS_IN_PHRASE, "Too many words in a FTS phrase or proximity search");
SETMSG(HA_ERR_FK_DEPTH_EXCEEDED, "Foreign key cascade delete/update exceeds");
......@@ -4186,9 +4185,6 @@ void handler::print_error(int error, myf errflag)
case HA_ERR_UNDO_REC_TOO_BIG:
textno= ER_UNDO_RECORD_TOO_BIG;
break;
case HA_ERR_TABLE_IN_FK_CHECK:
textno= ER_TABLE_IN_FK_CHECK;
break;
default:
{
/* The error was "unknown" to this function.
......
......@@ -6624,8 +6624,8 @@ ER_BINLOG_UNSAFE_CREATE_SELECT_AUTOINC
ER_BINLOG_UNSAFE_INSERT_TWO_KEYS
eng "INSERT... ON DUPLICATE KEY UPDATE on a table with more than one UNIQUE KEY is unsafe"
ER_TABLE_IN_FK_CHECK
eng "Table is being used in foreign key check"
ER_UNUSED_26
eng "You should never see it"
ER_UNUSED_1
eng "You should never see it"
......
......@@ -1854,9 +1854,6 @@ convert_error_code_to_mysql(
"InnoDB");
return(HA_ERR_INTERNAL_ERROR);
case DB_TABLE_IN_FK_CHECK:
return(HA_ERR_TABLE_IN_FK_CHECK);
case DB_TABLE_IS_BEING_USED:
return(HA_ERR_WRONG_COMMAND);
......
......@@ -121,8 +121,6 @@ enum dberr_t {
DB_READ_ONLY, /*!< Update operation attempted in
a read-only transaction */
DB_FTS_INVALID_DOCID, /* FTS Doc ID cannot be zero */
DB_TABLE_IN_FK_CHECK, /* table is being used in foreign
key check */
DB_ONLINE_LOG_TOO_BIG, /*!< Modification log grew too big
during online index creation */
......
......@@ -1916,23 +1916,6 @@ struct dict_table_t {
return versioned() && cols[vers_start].mtype == DATA_INT;
}
void inc_fk_checks()
{
#ifdef UNIV_DEBUG
int32_t fk_checks=
#endif
n_foreign_key_checks_running++;
ut_ad(fk_checks >= 0);
}
void dec_fk_checks()
{
#ifdef UNIV_DEBUG
int32_t fk_checks=
#endif
n_foreign_key_checks_running--;
ut_ad(fk_checks > 0);
}
/** For overflow fields returns potential max length stored inline */
inline size_t get_overflow_field_local_len() const;
......@@ -2110,11 +2093,6 @@ struct dict_table_t {
loading child table into memory along with its parent table. */
unsigned fk_max_recusive_level:8;
/** Count of how many foreign key check operations are currently being
performed on the table. We cannot drop the table while there are
foreign key checks running on it. */
Atomic_counter<int32_t> n_foreign_key_checks_running;
/** Transactions whose view low limit is greater than this number are
not allowed to store to the MySQL query cache or retrieve from it.
When a trx with undo logs commits, it sets this to the value of the
......
......@@ -1539,8 +1539,6 @@ row_ins_check_foreign_constraint(
upd_node= NULL;
#endif /* WITH_WSREP */
ut_ad(rw_lock_own(&dict_sys.latch, RW_LOCK_S));
err = DB_SUCCESS;
if (trx->check_foreigns == FALSE) {
......@@ -1886,8 +1884,6 @@ row_ins_check_foreign_constraint(
thr->lock_state = QUE_THR_LOCK_ROW;
check_table->inc_fk_checks();
lock_wait_suspend_thread(thr);
thr->lock_state = QUE_THR_LOCK_NOLOCK;
......@@ -1899,8 +1895,6 @@ row_ins_check_foreign_constraint(
} else {
err = DB_LOCK_WAIT;
}
check_table->dec_fk_checks();
}
exit_func:
......@@ -1931,7 +1925,6 @@ row_ins_check_foreign_constraints(
dict_foreign_t* foreign;
dberr_t err;
trx_t* trx;
ibool got_s_lock = FALSE;
DBUG_ASSERT(index->is_primary() == pk);
......@@ -1959,32 +1952,9 @@ row_ins_check_foreign_constraints(
FALSE, FALSE, DICT_ERR_IGNORE_NONE);
}
if (0 == trx->dict_operation_lock_mode) {
got_s_lock = TRUE;
row_mysql_freeze_data_dictionary(trx);
}
if (referenced_table) {
foreign->foreign_table->inc_fk_checks();
}
/* NOTE that if the thread ends up waiting for a lock
we will release dict_sys.latch temporarily!
But the counter on the table protects the referenced
table from being dropped while the check is running. */
err = row_ins_check_foreign_constraint(
TRUE, foreign, table, entry, thr);
if (referenced_table) {
foreign->foreign_table->dec_fk_checks();
}
if (got_s_lock) {
row_mysql_unfreeze_data_dictionary(trx);
}
if (ref_table != NULL) {
dict_table_close(ref_table, FALSE, FALSE);
}
......
......@@ -2867,7 +2867,6 @@ row_discard_tablespace_begin(
if (table) {
dict_stats_wait_bg_to_stop_using_table(table, trx);
ut_a(!is_system_tablespace(table->space_id));
ut_ad(!table->n_foreign_key_checks_running);
}
return(table);
......@@ -3102,8 +3101,6 @@ row_discard_tablespace_for_mysql(
err = DB_ERROR;
} else {
ut_ad(!table->n_foreign_key_checks_running);
bool fts_exist = (dict_table_has_fts_index(table)
|| DICT_TF2_FLAG_IS_SET(
table, DICT_TF2_FTS_HAS_DOC_ID));
......@@ -3462,16 +3459,14 @@ row_drop_table_for_mysql(
}
}
DBUG_EXECUTE_IF("row_drop_table_add_to_background", goto defer;);
/* TODO: could we replace the counter n_foreign_key_checks_running
with lock checks on the table? Acquire here an exclusive lock on the
table, and rewrite lock0lock.cc and the lock wait in srv0srv.cc so that
they can cope with the table having been dropped here? Foreign key
checks take an IS or IX lock on the table. */
DBUG_EXECUTE_IF("row_drop_table_add_to_background", goto dbug_defer;);
if (table->n_foreign_key_checks_running > 0) {
if (table->get_ref_count()) {
defer:
ut_ad(is_temp_name || strstr(table->name.m_name, "/FTS_"));
#ifndef DBUG_OFF
dbug_defer:
#endif
/* Rename #sql-backup to #sql-ib if table has open ref count
while dropping the table. This scenario can happen
when purge thread is waiting for dict_sys.mutex so
......@@ -3499,22 +3494,8 @@ row_drop_table_for_mysql(
goto funct_exit;
}
/* Remove all locks that are on the table or its records, if there
are no references to the table but it has record locks, we release
the record locks unconditionally. One use case is:
CREATE TABLE t2 (PRIMARY KEY (a)) SELECT * FROM t1;
If after the user transaction has done the SELECT and there is a
problem in completing the CREATE TABLE operation, MySQL will drop
the table. InnoDB will create a new background transaction to do the
actual drop, the trx instance that is passed to this function. To
preserve existing behaviour we remove the locks but ideally we
shouldn't have to. There should never be record locks on a table
that is going to be dropped. */
if (table->get_ref_count() > 0 || table->n_rec_locks > 0
|| lock_table_has_locks(table)) {
if (UNIV_UNLIKELY(table->n_rec_locks
|| lock_table_has_locks(table))) {
goto defer;
}
......@@ -4123,7 +4104,6 @@ row_rename_table_for_mysql(
ulint n_constraints_to_drop = 0;
ibool old_is_tmp, new_is_tmp;
pars_info_t* info = NULL;
int retry;
bool aux_fts_rename = false;
char* is_part = NULL;
......@@ -4235,23 +4215,6 @@ row_rename_table_for_mysql(
}
}
/* Is a foreign key check running on this table? */
for (retry = 0; retry < 100
&& table->n_foreign_key_checks_running > 0; ++retry) {
row_mysql_unlock_data_dictionary(trx);
os_thread_yield();
row_mysql_lock_data_dictionary(trx);
}
if (table->n_foreign_key_checks_running > 0) {
ib::error() << "In ALTER TABLE "
<< ut_get_name(trx, old_name)
<< " a FOREIGN KEY check is running. Cannot rename"
" table.";
err = DB_TABLE_IN_FK_CHECK;
goto funct_exit;
}
if (!table->is_temporary()) {
err = trx_undo_report_rename(trx, table);
......
......@@ -283,21 +283,9 @@ row_upd_check_references_constraints(
FALSE, FALSE, DICT_ERR_IGNORE_NONE);
}
if (foreign_table) {
foreign_table->inc_fk_checks();
}
/* NOTE that if the thread ends up waiting for a lock
we will release dict_sys.latch temporarily!
But the inc_fk_checks() protects foreign_table from
being dropped while the check is running. */
err = row_ins_check_foreign_constraint(
FALSE, foreign, table, entry, thr);
if (foreign_table) {
foreign_table->dec_fk_checks();
}
if (ref_table != NULL) {
dict_table_close(ref_table, FALSE, FALSE);
}
......
......@@ -436,8 +436,6 @@ ut_strerr(
return("End of index");
case DB_IO_ERROR:
return("I/O error");
case DB_TABLE_IN_FK_CHECK:
return("Table is being used in foreign key check");
case DB_NOT_FOUND:
return("not found");
case DB_ONLINE_LOG_TOO_BIG:
......
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