summaryrefslogtreecommitdiff
path: root/storage
diff options
context:
space:
mode:
authorMarko Mäkelä <marko.makela@mariadb.com>2018-11-22 10:41:15 +0200
committerMarko Mäkelä <marko.makela@mariadb.com>2018-11-22 10:41:15 +0200
commit2ebb110c36b2671614af26f34b35ece58a9b9f6a (patch)
tree46266c0b27c09cf6d95788aa711bf0e232b5f491 /storage
parent0fe90263c820d0972ae5dc9d201ea5a68214c710 (diff)
downloadmariadb-git-2ebb110c36b2671614af26f34b35ece58a9b9f6a.tar.gz
MDEV-17793 Crash in purge after instant DROP and emptying the table
There was a race condition between ALTER TABLE and purge. If a table turns out to be logically empty when instant ALTER TABLE is executing, we will convert the table to the canonical format, to avoid overhead during subsequent accesses, and to allow the data file to be imported into older versions of MariaDB. It could happen that at the time the table is logically empty, there still exists an undo log record for updating the hidden metadata record for an earlier instant ALTER TABLE operation. If the table was converted to the canonical format before purge processes this undo log record, the undo log record could be referring to index fields that no longer exist, causing a crash. To prevent the race condition, we must delete the old undo log records. We do this lazily by assigning a new table ID, so that the table lookup for the old undo log records will fail. dict_table_t::reassign_id(): Reassign the table_id to effectively lazily delete old undo log records. innobase_instant_try(): Invoke index->table->reassign_id() before index->clear_instant_alter().
Diffstat (limited to 'storage')
-rw-r--r--storage/innobase/handler/handler0alter.cc59
-rw-r--r--storage/innobase/include/dict0mem.h9
2 files changed, 68 insertions, 0 deletions
diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc
index 8b8ccd2fc11..522f6288762 100644
--- a/storage/innobase/handler/handler0alter.cc
+++ b/storage/innobase/handler/handler0alter.cc
@@ -5217,6 +5217,51 @@ dict_index_t::instant_metadata(const dtuple_t& row, mem_heap_t* heap) const
return entry;
}
+/** Assign a new id to invalidate old undo log records, so
+that purge will be unable to refer to fields that used to be
+instantly added to the end of the index. This is only to be
+used during ALTER TABLE when the table is empty, before
+invoking dict_index_t::clear_instant_alter().
+@param[in,out] trx dictionary transaction
+@return error code */
+inline dberr_t dict_table_t::reassign_id(trx_t* trx)
+{
+ DBUG_ASSERT(instant);
+ ut_ad(magic_n == DICT_TABLE_MAGIC_N);
+
+ table_id_t new_id;
+ dict_hdr_get_new_id(&new_id, NULL, NULL, NULL, false);
+ pars_info_t* pinfo = pars_info_create();
+
+ pars_info_add_ull_literal(pinfo, "old", id);
+ pars_info_add_ull_literal(pinfo, "new", new_id);
+
+ ut_ad(mutex_own(&dict_sys->mutex));
+ ut_ad(rw_lock_own(dict_operation_lock, RW_LOCK_X));
+ ut_ad(trx->dict_operation_lock_mode == RW_X_LATCH);
+
+ dberr_t err = que_eval_sql(
+ pinfo,
+ "PROCEDURE RENUMBER_TABLE_ID_PROC () IS\n"
+ "BEGIN\n"
+ "UPDATE SYS_TABLES SET ID=:new WHERE ID=:old;\n"
+ "UPDATE SYS_COLUMNS SET TABLE_ID=:new WHERE TABLE_ID=:old;\n"
+ "UPDATE SYS_INDEXES SET TABLE_ID=:new WHERE TABLE_ID=:old;\n"
+ "END;\n"
+ , FALSE, trx);
+ if (err == DB_SUCCESS) {
+ auto fold = ut_fold_ull(id);
+ HASH_DELETE(dict_table_t, id_hash, dict_sys->table_id_hash,
+ fold, this);
+ id = new_id;
+ fold = ut_fold_ull(id);
+ HASH_INSERT(dict_table_t, id_hash, dict_sys->table_id_hash,
+ fold, this);
+ }
+
+ return err;
+}
+
/** Insert or update SYS_COLUMNS and the hidden metadata record
for instant ALTER TABLE.
@param[in] ha_alter_info ALTER TABLE context
@@ -5495,6 +5540,20 @@ add_all_virtual:
empty_table:
/* The table is empty. */
ut_ad(page_is_root(block->frame));
+ if (index->table->instant) {
+ /* Assign a new dict_table_t::id
+ to invalidate old undo log records in purge,
+ so that they cannot refer to fields that were
+ instantly added to the end of the index,
+ instead of using the canonical positions
+ that will be replaced below
+ by index->clear_instant_alter(). */
+ err = index->table->reassign_id(trx);
+ if (err != DB_SUCCESS) {
+ goto func_exit;
+ }
+ }
+ /* MDEV-17383: free metadata BLOBs! */
btr_page_empty(block, NULL, index, 0, &mtr);
index->clear_instant_alter();
err = DB_SUCCESS;
diff --git a/storage/innobase/include/dict0mem.h b/storage/innobase/include/dict0mem.h
index 7e6fe455b72..6a408aaaa9e 100644
--- a/storage/innobase/include/dict0mem.h
+++ b/storage/innobase/include/dict0mem.h
@@ -1677,6 +1677,15 @@ struct dict_table_t {
const char* old_v_col_names,
const ulint* col_map);
+ /** Assign a new id to invalidate old undo log records, so
+ that purge will be unable to refer to fields that used to be
+ instantly added to the end of the index. This is only to be
+ used during ALTER TABLE when the table is empty, before
+ invoking dict_index_t::clear_instant_alter().
+ @param[in,out] trx dictionary transaction
+ @return error code */
+ inline dberr_t reassign_id(trx_t* trx);
+
/** Add the table definition to the data dictionary cache */
void add_to_cache();