diff options
Diffstat (limited to '')
-rw-r--r-- | storage/innobase/dict/drop.cc | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/storage/innobase/dict/drop.cc b/storage/innobase/dict/drop.cc new file mode 100644 index 00000000..dce71974 --- /dev/null +++ b/storage/innobase/dict/drop.cc @@ -0,0 +1,297 @@ +/***************************************************************************** + +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; + 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(); +} |