mirror of
https://github.com/MariaDB/server.git
synced 2025-02-04 21:02:17 +01:00
298 lines
10 KiB
C++
298 lines
10 KiB
C++
/*****************************************************************************
|
|
|
|
Copyright (c) 2021, 2022, 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 the Free Software
|
|
Foundation; version 2 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful, but WITHOUT
|
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along with
|
|
this program; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
|
|
|
|
*****************************************************************************/
|
|
|
|
/**
|
|
@file dict/drop.cc
|
|
Data Dictionary Language operations that delete .ibd files */
|
|
|
|
/* We implement atomic data dictionary operations as follows.
|
|
|
|
1. A data dictionary transaction is started.
|
|
2. We acquire exclusive lock on all the tables that are to be dropped
|
|
during the execution of the transaction.
|
|
3. We lock the data dictionary cache.
|
|
4. All metadata tables will be updated within the single DDL transaction,
|
|
including deleting or renaming InnoDB persistent statistics.
|
|
4b. If any lock wait would occur while we are holding the dict_sys latches,
|
|
we will instantly report a timeout error and roll back the transaction.
|
|
5. The transaction metadata is marked as committed.
|
|
6. If any files were deleted, we will durably write FILE_DELETE
|
|
to the redo log and start deleting the files.
|
|
6b. Also purge after a commit may perform file deletion. This is also the
|
|
recovery mechanism if the server was killed between step 5 and 6.
|
|
7. We unlock the data dictionary cache.
|
|
8. The file handles of the unlinked files will be closed. This will actually
|
|
reclaim the space in the file system (delete-on-close semantics).
|
|
|
|
Notes:
|
|
|
|
(a) Purge will be locked out by MDL. For internal tables related to
|
|
FULLTEXT INDEX, purge will not acquire MDL on the user table name,
|
|
and therefore, when we are dropping any FTS_ tables, we must suspend
|
|
and resume purge to prevent a race condition.
|
|
|
|
(b) If a transaction needs to both drop and create a table by some
|
|
name, it must rename the table in between. This is used by
|
|
ha_innobase::truncate() and fts_drop_common_tables().
|
|
|
|
(c) No data is ever destroyed before the transaction is committed,
|
|
so we can trivially roll back the transaction at any time.
|
|
Lock waits during a DDL operation are no longer a fatal error
|
|
that would cause the InnoDB to hang or to intentionally crash.
|
|
(Only ALTER TABLE...DISCARD TABLESPACE may discard data before commit.)
|
|
|
|
(d) The only changes to the data dictionary cache that are performed
|
|
before transaction commit and must be rolled back explicitly are as follows:
|
|
(d1) fts_optimize_add_table() to undo fts_optimize_remove_table()
|
|
*/
|
|
|
|
#include "trx0purge.h"
|
|
#include "dict0dict.h"
|
|
#include "dict0stats.h"
|
|
#include "dict0stats_bg.h"
|
|
|
|
#include "dict0defrag_bg.h"
|
|
#include "btr0defragment.h"
|
|
#include "ibuf0ibuf.h"
|
|
#include "lock0lock.h"
|
|
|
|
#include "que0que.h"
|
|
#include "pars0pars.h"
|
|
|
|
/** Try to drop the foreign key constraints for a persistent table.
|
|
@param name name of persistent table
|
|
@return error code */
|
|
dberr_t trx_t::drop_table_foreign(const table_name_t &name)
|
|
{
|
|
ut_ad(dict_sys.locked());
|
|
ut_ad(state == TRX_STATE_ACTIVE);
|
|
ut_ad(dict_operation);
|
|
ut_ad(dict_operation_lock_mode);
|
|
|
|
if (!dict_sys.sys_foreign || dict_sys.sys_foreign->corrupted)
|
|
return DB_SUCCESS;
|
|
|
|
if (!dict_sys.sys_foreign_cols || dict_sys.sys_foreign_cols->corrupted)
|
|
return DB_SUCCESS;
|
|
|
|
pars_info_t *info= pars_info_create();
|
|
pars_info_add_str_literal(info, "name", name.m_name);
|
|
return que_eval_sql(info,
|
|
"PROCEDURE DROP_FOREIGN() IS\n"
|
|
"fid CHAR;\n"
|
|
|
|
"DECLARE CURSOR fk IS\n"
|
|
"SELECT ID FROM SYS_FOREIGN\n"
|
|
"WHERE FOR_NAME=:name\n"
|
|
"AND TO_BINARY(FOR_NAME)=TO_BINARY(:name)\n"
|
|
"FOR UPDATE;\n"
|
|
|
|
"BEGIN\n"
|
|
"OPEN fk;\n"
|
|
"WHILE 1=1 LOOP\n"
|
|
" FETCH fk INTO fid;\n"
|
|
" IF (SQL % NOTFOUND)THEN RETURN;END IF;\n"
|
|
" DELETE FROM SYS_FOREIGN_COLS"
|
|
" WHERE ID=fid;\n"
|
|
" DELETE FROM SYS_FOREIGN WHERE ID=fid;\n"
|
|
"END LOOP;\n"
|
|
"CLOSE fk;\n"
|
|
"END;\n", this);
|
|
}
|
|
|
|
/** Try to drop the statistics for a persistent table.
|
|
@param name name of persistent table
|
|
@return error code */
|
|
dberr_t trx_t::drop_table_statistics(const table_name_t &name)
|
|
{
|
|
ut_ad(dict_sys.locked());
|
|
ut_ad(dict_operation_lock_mode);
|
|
|
|
if (strstr(name.m_name, "/" TEMP_FILE_PREFIX_INNODB) ||
|
|
!strcmp(name.m_name, TABLE_STATS_NAME) ||
|
|
!strcmp(name.m_name, INDEX_STATS_NAME))
|
|
return DB_SUCCESS;
|
|
|
|
char db[MAX_DB_UTF8_LEN], table[MAX_TABLE_UTF8_LEN];
|
|
dict_fs2utf8(name.m_name, db, sizeof db, table, sizeof table);
|
|
|
|
dberr_t err= dict_stats_delete_from_table_stats(db, table, this);
|
|
if (err == DB_SUCCESS || err == DB_STATS_DO_NOT_EXIST)
|
|
{
|
|
err= dict_stats_delete_from_index_stats(db, table, this);
|
|
if (err == DB_STATS_DO_NOT_EXIST)
|
|
err= DB_SUCCESS;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/** Try to drop a persistent table.
|
|
@param table persistent table
|
|
@param fk whether to drop FOREIGN KEY metadata
|
|
@return error code */
|
|
dberr_t trx_t::drop_table(const dict_table_t &table)
|
|
{
|
|
ut_ad(dict_sys.locked());
|
|
ut_ad(state == TRX_STATE_ACTIVE);
|
|
ut_ad(dict_operation);
|
|
ut_ad(dict_operation_lock_mode);
|
|
ut_ad(!table.is_temporary());
|
|
/* The table must be exclusively locked by this transaction. */
|
|
ut_ad(table.get_ref_count() <= 1);
|
|
ut_ad(table.n_lock_x_or_s == 1);
|
|
ut_ad(UT_LIST_GET_LEN(table.locks) >= 1);
|
|
#ifdef UNIV_DEBUG
|
|
bool found_x= false;
|
|
for (lock_t *lock= UT_LIST_GET_FIRST(table.locks); lock;
|
|
lock= UT_LIST_GET_NEXT(un_member.tab_lock.locks, lock))
|
|
{
|
|
ut_ad(lock->trx == this);
|
|
switch (lock->type_mode) {
|
|
case LOCK_TABLE | LOCK_X:
|
|
found_x= true;
|
|
break;
|
|
case LOCK_TABLE | LOCK_IX:
|
|
case LOCK_TABLE | LOCK_AUTO_INC:
|
|
break;
|
|
default:
|
|
ut_ad("unexpected lock type" == 0);
|
|
}
|
|
}
|
|
ut_ad(found_x);
|
|
#endif
|
|
|
|
if (dict_sys.sys_virtual && !dict_sys.sys_virtual->corrupted)
|
|
{
|
|
pars_info_t *info= pars_info_create();
|
|
pars_info_add_ull_literal(info, "id", table.id);
|
|
if (dberr_t err= que_eval_sql(info,
|
|
"PROCEDURE DROP_VIRTUAL() IS\n"
|
|
"BEGIN\n"
|
|
"DELETE FROM SYS_VIRTUAL"
|
|
" WHERE TABLE_ID=:id;\n"
|
|
"END;\n", this))
|
|
return err;
|
|
}
|
|
|
|
/* Once DELETE FROM SYS_INDEXES is committed, purge may invoke
|
|
dict_drop_index_tree(). */
|
|
|
|
if (!(table.flags2 & (DICT_TF2_FTS_HAS_DOC_ID | DICT_TF2_FTS)));
|
|
else if (dberr_t err= fts_drop_tables(this, table))
|
|
{
|
|
ib::error() << "Unable to remove FTS tables for "
|
|
<< table.name << ": " << err;
|
|
return err;
|
|
}
|
|
|
|
mod_tables.emplace(const_cast<dict_table_t*>(&table), undo_no).
|
|
first->second.set_dropped();
|
|
|
|
pars_info_t *info= pars_info_create();
|
|
pars_info_add_ull_literal(info, "id", table.id);
|
|
return que_eval_sql(info,
|
|
"PROCEDURE DROP_TABLE() IS\n"
|
|
"iid CHAR;\n"
|
|
|
|
"DECLARE CURSOR idx IS\n"
|
|
"SELECT ID FROM SYS_INDEXES\n"
|
|
"WHERE TABLE_ID=:id FOR UPDATE;\n"
|
|
|
|
"BEGIN\n"
|
|
|
|
"DELETE FROM SYS_TABLES WHERE ID=:id;\n"
|
|
"DELETE FROM SYS_COLUMNS WHERE TABLE_ID=:id;\n"
|
|
|
|
"OPEN idx;\n"
|
|
"WHILE 1 = 1 LOOP\n"
|
|
" FETCH idx INTO iid;\n"
|
|
" IF (SQL % NOTFOUND) THEN EXIT; END IF;\n"
|
|
" DELETE FROM SYS_INDEXES WHERE CURRENT OF idx;\n"
|
|
" DELETE FROM SYS_FIELDS WHERE INDEX_ID=iid;\n"
|
|
"END LOOP;\n"
|
|
"CLOSE idx;\n"
|
|
|
|
"END;\n", this);
|
|
}
|
|
|
|
/** Commit the transaction, possibly after drop_table().
|
|
@param deleted handles of data files that were deleted */
|
|
void trx_t::commit(std::vector<pfs_os_file_t> &deleted)
|
|
{
|
|
ut_ad(dict_operation);
|
|
flush_log_later= true;
|
|
commit_persist();
|
|
flush_log_later= false;
|
|
if (dict_operation)
|
|
{
|
|
std::vector<uint32_t> space_ids;
|
|
space_ids.reserve(mod_tables.size());
|
|
ut_ad(dict_sys.locked());
|
|
lock_sys.wr_lock(SRW_LOCK_CALL);
|
|
mutex_lock();
|
|
lock_release_on_drop(this);
|
|
ut_ad(UT_LIST_GET_LEN(lock.trx_locks) == 0);
|
|
ut_ad(ib_vector_is_empty(autoinc_locks));
|
|
mem_heap_empty(lock.lock_heap);
|
|
lock.table_locks.clear();
|
|
/* commit_persist() already reset this. */
|
|
ut_ad(!lock.was_chosen_as_deadlock_victim);
|
|
lock.n_rec_locks= 0;
|
|
lock.set_nth_bit_calls= 0;
|
|
while (dict_table_t *table= UT_LIST_GET_FIRST(lock.evicted_tables))
|
|
{
|
|
UT_LIST_REMOVE(lock.evicted_tables, table);
|
|
dict_mem_table_free(table);
|
|
}
|
|
dict_operation= false;
|
|
id= 0;
|
|
mutex_unlock();
|
|
|
|
for (const auto &p : mod_tables)
|
|
{
|
|
if (p.second.is_dropped())
|
|
{
|
|
dict_table_t *table= p.first;
|
|
dict_stats_recalc_pool_del(table->id, true);
|
|
dict_stats_defrag_pool_del(table, nullptr);
|
|
if (btr_defragment_active)
|
|
btr_defragment_remove_table(table);
|
|
const fil_space_t *space= table->space;
|
|
ut_ad(!p.second.is_aux_table() || purge_sys.must_wait_FTS());
|
|
dict_sys.remove(table);
|
|
if (const auto id= space ? space->id : 0)
|
|
{
|
|
space_ids.emplace_back(id);
|
|
pfs_os_file_t d= fil_delete_tablespace(id);
|
|
if (d != OS_FILE_CLOSED)
|
|
deleted.emplace_back(d);
|
|
}
|
|
}
|
|
}
|
|
|
|
lock_sys.wr_unlock();
|
|
|
|
mysql_mutex_lock(&lock_sys.wait_mutex);
|
|
lock_sys.deadlock_check();
|
|
mysql_mutex_unlock(&lock_sys.wait_mutex);
|
|
|
|
for (const auto id : space_ids)
|
|
ibuf_delete_for_discarded_space(id);
|
|
}
|
|
commit_cleanup();
|
|
}
|