Commit dbd6c6dc authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-25683 Atomic DDL: With innodb_force_recovery=3 InnoDB: Trying to load...

MDEV-25683 Atomic DDL: With innodb_force_recovery=3 InnoDB: Trying to load index but the index tree has been freed

The purpose of the parameter innodb_force_recovery is to allow some
data to be dumped from a corrupted database. Its values used to be
as follows:

innodb_force_recovery=0: normal (default)

innodb_force_recovery=1: ignore (skip log for) corrupted pages or
missing data files when applying the redo log

innodb_force_recovery=2: additionally, disable background tasks
(such as the purge of committed undo logs)

innodb_force_recovery=3: additionally, disable the rollback of
recovered incomplete (not committed or XA PREPARE) transactions

innodb_force_recovery=4: same as 3 (since MDEV-19514 in MariaDB 10.5)

innodb_force_recovery=5: additionally, do not process any undo log,
disallow any writes, and force READ UNCOMMITTED isolation level

innodb_force_recovery=6: additionally, pretend that ib_logfile0 does
not exist (prevent any recovery). Never use this!

The bad thing that happens with innodb_force_recovery=3 and
innodb_force_recovery=4 is that also the rollback of any recovered
DDL transaction will be skipped. This would break the DDL log recovery
that was introduced in MDEV-17567.

For one data directory sample, the DDL log recovery would hangs due to
a conflict on the InnoDB SYS_TABLES table, because the lock holder
transaction was not rolled back due to innodb_force_recovery=3.

Fix: Make innodb_force_recovery=3 skip the DML transaction rollback only,
and make innodb_force_recovery=4 (renamed to SRV_FORCE_NO_DDL_UNDO)
behave like innodb_force_recovery=3 used to (skip the rollback of all
recovered transactions, both DML and DDL).

Startup with innodb_force_recovery=4 will be unaffected by this change.
(There may be hangs, possibly preceded by messages about failing to
load an index.)

Side note: With innodb_force_recovery=5, any DDL log for InnoDB tables
will be essentially ignored by InnoDB, but the server will start up.
parent ea45f0eb
...@@ -3812,7 +3812,7 @@ dict_stats_update( ...@@ -3812,7 +3812,7 @@ dict_stats_update(
if (!table->is_readable()) { if (!table->is_readable()) {
return (dict_stats_report_error(table)); return (dict_stats_report_error(table));
} else if (srv_force_recovery > SRV_FORCE_NO_IBUF_MERGE) { } else if (srv_force_recovery >= SRV_FORCE_NO_UNDO_LOG_SCAN) {
/* If we have set a high innodb_force_recovery level, do /* If we have set a high innodb_force_recovery level, do
not calculate statistics, as a badly corrupted index can not calculate statistics, as a badly corrupted index can
cause a crash in it. */ cause a crash in it. */
......
...@@ -5845,7 +5845,7 @@ initialize_auto_increment(dict_table_t* table, const Field* field) ...@@ -5845,7 +5845,7 @@ initialize_auto_increment(dict_table_t* table, const Field* field)
table->persistent_autoinc without table->persistent_autoinc without
autoinc_mutex protection, and there might be multiple autoinc_mutex protection, and there might be multiple
ha_innobase::open() executing concurrently. */ ha_innobase::open() executing concurrently. */
} else if (srv_force_recovery > SRV_FORCE_NO_IBUF_MERGE) { } else if (srv_force_recovery >= SRV_FORCE_NO_UNDO_LOG_SCAN) {
/* If the recovery level is set so high that writes /* If the recovery level is set so high that writes
are disabled we force the AUTOINC counter to 0 are disabled we force the AUTOINC counter to 0
value effectively disabling writes to the table. value effectively disabling writes to the table.
...@@ -14872,7 +14872,7 @@ ha_innobase::info_low( ...@@ -14872,7 +14872,7 @@ ha_innobase::info_low(
} }
} }
if (srv_force_recovery > SRV_FORCE_NO_IBUF_MERGE) { if (srv_force_recovery >= SRV_FORCE_NO_UNDO_LOG_SCAN) {
goto func_exit; goto func_exit;
......
...@@ -562,11 +562,9 @@ enum { ...@@ -562,11 +562,9 @@ enum {
SRV_FORCE_NO_BACKGROUND = 2, /*!< prevent the main thread from SRV_FORCE_NO_BACKGROUND = 2, /*!< prevent the main thread from
running: if a crash would occur running: if a crash would occur
in purge, this prevents it */ in purge, this prevents it */
SRV_FORCE_NO_TRX_UNDO = 3, /*!< do not run trx rollback after SRV_FORCE_NO_TRX_UNDO = 3, /*!< do not run DML rollback after
recovery */ recovery */
SRV_FORCE_NO_IBUF_MERGE = 4, /*!< prevent also ibuf operations: SRV_FORCE_NO_DDL_UNDO = 4, /*!< prevent also DDL rollback */
if they would cause a crash, better
not do them */
SRV_FORCE_NO_UNDO_LOG_SCAN = 5, /*!< do not look at undo logs when SRV_FORCE_NO_UNDO_LOG_SCAN = 5, /*!< do not look at undo logs when
starting the database: InnoDB will starting the database: InnoDB will
treat even incomplete transactions treat even incomplete transactions
......
...@@ -1065,7 +1065,7 @@ dberr_t srv_start(bool create_new_db) ...@@ -1065,7 +1065,7 @@ dberr_t srv_start(bool create_new_db)
} }
high_level_read_only = srv_read_only_mode high_level_read_only = srv_read_only_mode
|| srv_force_recovery > SRV_FORCE_NO_IBUF_MERGE || srv_force_recovery >= SRV_FORCE_NO_UNDO_LOG_SCAN
|| srv_sys_space.created_new_raw(); || srv_sys_space.created_new_raw();
srv_started_redo = false; srv_started_redo = false;
...@@ -1704,7 +1704,7 @@ dberr_t srv_start(bool create_new_db) ...@@ -1704,7 +1704,7 @@ dberr_t srv_start(bool create_new_db)
if (!create_new_db) { if (!create_new_db) {
ut_ad(high_level_read_only ut_ad(high_level_read_only
|| srv_force_recovery <= SRV_FORCE_NO_IBUF_MERGE); || srv_force_recovery < SRV_FORCE_NO_UNDO_LOG_SCAN);
/* Validate a few system page types that were left /* Validate a few system page types that were left
uninitialized before MySQL or MariaDB 5.5. */ uninitialized before MySQL or MariaDB 5.5. */
...@@ -1745,7 +1745,7 @@ dberr_t srv_start(bool create_new_db) ...@@ -1745,7 +1745,7 @@ dberr_t srv_start(bool create_new_db)
should guarantee that there is at most one data should guarantee that there is at most one data
dictionary transaction active at a time. */ dictionary transaction active at a time. */
if (!high_level_read_only if (!high_level_read_only
&& srv_force_recovery < SRV_FORCE_NO_TRX_UNDO) { && srv_force_recovery <= SRV_FORCE_NO_TRX_UNDO) {
/* If the following call is ever removed, the /* If the following call is ever removed, the
first-time ha_innobase::open() must hold (or first-time ha_innobase::open() must hold (or
acquire and release) a table lock that acquire and release) a table lock that
...@@ -1759,7 +1759,7 @@ dberr_t srv_start(bool create_new_db) ...@@ -1759,7 +1759,7 @@ dberr_t srv_start(bool create_new_db)
trx_rollback_recovered(false); trx_rollback_recovered(false);
} }
if (srv_force_recovery <= SRV_FORCE_NO_IBUF_MERGE) { if (srv_force_recovery < SRV_FORCE_NO_UNDO_LOG_SCAN) {
/* The following call is necessary for the insert /* The following call is necessary for the insert
buffer to work with multiple tablespaces. We must buffer to work with multiple tablespaces. We must
know the mapping between space id's and .ibd file know the mapping between space id's and .ibd file
......
...@@ -709,7 +709,8 @@ void trx_rollback_recovered(bool all) ...@@ -709,7 +709,8 @@ void trx_rollback_recovered(bool all)
{ {
std::vector<trx_t*> trx_list; std::vector<trx_t*> trx_list;
ut_a(srv_force_recovery < SRV_FORCE_NO_TRX_UNDO); ut_a(srv_force_recovery <
(all ? SRV_FORCE_NO_TRX_UNDO : SRV_FORCE_NO_DDL_UNDO));
/* /*
Collect list of recovered ACTIVE transaction ids first. Once collected, no Collect list of recovered ACTIVE transaction ids first. Once collected, no
......
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