diff --git a/mysql-test/suite/gcol/r/innodb_virtual_basic.result b/mysql-test/suite/gcol/r/innodb_virtual_basic.result index 35534d68e632a..2e3de6045bf27 100644 --- a/mysql-test/suite/gcol/r/innodb_virtual_basic.result +++ b/mysql-test/suite/gcol/r/innodb_virtual_basic.result @@ -48,9 +48,9 @@ INSERT INTO t VALUES (1290, 212, DEFAULT, "xmx"); ROLLBACK; SELECT c FROM t; c -NULL 13 29 +NULL SELECT * FROM t; a b c h 10 3 13 mm @@ -303,23 +303,23 @@ END| CALL UPDATE_t(); SELECT c FROM t; c -NULL 19 -29 2103 +29 +NULL CALL DELETE_insert_t(); SELECT c FROM t; c -NULL 19 -29 2103 +29 +NULL DROP INDEX idx ON t; CALL UPDATE_t(); SELECT c FROM t; c -2103 19 +2103 29 NULL DROP PROCEDURE DELETE_insert_t; @@ -523,10 +523,10 @@ UPDATE t SET h = "e" WHERE h="a"; ROLLBACK; SELECT a, c, h FROM t; a c h -NULL NULL d 11 14 a 18 19 b 28 29 c +NULL NULL d DROP TABLE t; CREATE TABLE `t1` ( `col1` int(11) NOT NULL, diff --git a/mysql-test/suite/gcol/t/innodb_virtual_basic.test b/mysql-test/suite/gcol/t/innodb_virtual_basic.test index dd0409a373c27..280b0100bb37c 100644 --- a/mysql-test/suite/gcol/t/innodb_virtual_basic.test +++ b/mysql-test/suite/gcol/t/innodb_virtual_basic.test @@ -42,6 +42,7 @@ INSERT INTO t VALUES (128, 22, DEFAULT, "xx"); INSERT INTO t VALUES (1290, 212, DEFAULT, "xmx"); ROLLBACK; +--sorted_result SELECT c FROM t; SELECT * FROM t; @@ -356,13 +357,16 @@ END| delimiter ;| CALL UPDATE_t(); +--sorted_result SELECT c FROM t; CALL DELETE_insert_t(); +--sorted_result SELECT c FROM t; DROP INDEX idx ON t; CALL UPDATE_t(); +--sorted_result SELECT c FROM t; DROP PROCEDURE DELETE_insert_t; @@ -537,6 +541,7 @@ START TRANSACTION; UPDATE t SET m =10 WHERE m = 1; UPDATE t SET h = "e" WHERE h="a"; ROLLBACK; +--sorted_result SELECT a, c, h FROM t; DROP TABLE t; diff --git a/mysql-test/suite/vcol/r/races.result b/mysql-test/suite/vcol/r/races.result index c46ed5ba2ef53..c93c8b01e2eeb 100644 --- a/mysql-test/suite/vcol/r/races.result +++ b/mysql-test/suite/vcol/r/races.result @@ -14,3 +14,17 @@ disconnect con1; connection default; drop table t1; set debug_sync='reset'; +# +# MDEV-39261 MariaDB crash on startup in presence of +# indexed virtual columns +# +# Create 33 tables with virtual index +InnoDB 0 transactions not purged +connect purge_control,localhost,root; +START TRANSACTION WITH CONSISTENT SNAPSHOT; +connection default; +# Do update on all 33 tables +# restart: --innodb_purge_threads=1 --debug_dbug=d,ib_purge_virtual_index_callback +InnoDB 0 transactions not purged +# Drop all 33 tables +# restart diff --git a/mysql-test/suite/vcol/t/races.test b/mysql-test/suite/vcol/t/races.test index 1bf4e43dec919..b6b42b1771da9 100644 --- a/mysql-test/suite/vcol/t/races.test +++ b/mysql-test/suite/vcol/t/races.test @@ -20,3 +20,59 @@ disconnect con1; connection default; drop table t1; set debug_sync='reset'; + +--echo # +--echo # MDEV-39261 MariaDB crash on startup in presence of +--echo # indexed virtual columns +--echo # +# To make purge thread to work on multiple tables on the same batch, +# we need 33 tables because there are 32 pre-existing purge_node exists. + +--echo # Create 33 tables with virtual index +--disable_query_log +let $i = 33; +while ($i) +{ + eval CREATE TABLE t$i( + a INT PRIMARY KEY, + b INT DEFAULT 1, INDEX(b), + c INT GENERATED ALWAYS AS (a + b) VIRTUAL, + INDEX(c) + ) ENGINE=InnoDB; + eval INSERT INTO t$i(a) VALUES(1); + dec $i; +} +--enable_query_log +--source ../../innodb/include/wait_all_purged.inc +--connect purge_control,localhost,root +START TRANSACTION WITH CONSISTENT SNAPSHOT; + +--connection default +--echo # Do update on all 33 tables +--disable_query_log +let $i = 33; +while ($i) +{ + eval UPDATE t$i SET b = 11 WHERE a = 1; + dec $i; +} +--enable_query_log + +let $shutdown_timeout=0; +let $restart_parameters=--innodb_purge_threads=1 --debug_dbug=d,ib_purge_virtual_index_callback; +--source include/restart_mysqld.inc +--source ../../innodb/include/wait_all_purged.inc + +--echo # Drop all 33 tables +--disable_query_log +let $i = 33; +while ($i) +{ + eval DROP TABLE t$i; + dec $i; +} +--enable_query_log + +let $restart_parameters=; +let $shutdown_timeout=; +--source include/restart_mysqld.inc diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 79d7d7d32324a..94507b57a0458 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -820,7 +820,7 @@ static inline bool check_field_pointers(const TABLE *table) leave prelocked mode if needed. */ -int close_thread_tables(THD *thd) +int close_thread_tables(THD *thd) noexcept { TABLE *table; int error= 0; diff --git a/sql/sql_base.h b/sql/sql_base.h index 8dd9bca5b1d93..4381dd0dd5b96 100644 --- a/sql/sql_base.h +++ b/sql/sql_base.h @@ -163,7 +163,7 @@ TABLE_LIST *find_table_in_list(TABLE_LIST *table, TABLE_LIST *TABLE_LIST::*link, const LEX_CSTRING *db_name, const LEX_CSTRING *table_name); -int close_thread_tables(THD *thd); +int close_thread_tables(THD *thd) noexcept; void switch_to_nullable_trigger_fields(List &items, TABLE *); void switch_defaults_to_nullable_trigger_fields(TABLE *table); bool fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, diff --git a/sql/sql_class.cc b/sql/sql_class.cc index e9de780adbcf3..cb5b459e7a855 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -5024,10 +5024,10 @@ extern "C" const char *thd_priv_user(MYSQL_THD thd, size_t *length) have only one table open at any given time. */ TABLE *open_purge_table(THD *thd, const char *db, size_t dblen, - const char *tb, size_t tblen) + const char *tb, size_t tblen, + MDL_ticket *mdl_ticket) noexcept { DBUG_ENTER("open_purge_table"); - DBUG_ASSERT(thd->open_tables == NULL); DBUG_ASSERT(thd->locked_tables_mode < LTM_PRELOCKED); /* Purge already hold the MDL for the table */ @@ -5038,6 +5038,7 @@ TABLE *open_purge_table(THD *thd, const char *db, size_t dblen, tl->init_one_table(&db_name, &table_name, 0, TL_READ); tl->i_s_requested_object= OPEN_TABLE_ONLY; + tl->mdl_request.ticket= mdl_ticket; bool error= open_table(thd, tl, &ot_ctx); @@ -5050,13 +5051,6 @@ TABLE *open_purge_table(THD *thd, const char *db, size_t dblen, DBUG_RETURN(error ? NULL : tl->table); } -TABLE *get_purge_table(THD *thd) -{ - /* see above, at most one table can be opened */ - DBUG_ASSERT(thd->open_tables == NULL || thd->open_tables->next == NULL); - return thd->open_tables; -} - /** Find an open table in the list of prelocked tabled Used for foreign key actions, for example, in UPDATE t1 SET a=1; diff --git a/storage/innobase/dict/dict0dict.cc b/storage/innobase/dict/dict0dict.cc index bced539a0eb7c..58279219c61f7 100644 --- a/storage/innobase/dict/dict0dict.cc +++ b/storage/innobase/dict/dict0dict.cc @@ -626,13 +626,13 @@ bool dict_table_t::parse_name(char (&db_name)[NAME_LEN + 1], dict_sys.unfreeze(); *db_name_len= filename_to_tablename(db_buf, db_name, - MAX_DATABASE_NAME_LEN + 1, true); + NAME_LEN + 1, true); if (is_temp) return false; *tbl_name_len= filename_to_tablename(tbl_buf, tbl_name, - MAX_TABLE_NAME_LEN + 1, true); + NAME_LEN + 1, true); return true; } diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index fe65dc9ba5f0c..d2d0f4a842940 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -124,10 +124,10 @@ TABLE *find_fk_open_table(THD *thd, const char *db, size_t db_len, const char *table, size_t table_len); MYSQL_THD create_background_thd(); void reset_thd(MYSQL_THD thd); -TABLE *get_purge_table(THD *thd); TABLE *open_purge_table(THD *thd, const char *db, size_t dblen, - const char *tb, size_t tblen); -void close_thread_tables(THD* thd); + const char *tb, size_t tblen, + MDL_ticket *mdl_ticket) noexcept; +int close_thread_tables(THD* thd) noexcept; #ifdef MYSQL_DYNAMIC_PLUGIN #define tc_size 400 @@ -8536,7 +8536,7 @@ ATTRIBUTE_COLD bool wsrep_append_table_key(MYSQL_THD thd, const dict_table_t &ta { char db_buf[NAME_LEN + 1]; char tbl_buf[NAME_LEN + 1]; - ulint db_buf_len, tbl_buf_len; + size_t db_buf_len, tbl_buf_len; if (!table.parse_name(db_buf, tbl_buf, &db_buf_len, &tbl_buf_len)) { @@ -20071,33 +20071,18 @@ ha_innobase::multi_range_read_explain_info( for purge thread */ static TABLE* innodb_find_table_for_vc(THD* thd, dict_table_t* table) { - TABLE *mysql_table; - const bool bg_thread = THDVAR(thd, background_thread); - - if (bg_thread) { - if ((mysql_table = get_purge_table(thd))) { - return mysql_table; - } - } else { - if (table->vc_templ->mysql_table_query_id - == thd_get_query_id(thd)) { - return table->vc_templ->mysql_table; - } + if (table->vc_templ->mysql_table_query_id == thd_get_query_id(thd)) { + return table->vc_templ->mysql_table; } + TABLE *mysql_table; char db_buf[NAME_LEN + 1]; char tbl_buf[NAME_LEN + 1]; - ulint db_buf_len, tbl_buf_len; + size_t db_buf_len, tbl_buf_len; if (!table->parse_name(db_buf, tbl_buf, &db_buf_len, &tbl_buf_len)) { return NULL; } - - if (bg_thread) { - return open_purge_table(thd, db_buf, db_buf_len, - tbl_buf, tbl_buf_len); - } - mysql_table = find_fk_open_table(thd, db_buf, db_buf_len, tbl_buf, tbl_buf_len); table->vc_templ->mysql_table = mysql_table; @@ -21306,3 +21291,16 @@ void alter_stats_rebuild(dict_table_t *table, THD *thd) " table rebuild: %s", ut_strerr(ret)); DBUG_VOID_RETURN; } + +TABLE* innobase_open_purge_table(THD *thd, const char *db_buf, + size_t db_len, const char *tbl_buf, + size_t tbl_len, + MDL_ticket *mdl_ticket) noexcept +{ + return open_purge_table(thd, db_buf, db_len, tbl_buf, tbl_len, mdl_ticket); +} + +int innobase_close_thread_tables(THD *thd) noexcept +{ + return close_thread_tables(thd); +} diff --git a/storage/innobase/include/dict0mem.h b/storage/innobase/include/dict0mem.h index 0ab4e4d1fdae6..bc6f254f06070 100644 --- a/storage/innobase/include/dict0mem.h +++ b/storage/innobase/include/dict0mem.h @@ -2551,6 +2551,18 @@ struct dict_table_t { static dict_table_t *create(const span &name, fil_space_t *space, ulint n_cols, ulint n_v_cols, ulint flags, ulint flags2); + + /** @return whether the table has any indexed virtual column */ + bool has_virtual_index() const + { + for (dict_index_t *index = indexes.start; + index; index = UT_LIST_GET_NEXT(indexes, index)) + { + if (index->has_virtual()) + return true; + } + return false; + } }; inline void dict_index_t::set_modified(mtr_t& mtr) const diff --git a/storage/innobase/include/row0mysql.h b/storage/innobase/include/row0mysql.h index 63858f25f023e..941f420d216fb 100644 --- a/storage/innobase/include/row0mysql.h +++ b/storage/innobase/include/row0mysql.h @@ -40,6 +40,7 @@ Created 9/17/2000 Heikki Tuuri struct row_prebuilt_t; class ha_innobase; class ha_handler_stats; +class MDL_ticket; /*******************************************************************//** Frees the blob heap in prebuilt when no longer needed. */ @@ -830,6 +831,26 @@ void innobase_rename_vc_templ( dict_table_t* table); +/** Open a table for purge operations with MariaDB TABLE handle. +This is a wrapper to make open_purge_table() accessible from InnoDB +purge subsystem. +@param thd thread handle +@param db_buf database name buffer +@param db_len database name length +@param tbl_buf table name buffer +@param tbl_len table name length +@return MariaDB TABLE handle, or NULL if not opened */ +TABLE* innobase_open_purge_table(THD *thd, const char *db_buf, + size_t db_len, const char *tbl_buf, + size_t tbl_len, + MDL_ticket *mdl_ticket) noexcept; + +/** Close all tables opened by the thread. +This is a wrapper to make close_thread_tables() accessible from +InnoDB purge subsytem. +@param thd thread handle */ +int innobase_close_thread_tables(THD *thd) noexcept; + #define ROW_PREBUILT_FETCH_MAGIC_N 465765687 #define ROW_MYSQL_WHOLE_ROW 0 diff --git a/storage/innobase/include/row0purge.h b/storage/innobase/include/row0purge.h index baa7777e6c81e..8a6b6fb72cf61 100644 --- a/storage/innobase/include/row0purge.h +++ b/storage/innobase/include/row0purge.h @@ -67,6 +67,29 @@ row_purge_step( que_thr_t* thr) /*!< in: query thread */ MY_ATTRIBUTE((nonnull, warn_unused_result)); +/** Table context for purge operations. Holds InnoDB table handle, +MDL ticket, and MariaDB TABLE* for a table_id with indexed virtual +columns. This information is fetched by the purge +coordinator thread during batch preparation. Purge worker threads +can retrieve the TABLE* when needed for virtual column computation. */ +struct purge_table_ctx_t +{ + /** InnoDB table handle */ + dict_table_t *table; + + /** MDL ticket for the table */ + MDL_ticket *mdl_ticket; + + /** MariaDB TABLE handle (for virtual column computation) */ + TABLE *maria_table; + + purge_table_ctx_t() + : table(nullptr), mdl_ticket(nullptr), maria_table(nullptr) {} + + purge_table_ctx_t(dict_table_t *t, MDL_ticket *m, TABLE *mt) + : table(t), mdl_ticket(m), maria_table(mt) {} +}; + /** Purge worker context */ struct purge_node_t { @@ -111,8 +134,9 @@ struct purge_node_t /** Undo recs to purge */ std::queue undo_recs; - /** map of table identifiers to table handles and meta-data locks */ - std::unordered_map> tables; + /** map of table identifiers to purge table context which has + set by purge co-ordinator thread */ + std::unordered_map tables; /** Constructor */ explicit purge_node_t(que_thr_t *parent) : diff --git a/storage/innobase/include/row0vers.h b/storage/innobase/include/row0vers.h index 2ddffa41af195..e4284bbd10026 100644 --- a/storage/innobase/include/row0vers.h +++ b/storage/innobase/include/row0vers.h @@ -65,13 +65,15 @@ bool dtuple_vcol_data_missing(const dtuple_t &tuple, @param[in,out] row the cluster index row in dtuple form @param[in] clust_index clustered index @param[in] index the secondary index -@param[in] heap heap used to build virtual dtuple. */ +@param[in] heap heap used to build virtual dtuple. +@param[in] maria_table MariaDB table object */ bool row_vers_build_clust_v_col( dtuple_t* row, dict_index_t* clust_index, dict_index_t* index, - mem_heap_t* heap); + mem_heap_t* heap, + TABLE* maria_table= nullptr); /** Build a dtuple contains virtual column data for current cluster index @param[in] rec cluster index rec @param[in] clust_index cluster index @@ -83,6 +85,7 @@ row_vers_build_clust_v_col( @param[in,out] heap heap memory @param[in,out] v_heap heap memory to keep virtual column tuple @param[in,out] mtr mini-transaction +@param[in] maria_table MariaDB table object @return dtuple contains virtual column data */ dtuple_t* row_vers_build_cur_vrow( @@ -94,7 +97,8 @@ row_vers_build_cur_vrow( roll_ptr_t roll_ptr, mem_heap_t* heap, mem_heap_t* v_heap, - mtr_t* mtr); + mtr_t* mtr, + TABLE* maria_table= nullptr); /*****************************************************************//** Constructs the version of a clustered index record which a consistent diff --git a/storage/innobase/include/trx0purge.h b/storage/innobase/include/trx0purge.h index 0222139018972..87e174ddec681 100644 --- a/storage/innobase/include/trx0purge.h +++ b/storage/innobase/include/trx0purge.h @@ -229,6 +229,15 @@ class purge_sys_t to purge */ trx_rseg_t* rseg; /*!< Rollback segment for the next undo record to purge */ + + /** Coordinator thread's THD during batch processing. + The coordinator thread sets this at the start of trx_purge() + and clears it at the end. This is being used in + purge_node_t::end() to determine whether InnoDB should + call innobase_reset_background_thd(). The coordinator + skips this call because it manages table cleanup centrally in + trx_purge() after all workers complete. */ + THD* coordinator_thd= nullptr; private: uint32_t page_no; /*!< Page number for the next undo record to purge, page number of the @@ -317,7 +326,9 @@ class purge_sys_t void resume(); /** Close and reopen all tables in case of a MDL conflict with DDL */ - dict_table_t *close_and_reopen(table_id_t id, THD *thd, MDL_ticket **mdl); + dict_table_t *close_and_reopen(table_id_t id, THD *thd, + MDL_ticket **mdl, + TABLE **maria_table); private: /** Suspend purge during a DDL operation on FULLTEXT INDEX tables */ void wait_FTS(bool also_sys); diff --git a/storage/innobase/row/row0purge.cc b/storage/innobase/row/row0purge.cc index 44e79d17e97b1..3377d4b40416e 100644 --- a/storage/innobase/row/row0purge.cc +++ b/storage/innobase/row/row0purge.cc @@ -494,8 +494,13 @@ static bool row_purge_is_unsafe(const purge_node_t &node, clust_index->n_core_fields, ULINT_UNDEFINED, &heap); + TABLE *maria_table= nullptr; if (dict_index_has_virtual(index)) { v_heap = mem_heap_create(100); + auto it= node.tables.find(index->table->id); + if (it != node.tables.end()) { + maria_table = it->second.maria_table; + } } if (!rec_get_deleted_flag(rec, rec_offs_comp(clust_offsets))) { @@ -534,7 +539,8 @@ static bool row_purge_is_unsafe(const purge_node_t &node, || dbug_v_purge) { if (!row_vers_build_clust_v_col( - row, clust_index, index, heap)) { + row, clust_index, index, heap, + maria_table)) { goto unsafe_to_purge; } @@ -613,7 +619,8 @@ static bool row_purge_is_unsafe(const purge_node_t &node, cur_vrow = row_vers_build_cur_vrow( rec, clust_index, &clust_offsets, - index, trx_id, roll_ptr, heap, v_heap, mtr); + index, trx_id, roll_ptr, heap, v_heap, mtr, + maria_table); } version = rec; @@ -1462,13 +1469,13 @@ row_purge_parse_undo_rec( } auto &tables_entry= node->tables[table_id]; - node->table = tables_entry.first; + node->table = tables_entry.table; if (!node->table) { return false; } #ifndef DBUG_OFF - if (MDL_ticket* mdl = tables_entry.second) { + if (MDL_ticket* mdl = tables_entry.mdl_ticket) { static_cast(thd_mdl_context(current_thd)) ->lock_warrant = mdl->get_ctx(); } @@ -1643,7 +1650,18 @@ inline que_node_t *purge_node_t::end(THD *thd) DBUG_ASSERT(common.type == QUE_NODE_PURGE); ut_ad(undo_recs.empty()); ut_d(in_progress= false); - innobase_reset_background_thd(thd); + /* Only reset THD for worker threads, not the coordinator. + The coordinator thread opens TABLE* objects in + trx_purge_attach_undo_recs() and stores them in + purge_node_t->tables. These TABLE* objects must remain open until + the entire purge batch completes. Coordinator thread could + close the tables prematurely if it calls innobase_reset_background_thd() + The coordinator handles cleanup centrally in trx_purge() after all + purge_node_t entries are processed. Worker threads have their own + THD lifecycle and must call reset_background_thd() to clean up their + thread-local resources. */ + if (thd != purge_sys.coordinator_thd) + innobase_reset_background_thd(thd); #ifndef DBUG_OFF static_cast(thd_mdl_context(thd))->lock_warrant= nullptr; #endif diff --git a/storage/innobase/row/row0vers.cc b/storage/innobase/row/row0vers.cc index 617aab8a967e4..c967dd1510186 100644 --- a/storage/innobase/row/row0vers.cc +++ b/storage/innobase/row/row0vers.cc @@ -459,10 +459,10 @@ row_vers_build_clust_v_col( dtuple_t* row, dict_index_t* clust_index, dict_index_t* index, - mem_heap_t* heap) + mem_heap_t* heap, + TABLE* maria_table) { THD* thd= current_thd; - TABLE* maria_table= 0; ut_ad(dict_index_has_virtual(index)); ut_ad(index->table == clust_index->table); @@ -632,7 +632,8 @@ row_vers_build_cur_vrow( roll_ptr_t roll_ptr, mem_heap_t* heap, mem_heap_t* v_heap, - mtr_t* mtr) + mtr_t* mtr, + TABLE* maria_table) { dtuple_t* cur_vrow = NULL; @@ -653,7 +654,7 @@ row_vers_build_cur_vrow( NULL, NULL, NULL, NULL, heap); if (!row_vers_build_clust_v_col(row, clust_index, index, - heap)) { + heap, maria_table)) { return nullptr; } diff --git a/storage/innobase/trx/trx0purge.cc b/storage/innobase/trx/trx0purge.cc index 8ac3f93cd1557..12ffa14bf3f97 100644 --- a/storage/innobase/trx/trx0purge.cc +++ b/storage/innobase/trx/trx0purge.cc @@ -1054,21 +1054,20 @@ static void trx_purge_close_tables(purge_node_t *node, THD *thd) noexcept { for (auto &t : node->tables) { - dict_table_t *table= t.second.first; + dict_table_t *table= t.second.table; if (table != nullptr && table != reinterpret_cast(-1)) dict_table_close(table); } MDL_context *mdl_context= static_cast(thd_mdl_context(thd)); - for (auto &t : node->tables) { - dict_table_t *table= t.second.first; + dict_table_t *table= t.second.table; if (table != nullptr && table != reinterpret_cast(-1)) { - t.second.first= reinterpret_cast(-1); - if (mdl_context != nullptr && t.second.second != nullptr) - mdl_context->release_lock(t.second.second); + t.second.table= reinterpret_cast(-1); + if (mdl_context != nullptr && t.second.mdl_ticket != nullptr) + mdl_context->release_lock(t.second.mdl_ticket); } } } @@ -1080,104 +1079,102 @@ void purge_sys_t::wait_FTS(bool also_sys) std::this_thread::sleep_for(std::chrono::milliseconds(10)); } -__attribute__((nonnull)) -/** Acquire a metadata lock on a table. -@param table table handle +/** Open a table handle for the purge of committed transaction history +@param table_id InnoDB table identifier @param mdl_context metadata lock acquisition context @param mdl metadata lock +@param maria_table MariaDB TABLE* handle @return table handle @retval nullptr if the table is not found or accessible @retval -1 if the purge of history must be suspended due to DDL */ -static dict_table_t *trx_purge_table_acquire(dict_table_t *table, - MDL_context *mdl_context, - MDL_ticket **mdl) noexcept +static dict_table_t *trx_purge_table_open(table_id_t table_id, + MDL_context *mdl_context, + MDL_ticket **mdl, + TABLE **maria_table) noexcept { - ut_ad(dict_sys.frozen_not_locked()); - *mdl= nullptr; + dict_table_t *table; + *maria_table = nullptr; + + for (;;) + { + dict_sys.freeze(SRW_LOCK_CALL); + table= dict_sys.find_table(table_id); + if (table) + break; + dict_sys.unfreeze(); + dict_sys.lock(SRW_LOCK_CALL); + table= dict_load_table_on_id(table_id, DICT_ERR_IGNORE_FK_NOKEY); + dict_sys.unlock(); + if (!table) + return nullptr; + } if (!table->is_readable() || table->corrupted) + { + dict_sys.unfreeze(); return nullptr; + } + + char db_buf[NAME_LEN + 1]; + char tbl_buf[NAME_LEN + 1]; + size_t db_len = dict_get_db_name_len(table->name.m_name); + size_t tbl_len; - size_t db_len= dict_get_db_name_len(table->name.m_name); if (db_len == 0) - { /* InnoDB system tables are not covered by MDL */ - got_table: - table->acquire(); - return table; - } + goto got_table; if (purge_sys.must_wait_FTS()) - must_wait: + { +must_wait: + dict_sys.unfreeze(); return reinterpret_cast(-1); - - char db_buf[NAME_LEN + 1]; - char tbl_buf[NAME_LEN + 1]; - size_t tbl_len; + } if (!table->parse_name(db_buf, tbl_buf, &db_len, &tbl_len)) + { /* The name of an intermediate table starts with #sql */ goto got_table; + } { MDL_request request; - MDL_REQUEST_INIT(&request,MDL_key::TABLE, db_buf, tbl_buf, MDL_SHARED, + MDL_REQUEST_INIT(&request, MDL_key::TABLE, db_buf, tbl_buf, MDL_SHARED, MDL_EXPLICIT); if (mdl_context->try_acquire_lock(&request)) goto must_wait; - *mdl= request.ticket; + *mdl = request.ticket; if (!*mdl) goto must_wait; } - goto got_table; -} - -/** Open a table handle for the purge of committed transaction history -@param table_id InnoDB table identifier -@param mdl_context metadata lock acquisition context -@param mdl metadata lock -@return table handle -@retval nullptr if the table is not found or accessible -@retval -1 if the purge of history must be suspended due to DDL */ -static dict_table_t *trx_purge_table_open(table_id_t table_id, - MDL_context *mdl_context, - MDL_ticket **mdl) noexcept -{ - dict_table_t *table; +got_table: + table->acquire(); + dict_sys.unfreeze(); - for (;;) + /* Open MariaDB TABLE for tables with indexed virtual columns */ + if (*mdl && table->has_virtual_index()) { - dict_sys.freeze(SRW_LOCK_CALL); - table= dict_sys.find_table(table_id); - if (table) - break; - dict_sys.unfreeze(); - dict_sys.lock(SRW_LOCK_CALL); - table= dict_load_table_on_id(table_id, DICT_ERR_IGNORE_FK_NOKEY); - dict_sys.unlock(); - if (!table) - return nullptr; - /* At this point, the freshly loaded table may already have been evicted. - We must look it up again while holding a shared dict_sys.latch. We keep - trying this until the table is found in the cache or it cannot be found - in the dictionary (because the table has been dropped or rebuilt). */ + THD *thd= current_thd; + *maria_table= innobase_open_purge_table(thd, db_buf, db_len, tbl_buf, + tbl_len, *mdl); + if (*maria_table && table->vc_templ) + table->vc_templ->mysql_table= *maria_table; } - - table= trx_purge_table_acquire(table, mdl_context, mdl); - dict_sys.unfreeze(); return table; } ATTRIBUTE_COLD dict_table_t *purge_sys_t::close_and_reopen(table_id_t id, THD *thd, - MDL_ticket **mdl) + MDL_ticket **mdl, + TABLE **maria_table) { MDL_context *mdl_context= static_cast(thd_mdl_context(thd)); ut_ad(mdl_context); retry: ut_ad(m_active); + innobase_close_thread_tables(thd); for (que_thr_t *thr= UT_LIST_GET_FIRST(purge_sys.query->thrs); thr; thr= UT_LIST_GET_NEXT(thrs, thr)) trx_purge_close_tables(static_cast(thr->child), thd); @@ -1186,7 +1183,7 @@ dict_table_t *purge_sys_t::close_and_reopen(table_id_t id, THD *thd, wait_FTS(false); m_active= true; - dict_table_t *table= trx_purge_table_open(id, mdl_context, mdl); + dict_table_t *table= trx_purge_table_open(id, mdl_context, mdl, maria_table); if (table == reinterpret_cast(-1)) goto retry; @@ -1196,16 +1193,19 @@ dict_table_t *purge_sys_t::close_and_reopen(table_id_t id, THD *thd, purge_node_t *node= static_cast(thr->child); for (auto &t : node->tables) { - if (t.second.first) + if (!t.second.table) + continue; + + t.second.table= trx_purge_table_open(t.first, mdl_context, + &t.second.mdl_ticket, + &t.second.maria_table); + if (t.second.table == reinterpret_cast(-1)) { - t.second.first= trx_purge_table_open(t.first, mdl_context, - &t.second.second); - if (t.second.first == reinterpret_cast(-1)) - { - if (table) - dict_table_close(table, false, thd, *mdl); - goto retry; - } + if (table) + dict_table_close(table); + if (mdl_context && *mdl) + mdl_context->release_lock(*mdl); + goto retry; } } } @@ -1265,23 +1265,27 @@ static purge_sys_t::iterator trx_purge_attach_undo_recs(THD *thd, ut_ad(!table_node->in_progress); if (!table_node) { - std::pair p; - p.first= trx_purge_table_open(table_id, mdl_context, &p.second); - if (p.first == reinterpret_cast(-1)) - p.first= purge_sys.close_and_reopen(table_id, thd, &p.second); - + purge_table_ctx_t purge_ctx; + purge_ctx.table= trx_purge_table_open(table_id, mdl_context, + &purge_ctx.mdl_ticket, + &purge_ctx.maria_table); + + if (purge_ctx.table == reinterpret_cast(-1)) + purge_ctx.table= + purge_sys.close_and_reopen(table_id, thd, + &purge_ctx.mdl_ticket, + &purge_ctx.maria_table); if (!thr || !(thr= UT_LIST_GET_NEXT(thrs, thr))) thr= UT_LIST_GET_FIRST(purge_sys.query->thrs); ++*n_work_items; table_node= static_cast(thr->child); - ut_a(que_node_get_type(table_node) == QUE_NODE_PURGE); - ut_d(auto pair=) table_node->tables.emplace(table_id, p); + ut_d(auto pair=) table_node->tables.emplace(table_id, purge_ctx); ut_ad(pair.second); - if (p.first) + if (purge_ctx.table) goto enqueue; } - else if (table_node->tables[table_id].first) + else if (table_node->tables[table_id].table) { enqueue: table_node->undo_recs.push(purge_rec); @@ -1382,6 +1386,7 @@ TRANSACTIONAL_TARGET ulint trx_purge(ulint n_tasks, ulint history_size) THD *const thd= current_thd; + purge_sys.coordinator_thd= thd; /* Fetch the UNDO recs that need to be purged. */ ulint n_work= 0; const purge_sys_t::iterator head= trx_purge_attach_undo_recs(thd, &n_work); @@ -1444,6 +1449,7 @@ TRANSACTIONAL_TARGET ulint trx_purge(ulint n_tasks, ulint history_size) if (workers) trx_purge_wait_for_workers_to_complete(); + innobase_close_thread_tables(thd); for (thr= UT_LIST_GET_FIRST(purge_sys.query->thrs); thr && n_work--; thr= UT_LIST_GET_NEXT(thrs, thr)) { @@ -1453,6 +1459,7 @@ TRANSACTIONAL_TARGET ulint trx_purge(ulint n_tasks, ulint history_size) } } + purge_sys.coordinator_thd= nullptr; purge_sys.batch_cleanup(head); MONITOR_INC_VALUE(MONITOR_PURGE_INVOKED, 1);