MDEV-25503 InnoDB hangs on startup during recovery

InnoDB startup hangs if a DDL transaction needs to be
rolled back and a recovered transaction on statistics
tables exists. In that case, InnoDB should rollback
the transaction which holds locks on innodb_table_stats
or innodb_index_stats during trx_rollback_or_clean_recovered().
parent 2b0d5b78
...@@ -50,3 +50,18 @@ Warnings: ...@@ -50,3 +50,18 @@ Warnings:
Warning 1082 InnoDB: Table test/t1 contains 1 indexes inside InnoDB, which is different from the number of indexes 2 defined in the MariaDB Warning 1082 InnoDB: Table test/t1 contains 1 indexes inside InnoDB, which is different from the number of indexes 2 defined in the MariaDB
Warning 1082 InnoDB: Table test/t1 contains 1 indexes inside InnoDB, which is different from the number of indexes 2 defined in the MariaDB Warning 1082 InnoDB: Table test/t1 contains 1 indexes inside InnoDB, which is different from the number of indexes 2 defined in the MariaDB
DROP TABLE t1; DROP TABLE t1;
#
# MDEV-25503 InnoDB hangs on startup during recovery
#
CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB STATS_PERSISTENT=1;
connect con1,localhost,root,,;
BEGIN;
DELETE FROM mysql.innodb_table_stats;
connect con2,localhost,root,,;
SET DEBUG_SYNC='inplace_after_index_build SIGNAL blocked WAIT_FOR ever';
ALTER TABLE t1 FORCE;
connection default;
SET DEBUG_SYNC='now WAIT_FOR blocked';
SELECT * FROM t1;
a
DROP TABLE t1;
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
--source include/innodb_page_size.inc --source include/innodb_page_size.inc
--source include/have_debug_sync.inc
call mtr.add_suppression("Cannot find index f2 in InnoDB index dictionary."); call mtr.add_suppression("Cannot find index f2 in InnoDB index dictionary.");
call mtr.add_suppression("InnoDB indexes are inconsistent with what defined in .frm for table .*"); call mtr.add_suppression("InnoDB indexes are inconsistent with what defined in .frm for table .*");
call mtr.add_suppression("Table test/t1 contains 1 indexes inside InnoDB, which is different from the number of indexes 2 defined in the MariaDB .*"); call mtr.add_suppression("Table test/t1 contains 1 indexes inside InnoDB, which is different from the number of indexes 2 defined in the MariaDB .*");
...@@ -69,3 +71,23 @@ disconnect con1; ...@@ -69,3 +71,23 @@ disconnect con1;
SHOW KEYS FROM t1; SHOW KEYS FROM t1;
DROP TABLE t1; DROP TABLE t1;
remove_files_wildcard $datadir/test #sql-*.frm; remove_files_wildcard $datadir/test #sql-*.frm;
--echo #
--echo # MDEV-25503 InnoDB hangs on startup during recovery
--echo #
CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB STATS_PERSISTENT=1;
connect (con1,localhost,root,,);
BEGIN;
DELETE FROM mysql.innodb_table_stats;
connect (con2,localhost,root,,);
SET DEBUG_SYNC='inplace_after_index_build SIGNAL blocked WAIT_FOR ever';
send ALTER TABLE t1 FORCE;
connection default;
SET DEBUG_SYNC='now WAIT_FOR blocked';
--let $shutdown_timeout=0
--source include/restart_mysqld.inc
SELECT * FROM t1;
DROP TABLE t1;
remove_files_wildcard $datadir/test #sql-*.frm;
...@@ -6719,3 +6719,9 @@ dict_table_t::get_overflow_field_local_len() const ...@@ -6719,3 +6719,9 @@ dict_table_t::get_overflow_field_local_len() const
/* new-format table: do not store any BLOB prefix locally */ /* new-format table: do not store any BLOB prefix locally */
return BTR_EXTERN_FIELD_REF_SIZE; return BTR_EXTERN_FIELD_REF_SIZE;
} }
bool dict_table_t::is_stats_table() const
{
return !strcmp(name.m_name, TABLE_STATS_NAME) ||
!strcmp(name.m_name, INDEX_STATS_NAME);
}
...@@ -1935,6 +1935,11 @@ struct dict_table_t { ...@@ -1935,6 +1935,11 @@ struct dict_table_t {
return true; return true;
return false; return false;
} }
/** Check whether the table name is same as mysql/innodb_stats_table
or mysql/innodb_index_stats.
@return true if the table name is same as stats table */
bool is_stats_table() const;
}; };
inline bool table_name_t::is_temporary() const inline bool table_name_t::is_temporary() const
......
...@@ -1191,6 +1191,10 @@ struct trx_t { ...@@ -1191,6 +1191,10 @@ struct trx_t {
ut_ad(old_n_ref > 0); ut_ad(old_n_ref > 0);
} }
/** @return whether the table has lock on
mysql.innodb_table_stats and mysql.innodb_index_stats */
bool has_stats_table_lock() const;
/** Free the memory to trx_pools */ /** Free the memory to trx_pools */
inline void free(); inline void free();
......
...@@ -677,7 +677,8 @@ ibool ...@@ -677,7 +677,8 @@ ibool
trx_rollback_resurrected( trx_rollback_resurrected(
/*=====================*/ /*=====================*/
trx_t* trx, /*!< in: transaction to rollback or clean */ trx_t* trx, /*!< in: transaction to rollback or clean */
ibool* all) /*!< in/out: FALSE=roll back dictionary transactions; ibool* all) /*!< in/out: FALSE=roll back dictionary and
statistics table transactions;
TRUE=roll back all non-PREPARED transactions */ TRUE=roll back all non-PREPARED transactions */
{ {
ut_ad(trx_sys_mutex_own()); ut_ad(trx_sys_mutex_own());
...@@ -713,7 +714,8 @@ trx_rollback_resurrected( ...@@ -713,7 +714,8 @@ trx_rollback_resurrected(
} }
trx_mutex_exit(trx); trx_mutex_exit(trx);
if (*all || trx_get_dict_operation(trx) != TRX_DICT_OP_NONE) { if (*all || trx_get_dict_operation(trx) != TRX_DICT_OP_NONE
|| trx->has_stats_table_lock()) {
trx_sys_mutex_exit(); trx_sys_mutex_exit();
trx_rollback_active(trx); trx_rollback_active(trx);
if (trx->error_state != DB_SUCCESS) { if (trx->error_state != DB_SUCCESS) {
......
...@@ -3072,3 +3072,16 @@ trx_set_rw_mode( ...@@ -3072,3 +3072,16 @@ trx_set_rw_mode(
mutex_exit(&trx_sys->mutex); mutex_exit(&trx_sys->mutex);
} }
bool trx_t::has_stats_table_lock() const
{
for (lock_list::const_iterator it= lock.table_locks.begin(),
end= lock.table_locks.end(); it != end; ++it)
{
const lock_t *lock= *it;
if (lock && lock->un_member.tab_lock.table->is_stats_table())
return true;
}
return false;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment