Skip to content
Permalink
Browse files
MDEV-17158 TRUNCATE is not atomic after MDEV-13564
  • Loading branch information
dr-m committed Sep 10, 2018
2 parents 8618c58 + 75f8e86 commit b02c722
Show file tree
Hide file tree
Showing 16 changed files with 187 additions and 103 deletions.
@@ -11,4 +11,3 @@
##############################################################################

create-index-debug : MDEV-13680 InnoDB may crash when btr_page_alloc() fails
truncate_crash : MDEV-17158 log_write_up_to() sometimes fails
@@ -1,14 +1,14 @@
FLUSH TABLES;
CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO t1 SET a=1;
INSERT INTO t1 VALUES (1),(2);
connect wait,localhost,root,,test;
SET DEBUG_SYNC='after_trx_committed_in_memory SIGNAL c WAIT_FOR ever';
SET DEBUG_SYNC='before_trx_state_committed_in_memory SIGNAL c WAIT_FOR ever';
TRUNCATE TABLE t1;
connection default;
SET DEBUG_SYNC='now WAIT_FOR c';
disconnect wait;
SELECT * FROM t1;
a
1
SELECT COUNT(*) FROM t1;
COUNT(*)
0
TRUNCATE TABLE t1;
DROP TABLE t1;
@@ -5,10 +5,10 @@

FLUSH TABLES;
CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO t1 SET a=1;
INSERT INTO t1 VALUES (1),(2);

connect (wait,localhost,root,,test);
SET DEBUG_SYNC='after_trx_committed_in_memory SIGNAL c WAIT_FOR ever';
SET DEBUG_SYNC='before_trx_state_committed_in_memory SIGNAL c WAIT_FOR ever';
send TRUNCATE TABLE t1;

connection default;
@@ -17,6 +17,7 @@ SET DEBUG_SYNC='now WAIT_FOR c';
--source include/restart_mysqld.inc
disconnect wait;

SELECT * FROM t1;
--replace_result 2 0
SELECT COUNT(*) FROM t1;
TRUNCATE TABLE t1;
DROP TABLE t1;
@@ -38,6 +38,8 @@ Created 1/8/1996 Heikki Tuuri
#include "row0mysql.h"
#include "pars0pars.h"
#include "trx0roll.h"
#include "trx0rseg.h"
#include "trx0undo.h"
#include "ut0vec.h"
#include "dict0priv.h"
#include "fts0priv.h"
@@ -370,7 +372,33 @@ dict_build_table_def_step(

ut_ad(DICT_TF_GET_ZIP_SSIZE(table->flags) == 0
|| dict_table_has_atomic_blobs(table));

trx_t* trx = thr_get_trx(thr);
ut_ad(trx->table_id);
mtr_t mtr;
trx_undo_t* undo = trx->rsegs.m_redo.undo;
if (undo && !undo->table_id
&& trx_get_dict_operation(trx) == TRX_DICT_OP_TABLE) {
/* This must be a TRUNCATE operation where
the empty table is created after the old table
was renamed. Be sure to mark the transaction
associated with the new empty table, so that
we can remove it on recovery. */
mtr.start();
undo->table_id = trx->table_id;
undo->dict_operation = TRUE;
page_t* page = trx_undo_page_get(
page_id_t(trx->rsegs.m_redo.rseg->space->id,
undo->hdr_page_no),
&mtr);
mlog_write_ulint(page + undo->hdr_offset
+ TRX_UNDO_DICT_TRANS,
TRUE, MLOG_1BYTE, &mtr);
mlog_write_ull(page + undo->hdr_offset
+ TRX_UNDO_TABLE_ID,
trx->table_id, &mtr);
mtr.commit();
log_write_up_to(mtr.commit_lsn(), true);
}
/* Get a new tablespace ID */
ulint space_id;
dict_hdr_get_new_id(NULL, NULL, &space_id, table, false);
@@ -381,7 +409,7 @@ dict_build_table_def_step(
);

if (space_id == ULINT_UNDEFINED) {
return(DB_ERROR);
return DB_ERROR;
}

/* Determine the tablespace flags. */
@@ -416,7 +444,6 @@ dict_build_table_def_step(
}

table->space_id = space_id;
mtr_t mtr;
mtr.start();
mtr.set_named_space(table->space);
fsp_header_init(table->space, FIL_IBD_FILE_INITIAL_SIZE, &mtr);
@@ -426,8 +453,6 @@ dict_build_table_def_step(
!= REC_FORMAT_COMPRESSED);
table->space = fil_system.sys_space;
table->space_id = TRX_SYS_SPACE;
DBUG_EXECUTE_IF("ib_ddl_crash_during_tablespace_alloc",
DBUG_SUICIDE(););
}

ins_node_set_new_row(node->tab_def,
@@ -1556,9 +1556,14 @@ dict_table_rename_in_cache(
/*=======================*/
dict_table_t* table, /*!< in/out: table */
const char* new_name, /*!< in: new name */
ibool rename_also_foreigns)/*!< in: in ALTER TABLE we want
bool rename_also_foreigns,
/*!< in: in ALTER TABLE we want
to preserve the original table name
in constraints which reference it */
bool replace_new_file)
/*!< in: whether to replace the
file with the new name
(as part of rolling back TRUNCATE) */
{
dberr_t err;
dict_foreign_t* foreign;
@@ -1658,7 +1663,8 @@ dict_table_rename_in_cache(
}

/* New filepath must not exist. */
err = table->space->rename(new_name, new_path, true);
err = table->space->rename(new_name, new_path, true,
replace_new_file);
ut_free(new_path);

/* If the tablespace is remote, a new .isl file was created
@@ -74,12 +74,14 @@ if that the old filepath exists and the new filepath does not exist.
@param[in] old_path old filepath
@param[in] new_path new filepath
@param[in] is_discarded whether the tablespace is discarded
@param[in] replace_new whether to ignore the existence of new_path
@return innodb error code */
static dberr_t
fil_rename_tablespace_check(
const char* old_path,
const char* new_path,
bool is_discarded);
bool is_discarded,
bool replace_new = false);
/** Rename a single-table tablespace.
The tablespace must exist in the memory cache.
@param[in] id tablespace identifier
@@ -2866,12 +2868,14 @@ if that the old filepath exists and the new filepath does not exist.
@param[in] old_path old filepath
@param[in] new_path new filepath
@param[in] is_discarded whether the tablespace is discarded
@param[in] replace_new whether to ignore the existence of new_path
@return innodb error code */
static dberr_t
fil_rename_tablespace_check(
const char* old_path,
const char* new_path,
bool is_discarded)
bool is_discarded,
bool replace_new)
{
bool exists = false;
os_file_type_t ftype;
@@ -2887,25 +2891,58 @@ fil_rename_tablespace_check(
}

exists = false;
if (!os_file_status(new_path, &exists, &ftype) || exists) {
if (os_file_status(new_path, &exists, &ftype) && !exists) {
return DB_SUCCESS;
}

if (!replace_new) {
ib::error() << "Cannot rename '" << old_path
<< "' to '" << new_path
<< "' because the target file exists."
" Remove the target file and try again.";
return(DB_TABLESPACE_EXISTS);
}

/* This must be during the ROLLBACK of TRUNCATE TABLE.
Because InnoDB only allows at most one data dictionary
transaction at a time, and because this incomplete TRUNCATE
would have created a new tablespace file, we must remove
a possibly existing tablespace that is associated with the
new tablespace file. */
retry:
mutex_enter(&fil_system.mutex);
for (fil_space_t* space = UT_LIST_GET_FIRST(fil_system.space_list);
space; space = UT_LIST_GET_NEXT(space_list, space)) {
ulint id = space->id;
if (id && id < SRV_LOG_SPACE_FIRST_ID
&& space->purpose == FIL_TYPE_TABLESPACE
&& !strcmp(new_path,
UT_LIST_GET_FIRST(space->chain)->name)) {
ib::info() << "TRUNCATE rollback: " << id
<< "," << new_path;
mutex_exit(&fil_system.mutex);
dberr_t err = fil_delete_tablespace(id);
if (err != DB_SUCCESS) {
return err;
}
goto retry;
}
}
mutex_exit(&fil_system.mutex);
fil_delete_file(new_path);

return(DB_SUCCESS);
}

dberr_t fil_space_t::rename(const char* name, const char* path, bool log)
dberr_t fil_space_t::rename(const char* name, const char* path, bool log,
bool replace)
{
ut_ad(UT_LIST_GET_LEN(chain) == 1);
ut_ad(!is_system_tablespace(id));

if (log) {
dberr_t err = fil_rename_tablespace_check(
chain.start->name, path, false);
chain.start->name, path, false, replace);
if (err != DB_SUCCESS) {
return(err);
}

0 comments on commit b02c722

Please sign in to comment.