Commit 2ad007df authored by Annamalai Gurusami's avatar Annamalai Gurusami

Bug #14669848 CRASH DURING ALTER MAKES ORIGINAL TABLE INACCESSIBLE

When a new primary key is added to an InnoDB table, then the following
steps are taken by InnoDB plugin:

.  let t1 be the original table.
.  a temporary table t1@00231 will be created by cloning t1.
.  all data will be copied from t1 to t1@00231.
.  rename t1 to t1@00232.
.  rename t1@00231 to t1.
.  drop t1@00232.

The rename and drop operations involve file operations.  But file operations
cannot be rolled back.  So in row_merge_rename_tables(), just after doing data
dictionary update and before doing any file operations, generate redo logs
for file operations and commit the transaction.  This will ensure that any
crash after this commit, the table is still recoverable by moving .ibd and
.frm files.  Manual recovery is required.

During recovery, the rename file operation redo logs are processed.
Previously this was being ignored.

rb://1460 approved by Marko Makela.
parent 14dfe6fc
......@@ -2619,7 +2619,7 @@ retry:
mutex_exit(&fil_system->mutex);
#ifndef UNIV_HOTBACKUP
if (success) {
if (success && !recv_recovery_on) {
mtr_t mtr;
mtr_start(&mtr);
......@@ -4853,3 +4853,28 @@ fil_close(void)
fil_system = NULL;
}
/****************************************************************//**
Generate redo logs for swapping two .ibd files */
UNIV_INTERN
void
fil_mtr_rename_log(
/*===============*/
ulint old_space_id, /*!< in: tablespace id of the old
table. */
const char* old_name, /*!< in: old table name */
ulint new_space_id, /*!< in: tablespace id of the new
table */
const char* new_name, /*!< in: new table name */
const char* tmp_name) /*!< in: temp table name used while
swapping */
{
mtr_t mtr;
mtr_start(&mtr);
fil_op_write_log(MLOG_FILE_RENAME, old_space_id,
0, 0, old_name, tmp_name, &mtr);
fil_op_write_log(MLOG_FILE_RENAME, new_space_id,
0, 0, new_name, old_name, &mtr);
mtr_commit(&mtr);
}
......@@ -726,6 +726,21 @@ fil_tablespace_is_being_deleted(
/*============================*/
ulint id); /*!< in: space id */
/****************************************************************//**
Generate redo logs for swapping two .ibd files */
UNIV_INTERN
void
fil_mtr_rename_log(
/*===============*/
ulint old_space_id, /*!< in: tablespace id of the old
table. */
const char* old_name, /*!< in: old table name */
ulint new_space_id, /*!< in: tablespace id of the new
table */
const char* new_name, /*!< in: new table name */
const char* tmp_name); /*!< in: temp table name used while
swapping */
typedef struct fil_space_struct fil_space_t;
#endif
......@@ -955,8 +955,11 @@ recv_parse_or_apply_log_rec_body(
not NULL, then the log record is
applied to the page, and the log
record should be complete then */
mtr_t* mtr) /*!< in: mtr or NULL; should be non-NULL
mtr_t* mtr, /*!< in: mtr or NULL; should be non-NULL
if and only if block is non-NULL */
ulint space_id)
/*!< in: tablespace id obtained by
parsing initial log record */
{
dict_index_t* index = NULL;
page_t* page;
......@@ -1228,8 +1231,11 @@ recv_parse_or_apply_log_rec_body(
ut_ad(!page || page_type != FIL_PAGE_TYPE_ALLOCATED);
ptr = mlog_parse_string(ptr, end_ptr, page, page_zip);
break;
case MLOG_FILE_CREATE:
case MLOG_FILE_RENAME:
ptr = fil_op_log_parse_or_replay(ptr, end_ptr, type,
space_id, 0);
break;
case MLOG_FILE_CREATE:
case MLOG_FILE_DELETE:
case MLOG_FILE_CREATE2:
ptr = fil_op_log_parse_or_replay(ptr, end_ptr, type, 0, 0);
......@@ -1601,7 +1607,8 @@ recv_recover_page_func(
recv_parse_or_apply_log_rec_body(recv->type, buf,
buf + recv->len,
block, &mtr);
block, &mtr,
recv_addr->space);
end_lsn = recv->start_lsn + recv->len;
mach_write_ull(FIL_PAGE_LSN + page, end_lsn);
......@@ -2067,7 +2074,7 @@ recv_parse_log_rec(
#endif /* UNIV_LOG_LSN_DEBUG */
new_ptr = recv_parse_or_apply_log_rec_body(*type, new_ptr, end_ptr,
NULL, NULL);
NULL, NULL, *space);
if (UNIV_UNLIKELY(new_ptr == NULL)) {
return(0);
......
......@@ -2418,6 +2418,28 @@ row_merge_rename_tables(
goto err_exit;
}
/* Generate the redo logs for file operations */
fil_mtr_rename_log(old_table->space, old_name,
new_table->space, new_table->name, tmp_name);
/* What if the redo logs are flushed to disk here? This is
tested with following crash point */
DBUG_EXECUTE_IF("bug14669848_precommit", log_buffer_flush_to_disk();
DBUG_SUICIDE(););
/* File operations cannot be rolled back. So, before proceeding
with file operations, commit the dictionary changes.*/
trx_commit_for_mysql(trx);
/* If server crashes here, the dictionary in InnoDB and MySQL
will differ. The .ibd files and the .frm files must be swapped
manually by the administrator. No loss of data. */
DBUG_EXECUTE_IF("bug14669848", DBUG_SUICIDE(););
/* Ensure that the redo logs are flushed to disk. The config
innodb_flush_log_at_trx_commit must not affect this. */
log_buffer_flush_to_disk();
/* The following calls will also rename the .ibd data files if
the tables are stored in a single-table tablespace */
......
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