Browse Source

MDEV-36122: Protect table references with a lock

dict_table_open_on_id(): Simplify the logic.

dict_stats: A helper for acquiring MDL and opening the tables
mysql.innodb_table_stats and mysql.innodb_index_stats.

innodb_ft_aux_table_validate(): Contiguously hold dict_sys.latch
while accessing the table that we open with dict_table_open_on_name().

lock_table_children(): Do not hold a table reference while invoking
dict_acquire_mdl_shared<false>(), which may temporarily release and
reacquire the shared dict_sys.latch that we are holding.

prepare_inplace_alter_table_dict(): If an unexpected reference to the
table exists, wait for the purge subsystem to release its table handle,
similar to how we would do in case FULLTEXT INDEX existed. This function
is supposed to be protected by MDL_EXCLUSIVE on the table name.
If purge is going to access the table again later during is ALTER TABLE
operation, it will have access to an MDL compatible name for it and
therefore should conflict with any MDL_EXCLUSIVE that would cover
ha_innobase::commit_inplace_alter_table(commit=true).

ha_innobase::rename_table(): Before locking the data dictionary,
ensure that the purge subsystem is not holding a reference to the
table due to the lack of metadata locking, related to FULLTEXT INDEX
or the row-level undo logging of ALTER IGNORE TABLE.

ha_innobase::truncate(): Before locking the data dictionary,
ensure that the purge subsystem is not holding a reference to the
table due to insufficient metadata locking related to an earlier
ALTER IGNORE TABLE operation.

trx_purge_attach_undo_recs(), purge_sys_t::batch_cleanup():
Clear purge_sys.m_active only after all table handles have been released.

With these changes, no caller of dict_acquire_mdl_shared<false>
should be holding a table reference.

All remaining calls to dict_table_open_on_name(dict_locked=false)
except the one in fts_lock_table() and possibly in the DDL recovery
predicate innodb_check_version() should be protected by MDL, but there
currently is no assertion that would enforce this.

Reviewed by: Debarun Banerjee
pull/3889/head
Marko Mäkelä 8 months ago
parent
commit
d1a6792324
  1. 90
      storage/innobase/dict/dict0defrag_bg.cc
  2. 93
      storage/innobase/dict/dict0dict.cc
  3. 98
      storage/innobase/dict/dict0stats.cc
  4. 230
      storage/innobase/handler/ha_innodb.cc
  5. 85
      storage/innobase/handler/handler0alter.cc
  6. 21
      storage/innobase/include/dict0dict.h
  7. 5
      storage/innobase/lock/lock0lock.cc
  8. 4
      storage/innobase/trx/trx0purge.cc

90
storage/innobase/dict/dict0defrag_bg.cc

@ -217,47 +217,17 @@ dberr_t dict_stats_save_defrag_summary(dict_index_t *index, THD *thd)
if (index->is_ibuf())
return DB_SUCCESS;
MDL_ticket *mdl_table= nullptr, *mdl_index= nullptr;
dict_table_t *table_stats= dict_table_open_on_name(TABLE_STATS_NAME, false,
DICT_ERR_IGNORE_NONE);
if (table_stats)
{
dict_sys.freeze(SRW_LOCK_CALL);
table_stats= dict_acquire_mdl_shared<false>(table_stats, thd, &mdl_table);
dict_sys.unfreeze();
}
if (!table_stats || strcmp(table_stats->name.m_name, TABLE_STATS_NAME))
{
release_and_exit:
if (table_stats)
dict_table_close(table_stats, thd, mdl_table);
dict_stats stats;
if (stats.open(thd))
return DB_STATS_DO_NOT_EXIST;
}
dict_table_t *index_stats= dict_table_open_on_name(INDEX_STATS_NAME, false,
DICT_ERR_IGNORE_NONE);
if (index_stats)
{
dict_sys.freeze(SRW_LOCK_CALL);
index_stats= dict_acquire_mdl_shared<false>(index_stats, thd, &mdl_index);
dict_sys.unfreeze();
}
if (!index_stats)
goto release_and_exit;
if (strcmp(index_stats->name.m_name, INDEX_STATS_NAME))
{
dict_table_close(index_stats, thd, mdl_index);
goto release_and_exit;
}
trx_t *trx= trx_create();
trx->mysql_thd= thd;
trx_start_internal(trx);
dberr_t ret= trx->read_only
? DB_READ_ONLY
: lock_table_for_trx(table_stats, trx, LOCK_X);
: lock_table_for_trx(stats.table(), trx, LOCK_X);
if (ret == DB_SUCCESS)
ret= lock_table_for_trx(index_stats, trx, LOCK_X);
ret= lock_table_for_trx(stats.index(), trx, LOCK_X);
row_mysql_lock_data_dictionary(trx);
if (ret == DB_SUCCESS)
ret= dict_stats_save_index_stat(index, time(nullptr), "n_pages_freed",
@ -271,13 +241,9 @@ release_and_exit:
else
trx->rollback();
if (table_stats)
dict_table_close(table_stats, thd, mdl_table);
if (index_stats)
dict_table_close(index_stats, thd, mdl_index);
row_mysql_unlock_data_dictionary(trx);
trx->free();
stats.close();
return ret;
}
@ -353,49 +319,18 @@ dict_stats_save_defrag_stats(
if (!n_leaf_reserved)
return DB_SUCCESS;
THD *thd= current_thd;
MDL_ticket *mdl_table= nullptr, *mdl_index= nullptr;
dict_table_t* table_stats= dict_table_open_on_name(TABLE_STATS_NAME, false,
DICT_ERR_IGNORE_NONE);
if (table_stats)
{
dict_sys.freeze(SRW_LOCK_CALL);
table_stats= dict_acquire_mdl_shared<false>(table_stats, thd, &mdl_table);
dict_sys.unfreeze();
}
if (!table_stats || strcmp(table_stats->name.m_name, TABLE_STATS_NAME))
{
release_and_exit:
if (table_stats)
dict_table_close(table_stats, thd, mdl_table);
THD *const thd= current_thd;
dict_stats stats;
if (stats.open(thd))
return DB_STATS_DO_NOT_EXIST;
}
dict_table_t *index_stats= dict_table_open_on_name(INDEX_STATS_NAME, false,
DICT_ERR_IGNORE_NONE);
if (index_stats)
{
dict_sys.freeze(SRW_LOCK_CALL);
index_stats= dict_acquire_mdl_shared<false>(index_stats, thd, &mdl_index);
dict_sys.unfreeze();
}
if (!index_stats)
goto release_and_exit;
if (strcmp(index_stats->name.m_name, INDEX_STATS_NAME))
{
dict_table_close(index_stats, thd, mdl_index);
goto release_and_exit;
}
trx_t *trx= trx_create();
trx->mysql_thd= thd;
trx_start_internal(trx);
dberr_t ret= trx->read_only
? DB_READ_ONLY
: lock_table_for_trx(table_stats, trx, LOCK_X);
: lock_table_for_trx(stats.table(), trx, LOCK_X);
if (ret == DB_SUCCESS)
ret= lock_table_for_trx(index_stats, trx, LOCK_X);
ret= lock_table_for_trx(stats.index(), trx, LOCK_X);
row_mysql_lock_data_dictionary(trx);
@ -423,12 +358,9 @@ release_and_exit:
else
trx->rollback();
if (table_stats)
dict_table_close(table_stats, thd, mdl_table);
if (index_stats)
dict_table_close(index_stats, thd, mdl_index);
row_mysql_unlock_data_dictionary(trx);
trx->free();
stats.close();
return ret;
}

93
storage/innobase/dict/dict0dict.cc

@ -647,12 +647,9 @@ dict_acquire_mdl_shared(dict_table_t *table,
retry:
ut_ad(!trylock == dict_sys.frozen());
ut_ad(trylock || table->get_ref_count());
if (!table->is_readable() || table->corrupted)
{
if (!trylock)
table->release();
if (*mdl)
{
mdl_context->release_lock(*mdl);
@ -664,10 +661,7 @@ retry:
const table_id_t table_id{table->id};
if (!trylock)
{
table->release();
dict_sys.unfreeze();
}
{
MDL_request request;
@ -714,7 +708,8 @@ lookup:
return table;
}
table->acquire();
if (trylock)
table->acquire();
if (!table->parse_name<true>(db_buf1, tbl_buf1, &db1_len, &tbl1_len))
{
@ -828,6 +823,7 @@ dict_table_t *dict_table_open_on_id(table_id_t table_id, bool dict_locked,
dict_table_op_t table_op, THD *thd,
MDL_ticket **mdl)
{
retry:
if (!dict_locked)
dict_sys.freeze(SRW_LOCK_CALL);
@ -835,9 +831,21 @@ dict_table_t *dict_table_open_on_id(table_id_t table_id, bool dict_locked,
if (table)
{
table->acquire();
if (thd && !dict_locked)
table= dict_acquire_mdl_shared<false>(table, thd, mdl, table_op);
if (!dict_locked)
{
if (thd)
{
table= dict_acquire_mdl_shared<false>(table, thd, mdl, table_op);
if (table)
goto acquire;
}
else
acquire:
table->acquire();
dict_sys.unfreeze();
}
else
table->acquire();
}
else if (table_op != DICT_TABLE_OP_OPEN_ONLY_IF_CACHED)
{
@ -850,24 +858,16 @@ dict_table_t *dict_table_open_on_id(table_id_t table_id, bool dict_locked,
table_op == DICT_TABLE_OP_LOAD_TABLESPACE
? DICT_ERR_IGNORE_RECOVER_LOCK
: DICT_ERR_IGNORE_FK_NOKEY);
if (table)
table->acquire();
if (!dict_locked)
{
dict_sys.unlock();
if (table && thd)
{
dict_sys.freeze(SRW_LOCK_CALL);
table= dict_acquire_mdl_shared<false>(table, thd, mdl, table_op);
dict_sys.unfreeze();
}
return table;
if (table)
goto retry;
}
else if (table)
table->acquire();
}
if (!dict_locked)
dict_sys.unfreeze();
return table;
}
@ -1084,6 +1084,55 @@ dict_table_open_on_name(
DBUG_RETURN(table);
}
bool dict_stats::open(THD *thd) noexcept
{
ut_ad(!mdl_table);
ut_ad(!mdl_index);
ut_ad(!table_stats);
ut_ad(!index_stats);
ut_ad(!mdl_context);
mdl_context= static_cast<MDL_context*>(thd_mdl_context(thd));
if (!mdl_context)
return true;
/* FIXME: use compatible type, and maybe remove this parameter altogether! */
const double timeout= double(global_system_variables.lock_wait_timeout);
MDL_request request;
MDL_REQUEST_INIT(&request, MDL_key::TABLE, "mysql", "innodb_table_stats",
MDL_SHARED, MDL_EXPLICIT);
if (UNIV_UNLIKELY(mdl_context->acquire_lock(&request, timeout)))
return true;
mdl_table= request.ticket;
MDL_REQUEST_INIT(&request, MDL_key::TABLE, "mysql", "innodb_index_stats",
MDL_SHARED, MDL_EXPLICIT);
if (UNIV_UNLIKELY(mdl_context->acquire_lock(&request, timeout)))
goto release_mdl;
mdl_index= request.ticket;
table_stats= dict_table_open_on_name("mysql/innodb_table_stats", false,
DICT_ERR_IGNORE_NONE);
if (!table_stats)
goto release_mdl;
index_stats= dict_table_open_on_name("mysql/innodb_index_stats", false,
DICT_ERR_IGNORE_NONE);
if (index_stats)
return false;
table_stats->release();
release_mdl:
if (mdl_index)
mdl_context->release_lock(mdl_index);
mdl_context->release_lock(mdl_table);
return true;
}
void dict_stats::close() noexcept
{
table_stats->release();
index_stats->release();
mdl_context->release_lock(mdl_table);
mdl_context->release_lock(mdl_index);
}
/**********************************************************************//**
Adds system columns to a table object. */
void

98
storage/innobase/dict/dict0stats.cc

@ -2879,11 +2879,12 @@ dberr_t dict_stats_save(dict_table_t* table, index_id_t index_id)
pars_info_t* pinfo;
char db_utf8[MAX_DB_UTF8_LEN];
char table_utf8[MAX_TABLE_UTF8_LEN];
THD* const thd = current_thd;
#ifdef ENABLED_DEBUG_SYNC
DBUG_EXECUTE_IF("dict_stats_save_exit_notify",
SCOPE_EXIT([] {
debug_sync_set_action(current_thd,
SCOPE_EXIT([thd] {
debug_sync_set_action(thd,
STRING_WITH_LEN("now SIGNAL dict_stats_save_finished"));
});
);
@ -2904,41 +2905,10 @@ dberr_t dict_stats_save(dict_table_t* table, index_id_t index_id)
return (dict_stats_report_error(table));
}
THD* thd = current_thd;
MDL_ticket *mdl_table = nullptr, *mdl_index = nullptr;
dict_table_t* table_stats = dict_table_open_on_name(
TABLE_STATS_NAME, false, DICT_ERR_IGNORE_NONE);
if (table_stats) {
dict_sys.freeze(SRW_LOCK_CALL);
table_stats = dict_acquire_mdl_shared<false>(table_stats, thd,
&mdl_table);
dict_sys.unfreeze();
}
if (!table_stats
|| strcmp(table_stats->name.m_name, TABLE_STATS_NAME)) {
release_and_exit:
if (table_stats) {
dict_table_close(table_stats, thd, mdl_table);
}
dict_stats stats;
if (stats.open(thd)) {
return DB_STATS_DO_NOT_EXIST;
}
dict_table_t* index_stats = dict_table_open_on_name(
INDEX_STATS_NAME, false, DICT_ERR_IGNORE_NONE);
if (index_stats) {
dict_sys.freeze(SRW_LOCK_CALL);
index_stats = dict_acquire_mdl_shared<false>(index_stats, thd,
&mdl_index);
dict_sys.unfreeze();
}
if (!index_stats) {
goto release_and_exit;
}
if (strcmp(index_stats->name.m_name, INDEX_STATS_NAME)) {
dict_table_close(index_stats, thd, mdl_index);
goto release_and_exit;
}
dict_fs2utf8(table->name.m_name, db_utf8, sizeof(db_utf8),
table_utf8, sizeof(table_utf8));
const time_t now = time(NULL);
@ -2947,9 +2917,9 @@ release_and_exit:
trx_start_internal(trx);
dberr_t ret = trx->read_only
? DB_READ_ONLY
: lock_table_for_trx(table_stats, trx, LOCK_X);
: lock_table_for_trx(stats.table(), trx, LOCK_X);
if (ret == DB_SUCCESS) {
ret = lock_table_for_trx(index_stats, trx, LOCK_X);
ret = lock_table_for_trx(stats.index(), trx, LOCK_X);
}
if (ret != DB_SUCCESS) {
if (trx->state != TRX_STATE_NOT_STARTED) {
@ -3010,8 +2980,7 @@ free_and_exit:
dict_sys.unlock();
unlocked_free_and_exit:
trx->free();
dict_table_close(table_stats, thd, mdl_table);
dict_table_close(index_stats, thd, mdl_index);
stats.close();
return ret;
}
@ -3486,43 +3455,12 @@ dberr_t dict_stats_fetch_from_ps(dict_table_t *table)
stats. */
dict_stats_empty_table(table, true);
THD* thd = current_thd;
MDL_ticket *mdl_table = nullptr, *mdl_index = nullptr;
dict_table_t* table_stats = dict_table_open_on_name(
TABLE_STATS_NAME, false, DICT_ERR_IGNORE_NONE);
if (!table_stats) {
return DB_STATS_DO_NOT_EXIST;
}
dict_table_t* index_stats = dict_table_open_on_name(
INDEX_STATS_NAME, false, DICT_ERR_IGNORE_NONE);
if (!index_stats) {
table_stats->release();
return DB_STATS_DO_NOT_EXIST;
}
dict_sys.freeze(SRW_LOCK_CALL);
table_stats = dict_acquire_mdl_shared<false>(table_stats, thd,
&mdl_table);
if (!table_stats
|| strcmp(table_stats->name.m_name, TABLE_STATS_NAME)) {
release_and_exit:
dict_sys.unfreeze();
if (table_stats) {
dict_table_close(table_stats, thd, mdl_table);
}
if (index_stats) {
dict_table_close(index_stats, thd, mdl_index);
}
THD* const thd = current_thd;
dict_stats stats;
if (stats.open(thd)) {
return DB_STATS_DO_NOT_EXIST;
}
index_stats = dict_acquire_mdl_shared<false>(index_stats, thd,
&mdl_index);
if (!index_stats
|| strcmp(index_stats->name.m_name, INDEX_STATS_NAME)) {
goto release_and_exit;
}
#ifdef ENABLED_DEBUG_SYNC
DEBUG_SYNC(thd, "dict_stats_mdl_acquired");
#endif /* ENABLED_DEBUG_SYNC */
@ -3547,7 +3485,6 @@ release_and_exit:
"fetch_index_stats_step",
dict_stats_fetch_index_stats_step,
&index_fetch_arg);
dict_sys.unfreeze();
dict_sys.lock(SRW_LOCK_CALL);
que_t* graph = pars_sql(
pinfo,
@ -3613,18 +3550,11 @@ release_and_exit:
trx_start_internal_read_only(trx);
que_run_threads(que_fork_start_command(graph));
que_graph_free(graph);
dict_table_close(table_stats, thd, mdl_table);
dict_table_close(index_stats, thd, mdl_index);
trx_commit_for_mysql(trx);
dberr_t ret = trx->error_state;
dberr_t ret = index_fetch_arg.stats_were_modified
? trx->error_state : DB_STATS_DO_NOT_EXIST;
trx->free();
if (!index_fetch_arg.stats_were_modified) {
return DB_STATS_DO_NOT_EXIST;
}
stats.close();
return ret;
}

230
storage/innobase/handler/ha_innodb.cc

@ -1330,38 +1330,17 @@ static void innodb_drop_database(handlerton*, char *path)
dict_sys.unlock();
dict_table_t *table_stats, *index_stats;
MDL_ticket *mdl_table= nullptr, *mdl_index= nullptr;
table_stats= dict_table_open_on_name(TABLE_STATS_NAME, false,
DICT_ERR_IGNORE_NONE);
if (table_stats)
{
dict_sys.freeze(SRW_LOCK_CALL);
table_stats= dict_acquire_mdl_shared<false>(table_stats,
thd, &mdl_table);
dict_sys.unfreeze();
}
index_stats= dict_table_open_on_name(INDEX_STATS_NAME, false,
DICT_ERR_IGNORE_NONE);
if (index_stats)
{
dict_sys.freeze(SRW_LOCK_CALL);
index_stats= dict_acquire_mdl_shared<false>(index_stats,
thd, &mdl_index);
dict_sys.unfreeze();
}
dict_stats stats;
const bool stats_failed{stats.open(thd)};
trx_start_for_ddl(trx);
uint errors= 0;
char db[NAME_LEN + 1];
strconvert(&my_charset_filename, namebuf, len, system_charset_info, db,
sizeof db, &errors);
if (!errors && table_stats && index_stats &&
!strcmp(table_stats->name.m_name, TABLE_STATS_NAME) &&
!strcmp(index_stats->name.m_name, INDEX_STATS_NAME) &&
lock_table_for_trx(table_stats, trx, LOCK_X) == DB_SUCCESS &&
lock_table_for_trx(index_stats, trx, LOCK_X) == DB_SUCCESS)
if (!errors && !stats_failed &&
lock_table_for_trx(stats.table(), trx, LOCK_X) == DB_SUCCESS &&
lock_table_for_trx(stats.index(), trx, LOCK_X) == DB_SUCCESS)
{
row_mysql_lock_data_dictionary(trx);
if (dict_stats_delete(db, trx))
@ -1457,19 +1436,16 @@ static void innodb_drop_database(handlerton*, char *path)
if (err != DB_SUCCESS)
{
trx->rollback();
namebuf[len] = '\0';
ib::error() << "DROP DATABASE " << namebuf << ": " << err;
sql_print_error("InnoDB: DROP DATABASE %.*s: %s",
int(len), namebuf, ut_strerr(err));
}
else
trx->commit();
if (table_stats)
dict_table_close(table_stats, thd, mdl_table);
if (index_stats)
dict_table_close(index_stats, thd, mdl_index);
row_mysql_unlock_data_dictionary(trx);
trx->free();
if (!stats_failed)
stats.close();
if (err == DB_SUCCESS)
{
@ -13680,8 +13656,6 @@ int ha_innobase::delete_table(const char *name)
err= lock_table_children(table, trx);
}
dict_table_t *table_stats= nullptr, *index_stats= nullptr;
MDL_ticket *mdl_table= nullptr, *mdl_index= nullptr;
if (err == DB_SUCCESS)
err= lock_table_for_trx(table, trx, LOCK_X);
@ -13720,37 +13694,18 @@ int ha_innobase::delete_table(const char *name)
#endif
DEBUG_SYNC(thd, "before_delete_table_stats");
dict_stats stats;
bool stats_failed= true;
if (err == DB_SUCCESS && table->stats_is_persistent() &&
!table->is_stats_table())
{
table_stats= dict_table_open_on_name(TABLE_STATS_NAME, false,
DICT_ERR_IGNORE_NONE);
if (table_stats)
{
dict_sys.freeze(SRW_LOCK_CALL);
table_stats= dict_acquire_mdl_shared<false>(table_stats,
thd, &mdl_table);
dict_sys.unfreeze();
}
index_stats= dict_table_open_on_name(INDEX_STATS_NAME, false,
DICT_ERR_IGNORE_NONE);
if (index_stats)
{
dict_sys.freeze(SRW_LOCK_CALL);
index_stats= dict_acquire_mdl_shared<false>(index_stats,
thd, &mdl_index);
dict_sys.unfreeze();
}
stats_failed= stats.open(thd);
const bool skip_wait{table->name.is_temporary()};
if (table_stats && index_stats &&
!strcmp(table_stats->name.m_name, TABLE_STATS_NAME) &&
!strcmp(index_stats->name.m_name, INDEX_STATS_NAME) &&
!(err= lock_table_for_trx(table_stats, trx, LOCK_X, skip_wait)))
err= lock_table_for_trx(index_stats, trx, LOCK_X, skip_wait);
if (!stats_failed &&
!(err= lock_table_for_trx(stats.table(), trx, LOCK_X, skip_wait)))
err= lock_table_for_trx(stats.index(), trx, LOCK_X, skip_wait);
if (err != DB_SUCCESS && skip_wait)
{
@ -13759,10 +13714,8 @@ int ha_innobase::delete_table(const char *name)
ut_ad(err == DB_LOCK_WAIT);
ut_ad(trx->error_state == DB_SUCCESS);
err= DB_SUCCESS;
dict_table_close(table_stats, thd, mdl_table);
dict_table_close(index_stats, thd, mdl_index);
table_stats= nullptr;
index_stats= nullptr;
stats.close();
stats_failed= true;
}
}
@ -13833,13 +13786,11 @@ err_exit:
else if (rollback_add_partition)
purge_sys.resume_FTS();
#endif
if (table_stats)
dict_table_close(table_stats, thd, mdl_table);
if (index_stats)
dict_table_close(index_stats, thd, mdl_index);
row_mysql_unlock_data_dictionary(trx);
if (trx != parent_trx)
trx->free();
if (!stats_failed)
stats.close();
DBUG_RETURN(convert_error_code_to_mysql(err, 0, NULL));
}
@ -13854,7 +13805,7 @@ err_exit:
err= trx->drop_table_foreign(table->name);
}
if (err == DB_SUCCESS && table_stats && index_stats)
if (err == DB_SUCCESS && !stats_failed)
err= trx->drop_table_statistics(table->name);
if (err != DB_SUCCESS)
goto err_exit;
@ -13865,11 +13816,9 @@ err_exit:
std::vector<pfs_os_file_t> deleted;
trx->commit(deleted);
if (table_stats)
dict_table_close(table_stats, thd, mdl_table);
if (index_stats)
dict_table_close(index_stats, thd, mdl_index);
row_mysql_unlock_data_dictionary(trx);
if (!stats_failed)
stats.close();
for (pfs_os_file_t d : deleted)
os_file_close(d);
log_write_up_to(trx->commit_lsn, true);
@ -14065,9 +14014,6 @@ int ha_innobase::truncate()
ib_table->name.m_name, ib_table->id);
const char *name= mem_heap_strdup(heap, ib_table->name.m_name);
dict_table_t *table_stats = nullptr, *index_stats = nullptr;
MDL_ticket *mdl_table = nullptr, *mdl_index = nullptr;
dberr_t error= lock_table_children(ib_table, trx);
if (error == DB_SUCCESS)
@ -14075,6 +14021,7 @@ int ha_innobase::truncate()
const bool fts= error == DB_SUCCESS &&
ib_table->flags2 & (DICT_TF2_FTS_HAS_DOC_ID | DICT_TF2_FTS);
const bool pause_purge= error == DB_SUCCESS && ib_table->get_ref_count() > 1;
if (fts)
{
@ -14082,6 +14029,8 @@ int ha_innobase::truncate()
purge_sys.stop_FTS(*ib_table);
error= fts_lock_tables(trx, *ib_table);
}
else if (pause_purge)
purge_sys.stop_FTS();
if (error == DB_SUCCESS)
{
@ -14097,33 +14046,16 @@ int ha_innobase::truncate()
}
}
dict_stats stats;
bool stats_failed= true;
if (error == DB_SUCCESS && ib_table->stats_is_persistent() &&
!ib_table->is_stats_table())
{
table_stats= dict_table_open_on_name(TABLE_STATS_NAME, false,
DICT_ERR_IGNORE_NONE);
if (table_stats)
{
dict_sys.freeze(SRW_LOCK_CALL);
table_stats= dict_acquire_mdl_shared<false>(table_stats, m_user_thd,
&mdl_table);
dict_sys.unfreeze();
}
index_stats= dict_table_open_on_name(INDEX_STATS_NAME, false,
DICT_ERR_IGNORE_NONE);
if (index_stats)
{
dict_sys.freeze(SRW_LOCK_CALL);
index_stats= dict_acquire_mdl_shared<false>(index_stats, m_user_thd,
&mdl_index);
dict_sys.unfreeze();
}
if (table_stats && index_stats &&
!strcmp(table_stats->name.m_name, TABLE_STATS_NAME) &&
!strcmp(index_stats->name.m_name, INDEX_STATS_NAME) &&
!(error= lock_table_for_trx(table_stats, trx, LOCK_X)))
error= lock_table_for_trx(index_stats, trx, LOCK_X);
stats_failed= stats.open(m_user_thd);
if (!stats_failed &&
!(error= lock_table_for_trx(stats.table(), trx, LOCK_X)))
error= lock_table_for_trx(stats.index(), trx, LOCK_X);
}
if (error == DB_SUCCESS)
@ -14215,14 +14147,9 @@ int ha_innobase::truncate()
}
trx->free();
if (!stats_failed)
stats.close();
mem_heap_free(heap);
if (table_stats)
dict_table_close(table_stats, m_user_thd, mdl_table);
if (index_stats)
dict_table_close(index_stats, m_user_thd, mdl_index);
DBUG_RETURN(err);
}
@ -14284,8 +14211,6 @@ ha_innobase::rename_table(
trx_t* trx = innobase_trx_allocate(thd);
trx_start_for_ddl(trx);
dict_table_t *table_stats = nullptr, *index_stats = nullptr;
MDL_ticket *mdl_table = nullptr, *mdl_index = nullptr;
char norm_from[MAX_FULL_NAME_LEN];
char norm_to[MAX_FULL_NAME_LEN];
@ -14296,6 +14221,7 @@ ha_innobase::rename_table(
const bool from_temp = dict_table_t::is_temporary_name(norm_from);
dict_table_t* t;
bool pause_purge = false, fts_exist = false;
if (from_temp) {
/* There is no need to lock any FOREIGN KEY child tables. */
@ -14308,37 +14234,35 @@ ha_innobase::rename_table(
if (error == DB_SUCCESS) {
error = lock_table_for_trx(t, trx, LOCK_X);
}
fts_exist = error == DB_SUCCESS && t->flags2
& (DICT_TF2_FTS_HAS_DOC_ID | DICT_TF2_FTS);
pause_purge = error == DB_SUCCESS
&& t->get_ref_count() > 1;
if (fts_exist) {
fts_optimize_remove_table(t);
purge_sys.stop_FTS(*t);
if (error == DB_SUCCESS) {
error = fts_lock_tables(trx, *t);
}
} else if (pause_purge) {
purge_sys.stop_FTS();
}
}
}
dict_stats stats;
bool stats_fail = true;
if (strcmp(norm_from, TABLE_STATS_NAME)
&& strcmp(norm_from, INDEX_STATS_NAME)
&& strcmp(norm_to, TABLE_STATS_NAME)
&& strcmp(norm_to, INDEX_STATS_NAME)) {
table_stats = dict_table_open_on_name(TABLE_STATS_NAME, false,
DICT_ERR_IGNORE_NONE);
if (table_stats) {
dict_sys.freeze(SRW_LOCK_CALL);
table_stats = dict_acquire_mdl_shared<false>(
table_stats, thd, &mdl_table);
dict_sys.unfreeze();
}
index_stats = dict_table_open_on_name(INDEX_STATS_NAME, false,
DICT_ERR_IGNORE_NONE);
if (index_stats) {
dict_sys.freeze(SRW_LOCK_CALL);
index_stats = dict_acquire_mdl_shared<false>(
index_stats, thd, &mdl_index);
dict_sys.unfreeze();
}
if (error == DB_SUCCESS && table_stats && index_stats
&& !strcmp(table_stats->name.m_name, TABLE_STATS_NAME)
&& !strcmp(index_stats->name.m_name, INDEX_STATS_NAME)) {
error = lock_table_for_trx(table_stats, trx, LOCK_X,
from_temp);
stats_fail = stats.open(thd);
if (!stats_fail && error == DB_SUCCESS) {
error = lock_table_for_trx(stats.table(), trx,
LOCK_X, from_temp);
if (error == DB_SUCCESS) {
error = lock_table_for_trx(index_stats, trx,
error = lock_table_for_trx(stats.index(), trx,
LOCK_X, from_temp);
}
if (error != DB_SUCCESS && from_temp) {
@ -14349,10 +14273,8 @@ ha_innobase::rename_table(
we cannot lock the tables, when the
table is being renamed from from a
temporary name. */
dict_table_close(table_stats, thd, mdl_table);
dict_table_close(index_stats, thd, mdl_index);
table_stats = nullptr;
index_stats = nullptr;
stats.close();
stats_fail = true;
}
}
}
@ -14379,7 +14301,7 @@ ha_innobase::rename_table(
DEBUG_SYNC(thd, "after_innobase_rename_table");
if (error == DB_SUCCESS && table_stats && index_stats) {
if (error == DB_SUCCESS && !stats_fail) {
error = dict_stats_rename_table(norm_from, norm_to, trx);
if (error == DB_DUPLICATE_KEY) {
/* The duplicate may also occur in
@ -14393,7 +14315,10 @@ ha_innobase::rename_table(
if (error == DB_SUCCESS) {
trx->flush_log_later = true;
if (t) {
ut_ad(dict_sys.frozen());
ut_ad(dict_sys.locked());
if (fts_exist) {
fts_optimize_add_table(t);
}
if (UNIV_LIKELY(t->release())) {
stats_deinit(t);
} else {
@ -14403,23 +14328,28 @@ ha_innobase::rename_table(
innobase_commit_low(trx);
} else {
if (t) {
if (fts_exist) {
fts_optimize_add_table(t);
}
t->release();
}
trx->rollback();
}
if (table_stats) {
dict_table_close(table_stats, thd, mdl_table);
}
if (index_stats) {
dict_table_close(index_stats, thd, mdl_index);
}
row_mysql_unlock_data_dictionary(trx);
if (fts_exist || pause_purge) {
purge_sys.resume_FTS();
}
if (error == DB_SUCCESS) {
log_write_up_to(trx->commit_lsn, true);
}
trx->flush_log_later = false;
trx->free();
if (!stats_fail) {
stats.close();
}
if (error == DB_DUPLICATE_KEY) {
/* We are not able to deal with handler::get_dup_key()
@ -17714,11 +17644,16 @@ static int innodb_ft_aux_table_validate(THD *thd, st_mysql_sys_var*,
int len = sizeof buf;
if (const char* table_name = value->val_str(value, buf, &len)) {
/* Because we are not acquiring MDL on the table name,
we must contiguously hold dict_sys.latch while we are
examining the table, to protect us against concurrent DDL. */
dict_sys.lock(SRW_LOCK_CALL);
if (dict_table_t* table = dict_table_open_on_name(
table_name, false, DICT_ERR_IGNORE_NONE)) {
table_name, true, DICT_ERR_IGNORE_NONE)) {
table->release();
const table_id_t id = dict_table_has_fts_index(table)
? table->id : 0;
table->release();
dict_sys.unlock();
if (id) {
innodb_ft_aux_table_id = id;
if (table_name == buf) {
@ -17729,12 +17664,11 @@ static int innodb_ft_aux_table_validate(THD *thd, st_mysql_sys_var*,
len);
}
*static_cast<const char**>(save) = table_name;
return 0;
}
}
dict_sys.unlock();
return 1;
} else {
*static_cast<char**>(save) = NULL;

85
storage/innobase/handler/handler0alter.cc

@ -6611,8 +6611,9 @@ prepare_inplace_alter_table_dict(
mem_heap_alloc(ctx->heap, ctx->num_to_add_index
* sizeof *ctx->add_key_numbers));
const bool fts_exist = ctx->new_table->flags2
const bool have_fts = user_table->flags2
& (DICT_TF2_FTS_HAS_DOC_ID | DICT_TF2_FTS);
const bool pause_purge = have_fts || user_table->get_ref_count() > 1;
/* Acquire a lock on the table before creating any indexes. */
bool table_lock_failed = false;
@ -6639,13 +6640,18 @@ acquire_lock:
user_table->lock_shared_unlock();
}
if (fts_exist) {
purge_sys.stop_FTS(*ctx->new_table);
if (pause_purge) {
purge_sys.stop_FTS();
if (have_fts) {
purge_sys.stop_FTS(*user_table, true);
}
if (error == DB_SUCCESS) {
error = fts_lock_tables(ctx->trx, *ctx->new_table);
error = fts_lock_tables(ctx->trx, *user_table);
}
}
ut_ad(user_table->get_ref_count() == 1);
if (error == DB_SUCCESS) {
error = lock_sys_tables(ctx->trx);
}
@ -7478,7 +7484,7 @@ error_handling_drop_uncached:
/* fts_create_common_tables() may drop old common tables,
whose files would be deleted here. */
commit_unlock_and_unlink(ctx->trx);
if (fts_exist) {
if (pause_purge) {
purge_sys.resume_FTS();
}
@ -7575,7 +7581,7 @@ err_exit:
ctx->trx->free();
}
trx_commit_for_mysql(ctx->prebuilt->trx);
if (fts_exist) {
if (pause_purge) {
purge_sys.resume_FTS();
}
@ -11572,34 +11578,16 @@ err_index:
}
}
dict_table_t *table_stats = nullptr, *index_stats = nullptr;
MDL_ticket *mdl_table = nullptr, *mdl_index = nullptr;
dict_stats stats;
bool stats_failed = true;
dberr_t error = DB_SUCCESS;
if (!ctx0->old_table->is_stats_table() &&
!ctx0->new_table->is_stats_table()) {
table_stats = dict_table_open_on_name(
TABLE_STATS_NAME, false, DICT_ERR_IGNORE_NONE);
if (table_stats) {
dict_sys.freeze(SRW_LOCK_CALL);
table_stats = dict_acquire_mdl_shared<false>(
table_stats, m_user_thd, &mdl_table);
dict_sys.unfreeze();
}
index_stats = dict_table_open_on_name(
INDEX_STATS_NAME, false, DICT_ERR_IGNORE_NONE);
if (index_stats) {
dict_sys.freeze(SRW_LOCK_CALL);
index_stats = dict_acquire_mdl_shared<false>(
index_stats, m_user_thd, &mdl_index);
dict_sys.unfreeze();
}
if (table_stats && index_stats
&& !strcmp(table_stats->name.m_name, TABLE_STATS_NAME)
&& !strcmp(index_stats->name.m_name, INDEX_STATS_NAME)
&& !(error = lock_table_for_trx(table_stats,
stats_failed = stats.open(m_user_thd);
if (!stats_failed
&& !(error = lock_table_for_trx(stats.table(),
trx, LOCK_X))) {
error = lock_table_for_trx(index_stats, trx, LOCK_X);
error = lock_table_for_trx(stats.index(), trx, LOCK_X);
}
}
@ -11613,13 +11601,9 @@ err_index:
error = lock_sys_tables(trx);
}
if (error != DB_SUCCESS) {
if (table_stats) {
dict_table_close(table_stats, m_user_thd, mdl_table);
}
if (index_stats) {
dict_table_close(index_stats, m_user_thd, mdl_index);
if (!stats_failed) {
stats.close();
}
my_error_innodb(error, table_share->table_name.str, 0);
if (fts_exist) {
purge_sys.resume_FTS();
}
@ -11635,6 +11619,7 @@ err_index:
trx_start_for_ddl(trx);
}
my_error_innodb(error, table_share->table_name.str, 0);
DBUG_RETURN(true);
}
@ -11652,15 +11637,10 @@ err_index:
fail:
trx->rollback();
ut_ad(!trx->fts_trx);
if (table_stats) {
dict_table_close(table_stats, m_user_thd,
mdl_table);
}
if (index_stats) {
dict_table_close(index_stats, m_user_thd,
mdl_index);
}
row_mysql_unlock_data_dictionary(trx);
if (!stats_failed) {
stats.close();
}
if (fts_exist) {
purge_sys.resume_FTS();
}
@ -11680,14 +11660,14 @@ fail:
if (commit_try_rebuild(ha_alter_info, ctx,
altered_table, table,
table_stats && index_stats,
!stats_failed,
trx,
table_share->table_name.str)) {
goto fail;
}
} else if (commit_try_norebuild(ha_alter_info, ctx,
altered_table, table,
table_stats && index_stats,
!stats_failed,
trx,
table_share->table_name.str)) {
goto fail;
@ -11710,13 +11690,6 @@ fail:
#endif
}
if (table_stats) {
dict_table_close(table_stats, m_user_thd, mdl_table);
}
if (index_stats) {
dict_table_close(index_stats, m_user_thd, mdl_index);
}
/* Commit or roll back the changes to the data dictionary. */
DEBUG_SYNC(m_user_thd, "innodb_alter_inplace_before_commit");
@ -11865,6 +11838,9 @@ foreign_fail:
DBUG_EXECUTE_IF("innodb_alter_commit_crash_after_commit",
DBUG_SUICIDE(););
trx->free();
if (!stats_failed) {
stats.close();
}
if (fts_exist) {
purge_sys.resume_FTS();
}
@ -11921,6 +11897,9 @@ foreign_fail:
DBUG_EXECUTE_IF("innodb_alter_commit_crash_after_commit",
DBUG_SUICIDE(););
trx->free();
if (!stats_failed) {
stats.close();
}
if (fts_exist) {
purge_sys.resume_FTS();
}

21
storage/innobase/include/dict0dict.h

@ -1657,6 +1657,27 @@ bool
dict_table_have_virtual_index(
dict_table_t* table);
/** Helper for opening the InnoDB persistent statistics tables */
class dict_stats final
{
MDL_context *mdl_context= nullptr;
MDL_ticket *mdl_table= nullptr, *mdl_index= nullptr;
dict_table_t *table_stats= nullptr, *index_stats= nullptr;
public:
dict_stats()= default;
/** Open the statistics tables.
@return whether the operation failed */
bool open(THD *thd) noexcept;
/** Close the statistics tables after !open_tables(thd). */
void close() noexcept;
dict_table_t *table() const noexcept { return table_stats; }
dict_table_t *index() const noexcept { return index_stats; }
};
#include "dict0dict.inl"
#endif

5
storage/innobase/lock/lock0lock.cc

@ -4140,13 +4140,12 @@ dberr_t lock_table_children(dict_table_t *table, trx_t *trx)
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();
if (mdl)
child->acquire();
children.emplace_back(table_mdl{child, mdl});
goto rescan;
}

4
storage/innobase/trx/trx0purge.cc

@ -1293,8 +1293,6 @@ static purge_sys_t::iterator trx_purge_attach_undo_recs(THD *thd,
break;
}
purge_sys.m_active= false;
#ifdef UNIV_DEBUG
thr= UT_LIST_GET_FIRST(purge_sys.query->thrs);
for (ulint i= 0; thr && i < *n_work_items;
@ -1343,6 +1341,8 @@ static void trx_purge_wait_for_workers_to_complete()
TRANSACTIONAL_INLINE
void purge_sys_t::batch_cleanup(const purge_sys_t::iterator &head)
{
m_active= false;
/* Release the undo pages. */
for (auto p : pages)
p.second->unfix();

Loading…
Cancel
Save