2016-08-12 10:17:45 +02:00
|
|
|
/*****************************************************************************
|
|
|
|
|
2018-08-02 07:19:57 +02:00
|
|
|
Copyright (c) 2013, 2018, Oracle and/or its affiliates. All Rights Reserved.
|
2018-03-11 22:34:23 +01:00
|
|
|
Copyright (c) 2017, 2018, MariaDB Corporation.
|
2016-08-12 10:17:45 +02:00
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it under
|
|
|
|
the terms of the GNU General Public License as published by the Free Software
|
|
|
|
Foundation; version 2 of the License.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful, but WITHOUT
|
|
|
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
|
|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License along with
|
|
|
|
this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
|
|
|
|
|
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
/**************************************************//**
|
|
|
|
@file row/row0trunc.cc
|
|
|
|
TRUNCATE implementation
|
|
|
|
|
|
|
|
Created 2013-04-12 Sunny Bains
|
|
|
|
*******************************************************/
|
|
|
|
|
|
|
|
#include "row0mysql.h"
|
|
|
|
#include "pars0pars.h"
|
2018-11-19 10:42:14 +01:00
|
|
|
#include "btr0pcur.h"
|
2016-08-12 10:17:45 +02:00
|
|
|
#include "dict0crea.h"
|
|
|
|
#include "dict0boot.h"
|
2018-11-29 11:53:44 +01:00
|
|
|
#include "dict0load.h"
|
2016-08-12 10:17:45 +02:00
|
|
|
#include "dict0stats.h"
|
|
|
|
#include "dict0stats_bg.h"
|
|
|
|
#include "lock0lock.h"
|
|
|
|
#include "fts0fts.h"
|
|
|
|
#include "srv0start.h"
|
|
|
|
#include "row0trunc.h"
|
|
|
|
#include "os0file.h"
|
2018-03-11 22:34:23 +01:00
|
|
|
#include "que0que.h"
|
|
|
|
#include "trx0undo.h"
|
2016-08-12 10:17:45 +02:00
|
|
|
|
MDEV-12219 Discard temporary undo logs at transaction commit
Starting with MySQL 5.7, temporary tables in InnoDB are handled
differently from persistent tables. Because temporary tables are
private to a connection, concurrency control and multi-versioning
(MVCC) are not applicable. For performance reasons, purge is
disabled as well. Rollback is supported for temporary tables;
that is why we have the temporary undo logs in the first place.
Because MVCC and purge are disabled for temporary tables, we should
discard all temporary undo logs already at transaction commit,
just like we discard the persistent insert_undo logs. Before this
change, update_undo logs were being preserved.
trx_temp_undo_t: A wrapper for temporary undo logs, comprising
a rollback segment and a single temporary undo log.
trx_rsegs_t::m_noredo: Use trx_temp_undo_t.
(Instead of insert_undo, update_undo, there will be a single undo.)
trx_is_noredo_rseg_updated(), trx_is_rseg_assigned(): Remove.
trx_undo_add_page(): Remove the parameter undo_ptr.
Acquire and release the rollback segment mutex inside the function.
trx_undo_free_last_page(): Remove the parameter trx.
trx_undo_truncate_end(): Remove the parameter trx, and add the
parameter is_temp. Clean up the code a bit.
trx_undo_assign_undo(): Split the parameter undo_ptr into rseg, undo.
trx_undo_commit_cleanup(): Renamed from trx_undo_insert_cleanup().
Replace the parameter undo_ptr with undo.
This will discard the temporary undo or insert_undo log at
commit/rollback.
trx_purge_add_update_undo_to_history(), trx_undo_update_cleanup():
Remove 3 parameters. Always operate on the persistent update_undo.
trx_serialise(): Renamed from trx_serialisation_number_get().
trx_write_serialisation_history(): Simplify the code flow.
If there are no persistent changes, do not update MONITOR_TRX_COMMIT_UNDO.
trx_commit_in_memory(): Simplify the logic, and add assertions.
trx_undo_page_report_modify(): Keep a direct reference to the
persistent update_undo log.
trx_undo_report_row_operation(): Simplify some code.
Always assign TRX_UNDO_INSERT for temporary undo logs.
trx_prepare_low(): Keep only one parameter. Prepare all 3 undo logs.
trx_roll_try_truncate(): Remove the parameter undo_ptr.
Try to truncate all 3 undo logs of the transaction.
trx_roll_pop_top_rec_of_trx_low(): Remove.
trx_roll_pop_top_rec_of_trx(): Remove the redundant parameter
trx->roll_limit. Clear roll_limit when exhausting the undo logs.
Consider all 3 undo logs at once, prioritizing the persistent
undo logs.
row_undo(): Minor cleanup. Let trx_roll_pop_top_rec_of_trx()
reset the trx->roll_limit.
2017-03-09 22:20:51 +01:00
|
|
|
/* FIXME: For temporary tables, use a simple approach of btr_free()
|
|
|
|
and btr_create() of each index tree. */
|
|
|
|
|
|
|
|
/* FIXME: For persistent tables, remove this code in MDEV-11655
|
|
|
|
and use a combination of the transactional DDL log to make atomic the
|
|
|
|
low-level operations ha_innobase::delete_table(), ha_innobase::create(). */
|
|
|
|
|
2016-08-12 10:17:45 +02:00
|
|
|
bool truncate_t::s_fix_up_active = false;
|
|
|
|
truncate_t::tables_t truncate_t::s_tables;
|
|
|
|
truncate_t::truncated_tables_t truncate_t::s_truncated_tables;
|
|
|
|
|
MDEV-13564: Implement innodb_unsafe_truncate=ON for compatibility
While MariaDB Server 10.2 is not really guaranteed to be compatible
with Percona XtraBackup 2.4 (for example, the MySQL 5.7 undo log format
change that could be present in XtraBackup, but was reverted from
MariaDB in MDEV-12289), we do not want to disrupt users who have
deployed xtrabackup and MariaDB Server 10.2 in their environments.
With this change, MariaDB 10.2 will continue to use the backup-unsafe
TRUNCATE TABLE code, so that neither the undo log nor the redo log
formats will change in an incompatible way.
Undo tablespace truncation will keep using the redo log only. Recovery
or backup with old code will fail to shrink the undo tablespace files,
but the contents will be recovered just fine.
In the MariaDB Server 10.2 series only, we introduce the configuration
parameter innodb_unsafe_truncate and make it ON by default. To allow
MariaDB Backup (mariabackup) to work properly with TRUNCATE TABLE
operations, use loose_innodb_unsafe_truncate=OFF.
MariaDB Server 10.3.10 and later releases will always use the
backup-safe TRUNCATE TABLE, and this parameter will not be
added there.
recv_recovery_rollback_active(): Skip row_mysql_drop_garbage_tables()
unless innodb_unsafe_truncate=OFF. It is too unsafe to drop orphan
tables if RENAME operations are not transactional within InnoDB.
LOG_HEADER_FORMAT_10_3: Replaces LOG_HEADER_FORMAT_CURRENT.
log_init(), log_group_file_header_flush(),
srv_prepare_to_delete_redo_log_files(),
innobase_start_or_create_for_mysql(): Choose the redo log format
and subformat based on the value of innodb_unsafe_truncate.
2018-10-10 18:19:29 +02:00
|
|
|
static const byte magic[] = { 0x01, 0xf3, 0xa1, 0x20 };
|
|
|
|
|
2016-08-12 10:17:45 +02:00
|
|
|
/**
|
|
|
|
Iterator over the the raw records in an index, doesn't support MVCC. */
|
|
|
|
class IndexIterator {
|
|
|
|
|
|
|
|
public:
|
|
|
|
/**
|
|
|
|
Iterate over an indexes records
|
|
|
|
@param index index to iterate over */
|
|
|
|
explicit IndexIterator(dict_index_t* index)
|
|
|
|
:
|
|
|
|
m_index(index)
|
|
|
|
{
|
|
|
|
/* Do nothing */
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Search for key. Position the cursor on a record GE key.
|
|
|
|
@return DB_SUCCESS or error code. */
|
|
|
|
dberr_t search(dtuple_t& key, bool noredo)
|
|
|
|
{
|
|
|
|
mtr_start(&m_mtr);
|
|
|
|
|
|
|
|
if (noredo) {
|
|
|
|
mtr_set_log_mode(&m_mtr, MTR_LOG_NO_REDO);
|
|
|
|
}
|
|
|
|
|
|
|
|
btr_pcur_open_on_user_rec(
|
|
|
|
m_index,
|
|
|
|
&key,
|
|
|
|
PAGE_CUR_GE,
|
|
|
|
BTR_MODIFY_LEAF,
|
|
|
|
&m_pcur, &m_mtr);
|
|
|
|
|
|
|
|
return(DB_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Iterate over all the records
|
|
|
|
@return DB_SUCCESS or error code */
|
|
|
|
template <typename Callback>
|
|
|
|
dberr_t for_each(Callback& callback)
|
|
|
|
{
|
|
|
|
dberr_t err = DB_SUCCESS;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
|
|
|
|
if (!btr_pcur_is_on_user_rec(&m_pcur)
|
|
|
|
|| !callback.match(&m_mtr, &m_pcur)) {
|
|
|
|
|
|
|
|
/* The end of of the index has been reached. */
|
|
|
|
err = DB_END_OF_INDEX;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
rec_t* rec = btr_pcur_get_rec(&m_pcur);
|
|
|
|
|
|
|
|
if (!rec_get_deleted_flag(rec, FALSE)) {
|
|
|
|
|
|
|
|
err = callback(&m_mtr, &m_pcur);
|
|
|
|
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
btr_pcur_move_to_next_user_rec(&m_pcur, &m_mtr);
|
|
|
|
}
|
|
|
|
|
|
|
|
btr_pcur_close(&m_pcur);
|
|
|
|
mtr_commit(&m_mtr);
|
|
|
|
|
|
|
|
return(err == DB_END_OF_INDEX ? DB_SUCCESS : err);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
// Disable copying
|
|
|
|
IndexIterator(const IndexIterator&);
|
|
|
|
IndexIterator& operator=(const IndexIterator&);
|
|
|
|
|
|
|
|
private:
|
|
|
|
mtr_t m_mtr;
|
|
|
|
btr_pcur_t m_pcur;
|
|
|
|
dict_index_t* m_index;
|
|
|
|
};
|
|
|
|
|
|
|
|
/** SysIndex table iterator, iterate over records for a table. */
|
|
|
|
class SysIndexIterator {
|
|
|
|
|
|
|
|
public:
|
|
|
|
/**
|
|
|
|
Iterate over all the records that match the table id.
|
|
|
|
@return DB_SUCCESS or error code */
|
|
|
|
template <typename Callback>
|
|
|
|
dberr_t for_each(Callback& callback) const
|
|
|
|
{
|
|
|
|
dict_index_t* sys_index;
|
|
|
|
byte buf[DTUPLE_EST_ALLOC(1)];
|
|
|
|
dtuple_t* tuple =
|
|
|
|
dtuple_create_from_mem(buf, sizeof(buf), 1, 0);
|
|
|
|
dfield_t* dfield = dtuple_get_nth_field(tuple, 0);
|
|
|
|
|
|
|
|
dfield_set_data(
|
|
|
|
dfield,
|
|
|
|
callback.table_id(),
|
|
|
|
sizeof(*callback.table_id()));
|
|
|
|
|
|
|
|
sys_index = dict_table_get_first_index(dict_sys->sys_indexes);
|
|
|
|
|
|
|
|
dict_index_copy_types(tuple, sys_index, 1);
|
|
|
|
|
|
|
|
IndexIterator iterator(sys_index);
|
|
|
|
|
|
|
|
/* Search on the table id and position the cursor
|
|
|
|
on GE table_id. */
|
|
|
|
iterator.search(*tuple, callback.get_logging_status());
|
|
|
|
|
|
|
|
return(iterator.for_each(callback));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/** Generic callback abstract class. */
|
|
|
|
class Callback
|
|
|
|
{
|
|
|
|
|
|
|
|
public:
|
|
|
|
/**
|
|
|
|
Constructor
|
|
|
|
@param table_id id of the table being operated.
|
|
|
|
@param noredo if true turn off logging. */
|
|
|
|
Callback(table_id_t table_id, bool noredo)
|
|
|
|
:
|
|
|
|
m_id(),
|
|
|
|
m_noredo(noredo)
|
|
|
|
{
|
|
|
|
/* Convert to storage byte order. */
|
|
|
|
mach_write_to_8(&m_id, table_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Destructor */
|
|
|
|
virtual ~Callback()
|
|
|
|
{
|
|
|
|
/* Do nothing */
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
@param mtr mini-transaction covering the iteration
|
|
|
|
@param pcur persistent cursor used for iteration
|
|
|
|
@return true if the table id column matches. */
|
|
|
|
bool match(mtr_t* mtr, btr_pcur_t* pcur) const
|
|
|
|
{
|
|
|
|
ulint len;
|
|
|
|
const byte* field;
|
|
|
|
rec_t* rec = btr_pcur_get_rec(pcur);
|
|
|
|
|
|
|
|
field = rec_get_nth_field_old(
|
|
|
|
rec, DICT_FLD__SYS_INDEXES__TABLE_ID, &len);
|
|
|
|
|
|
|
|
ut_ad(len == 8);
|
|
|
|
|
|
|
|
return(memcmp(&m_id, field, len) == 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
@return pointer to table id storage format buffer */
|
|
|
|
const table_id_t* table_id() const
|
|
|
|
{
|
|
|
|
return(&m_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
@return return if logging needs to be turned off. */
|
|
|
|
bool get_logging_status() const
|
|
|
|
{
|
|
|
|
return(m_noredo);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
// Disably copying
|
|
|
|
Callback(const Callback&);
|
|
|
|
Callback& operator=(const Callback&);
|
|
|
|
|
|
|
|
protected:
|
|
|
|
/** Table id in storage format */
|
|
|
|
table_id_t m_id;
|
|
|
|
|
|
|
|
/** Turn off logging. */
|
|
|
|
const bool m_noredo;
|
|
|
|
};
|
|
|
|
|
MDEV-13564: Implement innodb_unsafe_truncate=ON for compatibility
While MariaDB Server 10.2 is not really guaranteed to be compatible
with Percona XtraBackup 2.4 (for example, the MySQL 5.7 undo log format
change that could be present in XtraBackup, but was reverted from
MariaDB in MDEV-12289), we do not want to disrupt users who have
deployed xtrabackup and MariaDB Server 10.2 in their environments.
With this change, MariaDB 10.2 will continue to use the backup-unsafe
TRUNCATE TABLE code, so that neither the undo log nor the redo log
formats will change in an incompatible way.
Undo tablespace truncation will keep using the redo log only. Recovery
or backup with old code will fail to shrink the undo tablespace files,
but the contents will be recovered just fine.
In the MariaDB Server 10.2 series only, we introduce the configuration
parameter innodb_unsafe_truncate and make it ON by default. To allow
MariaDB Backup (mariabackup) to work properly with TRUNCATE TABLE
operations, use loose_innodb_unsafe_truncate=OFF.
MariaDB Server 10.3.10 and later releases will always use the
backup-safe TRUNCATE TABLE, and this parameter will not be
added there.
recv_recovery_rollback_active(): Skip row_mysql_drop_garbage_tables()
unless innodb_unsafe_truncate=OFF. It is too unsafe to drop orphan
tables if RENAME operations are not transactional within InnoDB.
LOG_HEADER_FORMAT_10_3: Replaces LOG_HEADER_FORMAT_CURRENT.
log_init(), log_group_file_header_flush(),
srv_prepare_to_delete_redo_log_files(),
innobase_start_or_create_for_mysql(): Choose the redo log format
and subformat based on the value of innodb_unsafe_truncate.
2018-10-10 18:19:29 +02:00
|
|
|
/**
|
|
|
|
Creates a TRUNCATE log record with space id, table name, data directory path,
|
|
|
|
tablespace flags, table format, index ids, index types, number of index fields
|
|
|
|
and index field information of the table. */
|
|
|
|
class TruncateLogger : public Callback {
|
|
|
|
|
|
|
|
public:
|
|
|
|
/**
|
|
|
|
Constructor
|
|
|
|
|
|
|
|
@param table Table to truncate
|
|
|
|
@param flags tablespace falgs */
|
|
|
|
TruncateLogger(
|
|
|
|
dict_table_t* table,
|
|
|
|
ulint flags,
|
|
|
|
table_id_t new_table_id)
|
|
|
|
:
|
|
|
|
Callback(table->id, false),
|
|
|
|
m_table(table),
|
|
|
|
m_flags(flags),
|
|
|
|
m_truncate(table->id, new_table_id, table->data_dir_path),
|
|
|
|
m_log_file_name()
|
|
|
|
{
|
|
|
|
/* Do nothing */
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Initialize Truncate Logger by constructing Truncate Log File Name.
|
|
|
|
|
|
|
|
@return DB_SUCCESS or error code. */
|
|
|
|
dberr_t init()
|
|
|
|
{
|
|
|
|
/* Construct log file name. */
|
|
|
|
ulint log_file_name_buf_sz =
|
|
|
|
strlen(srv_log_group_home_dir)
|
|
|
|
+ (22 + 22 + sizeof "ib_trunc.log");
|
|
|
|
|
|
|
|
m_log_file_name = UT_NEW_ARRAY_NOKEY(char, log_file_name_buf_sz);
|
|
|
|
if (m_log_file_name == NULL) {
|
|
|
|
return(DB_OUT_OF_MEMORY);
|
|
|
|
}
|
|
|
|
memset(m_log_file_name, 0, log_file_name_buf_sz);
|
|
|
|
|
|
|
|
strcpy(m_log_file_name, srv_log_group_home_dir);
|
|
|
|
ulint log_file_name_len = strlen(m_log_file_name);
|
|
|
|
if (m_log_file_name[log_file_name_len - 1]
|
|
|
|
!= OS_PATH_SEPARATOR) {
|
|
|
|
|
|
|
|
m_log_file_name[log_file_name_len]
|
|
|
|
= OS_PATH_SEPARATOR;
|
|
|
|
log_file_name_len = strlen(m_log_file_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
snprintf(m_log_file_name + log_file_name_len,
|
|
|
|
log_file_name_buf_sz - log_file_name_len,
|
|
|
|
"ib_%u_" IB_ID_FMT "_trunc.log",
|
|
|
|
m_table->space, m_table->id);
|
|
|
|
|
|
|
|
return(DB_SUCCESS);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Destructor */
|
|
|
|
~TruncateLogger()
|
|
|
|
{
|
|
|
|
if (m_log_file_name != NULL) {
|
|
|
|
bool exist;
|
|
|
|
os_file_delete_if_exists(
|
|
|
|
innodb_log_file_key, m_log_file_name, &exist);
|
|
|
|
UT_DELETE_ARRAY(m_log_file_name);
|
|
|
|
m_log_file_name = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
@param mtr mini-transaction covering the read
|
|
|
|
@param pcur persistent cursor used for reading
|
|
|
|
@return DB_SUCCESS or error code */
|
|
|
|
dberr_t operator()(mtr_t* mtr, btr_pcur_t* pcur);
|
|
|
|
|
|
|
|
/** Called after iteratoring over the records.
|
|
|
|
@return true if invariant satisfied. */
|
|
|
|
bool debug() const
|
|
|
|
{
|
|
|
|
/* We must find all the index entries on disk. */
|
|
|
|
return(UT_LIST_GET_LEN(m_table->indexes)
|
|
|
|
== m_truncate.indexes());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Write the TRUNCATE log
|
|
|
|
@return DB_SUCCESS or error code */
|
|
|
|
dberr_t log() const
|
|
|
|
{
|
|
|
|
dberr_t err = DB_SUCCESS;
|
|
|
|
|
|
|
|
if (m_log_file_name == 0) {
|
|
|
|
return(DB_ERROR);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ret;
|
|
|
|
os_file_t handle = os_file_create(
|
|
|
|
innodb_log_file_key, m_log_file_name,
|
|
|
|
OS_FILE_CREATE, OS_FILE_NORMAL,
|
|
|
|
OS_LOG_FILE, srv_read_only_mode, &ret);
|
|
|
|
if (!ret) {
|
|
|
|
return(DB_IO_ERROR);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ulint sz = UNIV_PAGE_SIZE;
|
|
|
|
void* buf = ut_zalloc_nokey(sz + UNIV_PAGE_SIZE);
|
|
|
|
if (buf == 0) {
|
|
|
|
os_file_close(handle);
|
|
|
|
return(DB_OUT_OF_MEMORY);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Align the memory for file i/o if we might have O_DIRECT set*/
|
|
|
|
byte* log_buf = static_cast<byte*>(
|
|
|
|
ut_align(buf, UNIV_PAGE_SIZE));
|
|
|
|
|
|
|
|
lsn_t lsn = log_get_lsn();
|
|
|
|
|
|
|
|
/* Generally loop should exit in single go but
|
|
|
|
just for those 1% of rare cases we need to assume
|
|
|
|
corner case. */
|
|
|
|
do {
|
|
|
|
/* First 4 bytes are reserved for magic number
|
|
|
|
which is currently 0. */
|
|
|
|
err = m_truncate.write(
|
|
|
|
log_buf + 4, log_buf + sz - 4,
|
|
|
|
m_table->space, m_table->name.m_name,
|
|
|
|
m_flags, m_table->flags, lsn);
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF("ib_err_trunc_oom_logging",
|
|
|
|
err = DB_FAIL;);
|
|
|
|
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
ut_ad(err == DB_FAIL);
|
|
|
|
ut_free(buf);
|
|
|
|
sz *= 2;
|
|
|
|
buf = ut_zalloc_nokey(sz + UNIV_PAGE_SIZE);
|
|
|
|
DBUG_EXECUTE_IF("ib_err_trunc_oom_logging",
|
|
|
|
ut_free(buf);
|
|
|
|
buf = 0;);
|
|
|
|
if (buf == 0) {
|
|
|
|
os_file_close(handle);
|
|
|
|
return(DB_OUT_OF_MEMORY);
|
|
|
|
}
|
|
|
|
log_buf = static_cast<byte*>(
|
|
|
|
ut_align(buf, UNIV_PAGE_SIZE));
|
|
|
|
}
|
|
|
|
|
|
|
|
} while (err != DB_SUCCESS);
|
|
|
|
|
|
|
|
dberr_t io_err;
|
|
|
|
|
|
|
|
IORequest request(IORequest::WRITE);
|
|
|
|
|
|
|
|
io_err = os_file_write(
|
|
|
|
request, m_log_file_name, handle, log_buf, 0, sz);
|
|
|
|
|
|
|
|
if (io_err != DB_SUCCESS) {
|
|
|
|
|
|
|
|
ib::error()
|
|
|
|
<< "IO: Failed to write the file size to '"
|
|
|
|
<< m_log_file_name << "'";
|
|
|
|
|
|
|
|
/* Preserve the original error code */
|
|
|
|
if (err == DB_SUCCESS) {
|
|
|
|
err = io_err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
os_file_flush(handle);
|
|
|
|
os_file_close(handle);
|
|
|
|
|
|
|
|
ut_free(buf);
|
|
|
|
|
|
|
|
/* Why we need MLOG_TRUNCATE when we have truncate_log for
|
|
|
|
recovery?
|
|
|
|
- truncate log can protect us if crash happens while truncate
|
|
|
|
is active. Once truncate is done truncate log is removed.
|
|
|
|
- If crash happens post truncate and system is yet to
|
|
|
|
checkpoint, on recovery we would see REDO records from action
|
|
|
|
before truncate (unless we explicitly checkpoint before
|
|
|
|
returning from truncate API. Costly alternative so rejected).
|
|
|
|
- These REDO records may reference a page that doesn't exist
|
|
|
|
post truncate so we need a mechanism to skip all such REDO
|
|
|
|
records. MLOG_TRUNCATE records space_id and lsn that exactly
|
|
|
|
serve the purpose.
|
|
|
|
- If checkpoint happens post truncate and crash happens post
|
|
|
|
this point then neither MLOG_TRUNCATE nor REDO record
|
|
|
|
from action before truncate are accessible. */
|
|
|
|
if (!is_system_tablespace(m_table->space)) {
|
|
|
|
mtr_t mtr;
|
|
|
|
byte* log_ptr;
|
|
|
|
|
|
|
|
mtr_start(&mtr);
|
|
|
|
|
|
|
|
log_ptr = mlog_open(&mtr, 11 + 8);
|
|
|
|
log_ptr = mlog_write_initial_log_record_low(
|
|
|
|
MLOG_TRUNCATE, m_table->space, 0,
|
|
|
|
log_ptr, &mtr);
|
|
|
|
|
|
|
|
mach_write_to_8(log_ptr, lsn);
|
|
|
|
log_ptr += 8;
|
|
|
|
|
|
|
|
mlog_close(&mtr, log_ptr);
|
|
|
|
mtr_commit(&mtr);
|
|
|
|
}
|
|
|
|
|
|
|
|
return(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Indicate completion of truncate log by writing magic-number.
|
|
|
|
File will be removed from the system but to protect against
|
|
|
|
unlink (File-System) anomalies we ensure we write magic-number. */
|
|
|
|
void done()
|
|
|
|
{
|
|
|
|
if (m_log_file_name == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ret;
|
|
|
|
os_file_t handle = os_file_create_simple_no_error_handling(
|
|
|
|
innodb_log_file_key, m_log_file_name,
|
|
|
|
OS_FILE_OPEN, OS_FILE_READ_WRITE,
|
|
|
|
srv_read_only_mode, &ret);
|
|
|
|
DBUG_EXECUTE_IF("ib_err_trunc_writing_magic_number",
|
|
|
|
os_file_close(handle);
|
|
|
|
ret = false;);
|
|
|
|
if (!ret) {
|
|
|
|
ib::error() << "Failed to open truncate log file "
|
|
|
|
<< m_log_file_name << "."
|
|
|
|
" If server crashes before truncate log is"
|
|
|
|
" removed make sure it is manually removed"
|
|
|
|
" before restarting server";
|
|
|
|
os_file_delete(innodb_log_file_key, m_log_file_name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (os_file_write(IORequest(IORequest::WRITE),
|
|
|
|
m_log_file_name, handle, magic, 0,
|
|
|
|
sizeof magic) != DB_SUCCESS) {
|
|
|
|
ib::error()
|
|
|
|
<< "IO: Failed to write the magic number to '"
|
|
|
|
<< m_log_file_name << "'";
|
|
|
|
}
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF("ib_trunc_crash_after_updating_magic_no",
|
|
|
|
DBUG_SUICIDE(););
|
|
|
|
os_file_flush(handle);
|
|
|
|
os_file_close(handle);
|
|
|
|
DBUG_EXECUTE_IF("ib_trunc_crash_after_logging_complete",
|
|
|
|
log_buffer_flush_to_disk();
|
|
|
|
os_thread_sleep(1000000);
|
|
|
|
DBUG_SUICIDE(););
|
|
|
|
os_file_delete(innodb_log_file_key, m_log_file_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
/** Lookup the index using the index id.
|
|
|
|
@return index instance if found else NULL */
|
|
|
|
const dict_index_t* find(index_id_t id) const
|
|
|
|
{
|
|
|
|
for (const dict_index_t* index = UT_LIST_GET_FIRST(
|
|
|
|
m_table->indexes);
|
|
|
|
index != NULL;
|
|
|
|
index = UT_LIST_GET_NEXT(indexes, index)) {
|
|
|
|
|
|
|
|
if (index->id == id) {
|
|
|
|
return(index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
/** Table to be truncated */
|
|
|
|
dict_table_t* m_table;
|
|
|
|
|
|
|
|
/** Tablespace flags */
|
|
|
|
ulint m_flags;
|
|
|
|
|
|
|
|
/** Collect table to truncate information */
|
|
|
|
truncate_t m_truncate;
|
|
|
|
|
|
|
|
/** Truncate log file name. */
|
|
|
|
char* m_log_file_name;
|
|
|
|
};
|
|
|
|
|
2016-08-12 10:17:45 +02:00
|
|
|
/**
|
|
|
|
Scan to find out truncate log file from the given directory path.
|
|
|
|
|
|
|
|
@param dir_path look for log directory in following path.
|
|
|
|
@param log_files cache to hold truncate log file name found.
|
|
|
|
@return DB_SUCCESS or error code. */
|
|
|
|
dberr_t
|
|
|
|
TruncateLogParser::scan(
|
|
|
|
const char* dir_path,
|
|
|
|
trunc_log_files_t& log_files)
|
|
|
|
{
|
|
|
|
os_file_dir_t dir;
|
|
|
|
os_file_stat_t fileinfo;
|
|
|
|
dberr_t err = DB_SUCCESS;
|
MDEV-13564 Mariabackup does not work with TRUNCATE
Implement undo tablespace truncation via normal redo logging.
Implement TRUNCATE TABLE as a combination of RENAME to #sql-ib name,
CREATE, and DROP.
Note: Orphan #sql-ib*.ibd may be left behind if MariaDB Server 10.2
is killed before the DROP operation is committed. If MariaDB Server 10.2
is killed during TRUNCATE, it is also possible that the old table
was renamed to #sql-ib*.ibd but the data dictionary will refer to the
table using the original name.
In MariaDB Server 10.3, RENAME inside InnoDB is transactional,
and #sql-* tables will be dropped on startup. So, this new TRUNCATE
will be fully crash-safe in 10.3.
ha_mroonga::wrapper_truncate(): Pass table options to the underlying
storage engine, now that ha_innobase::truncate() will need them.
rpl_slave_state::truncate_state_table(): Before truncating
mysql.gtid_slave_pos, evict any cached table handles from
the table definition cache, so that there will be no stale
references to the old table after truncating.
== TRUNCATE TABLE ==
WL#6501 in MySQL 5.7 introduced separate log files for implementing
atomic and crash-safe TRUNCATE TABLE, instead of using the InnoDB
undo and redo log. Some convoluted logic was added to the InnoDB
crash recovery, and some extra synchronization (including a redo log
checkpoint) was introduced to make this work. This synchronization
has caused performance problems and race conditions, and the extra
log files cannot be copied or applied by external backup programs.
In order to support crash-upgrade from MariaDB 10.2, we will keep
the logic for parsing and applying the extra log files, but we will
no longer generate those files in TRUNCATE TABLE.
A prerequisite for crash-safe TRUNCATE is a crash-safe RENAME TABLE
(with full redo and undo logging and proper rollback). This will
be implemented in MDEV-14717.
ha_innobase::truncate(): Invoke RENAME, create(), delete_table().
Because RENAME cannot be fully rolled back before MariaDB 10.3
due to missing undo logging, add some explicit rename-back in
case the operation fails.
ha_innobase::delete(): Introduce a variant that takes sqlcom as
a parameter. In TRUNCATE TABLE, we do not want to touch any
FOREIGN KEY constraints.
ha_innobase::create(): Add the parameters file_per_table, trx.
In TRUNCATE, the new table must be created in the same transaction
that renames the old table.
create_table_info_t::create_table_info_t(): Add the parameters
file_per_table, trx.
row_drop_table_for_mysql(): Replace a bool parameter with sqlcom.
row_drop_table_after_create_fail(): New function, wrapping
row_drop_table_for_mysql().
dict_truncate_index_tree_in_mem(), fil_truncate_tablespace(),
fil_prepare_for_truncate(), fil_reinit_space_header_for_table(),
row_truncate_table_for_mysql(), TruncateLogger,
row_truncate_prepare(), row_truncate_rollback(),
row_truncate_complete(), row_truncate_fts(),
row_truncate_update_system_tables(),
row_truncate_foreign_key_checks(), row_truncate_sanity_checks():
Remove.
row_upd_check_references_constraints(): Remove a check for
TRUNCATE, now that the table is no longer truncated in place.
The new test innodb.truncate_foreign uses DEBUG_SYNC to cover some
race-condition like scenarios. The test innodb-innodb.truncate does
not use any synchronization.
We add a redo log subformat to indicate backup-friendly format.
MariaDB 10.4 will remove support for the old TRUNCATE logging,
so crash-upgrade from old 10.2 or 10.3 to 10.4 will involve
limitations.
== Undo tablespace truncation ==
MySQL 5.7 implements undo tablespace truncation. It is only
possible when innodb_undo_tablespaces is set to at least 2.
The logging is implemented similar to the WL#6501 TRUNCATE,
that is, using separate log files and a redo log checkpoint.
We can simply implement undo tablespace truncation within
a single mini-transaction that reinitializes the undo log
tablespace file. Unfortunately, due to the redo log format
of some operations, currently, the total redo log written by
undo tablespace truncation will be more than the combined size
of the truncated undo tablespace. It should be acceptable
to have a little more than 1 megabyte of log in a single
mini-transaction. This will be fixed in MDEV-17138 in
MariaDB Server 10.4.
recv_sys_t: Add truncated_undo_spaces[] to remember for which undo
tablespaces a MLOG_FILE_CREATE2 record was seen.
namespace undo: Remove some unnecessary declarations.
fil_space_t::is_being_truncated: Document that this flag now
only applies to undo tablespaces. Remove some references.
fil_space_t::is_stopping(): Do not refer to is_being_truncated.
This check is for tablespaces of tables. Potentially used
tablespaces are never truncated any more.
buf_dblwr_process(): Suppress the out-of-bounds warning
for undo tablespaces.
fil_truncate_log(): Write a MLOG_FILE_CREATE2 with a nonzero
page number (new size of the tablespace in pages) to inform
crash recovery that the undo tablespace size has been reduced.
fil_op_write_log(): Relax assertions, so that MLOG_FILE_CREATE2
can be written for undo tablespaces (without .ibd file suffix)
for a nonzero page number.
os_file_truncate(): Add the parameter allow_shrink=false
so that undo tablespaces can actually be shrunk using this function.
fil_name_parse(): For undo tablespace truncation,
buffer MLOG_FILE_CREATE2 in truncated_undo_spaces[].
recv_read_in_area(): Avoid reading pages for which no redo log
records remain buffered, after recv_addr_trim() removed them.
trx_rseg_header_create(): Add a FIXME comment that we could write
much less redo log.
trx_undo_truncate_tablespace(): Reinitialize the undo tablespace
in a single mini-transaction, which will be flushed to the redo log
before the file size is trimmed.
recv_addr_trim(): Discard any redo logs for pages that were
logged after the new end of a file, before the truncation LSN.
If the rec_list becomes empty, reduce n_addrs. After removing
any affected records, actually truncate the file.
recv_apply_hashed_log_recs(): Invoke recv_addr_trim() right before
applying any log records. The undo tablespace files must be open
at this point.
buf_flush_or_remove_pages(), buf_flush_dirty_pages(),
buf_LRU_flush_or_remove_pages(): Add a parameter for specifying
the number of the first page to flush or remove (default 0).
trx_purge_initiate_truncate(): Remove the log checkpoints, the
extra logging, and some unnecessary crash points. Merge the code
from trx_undo_truncate_tablespace(). First, flush all to-be-discarded
pages (beyond the new end of the file), then trim the space->size
to make the page allocation deterministic. At the only remaining
crash injection point, flush the redo log, so that the recovery
can be tested.
2018-08-28 12:43:06 +02:00
|
|
|
const ulint dir_len = strlen(dir_path);
|
2016-08-12 10:17:45 +02:00
|
|
|
|
|
|
|
/* Scan and look out for the truncate log files. */
|
|
|
|
dir = os_file_opendir(dir_path, true);
|
|
|
|
if (dir == NULL) {
|
|
|
|
return(DB_IO_ERROR);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (fil_file_readdir_next_file(
|
|
|
|
&err, dir_path, dir, &fileinfo) == 0) {
|
|
|
|
|
|
|
|
ulint nm_len = strlen(fileinfo.name);
|
|
|
|
|
|
|
|
if (fileinfo.type == OS_FILE_TYPE_FILE
|
MDEV-13564 Mariabackup does not work with TRUNCATE
Implement undo tablespace truncation via normal redo logging.
Implement TRUNCATE TABLE as a combination of RENAME to #sql-ib name,
CREATE, and DROP.
Note: Orphan #sql-ib*.ibd may be left behind if MariaDB Server 10.2
is killed before the DROP operation is committed. If MariaDB Server 10.2
is killed during TRUNCATE, it is also possible that the old table
was renamed to #sql-ib*.ibd but the data dictionary will refer to the
table using the original name.
In MariaDB Server 10.3, RENAME inside InnoDB is transactional,
and #sql-* tables will be dropped on startup. So, this new TRUNCATE
will be fully crash-safe in 10.3.
ha_mroonga::wrapper_truncate(): Pass table options to the underlying
storage engine, now that ha_innobase::truncate() will need them.
rpl_slave_state::truncate_state_table(): Before truncating
mysql.gtid_slave_pos, evict any cached table handles from
the table definition cache, so that there will be no stale
references to the old table after truncating.
== TRUNCATE TABLE ==
WL#6501 in MySQL 5.7 introduced separate log files for implementing
atomic and crash-safe TRUNCATE TABLE, instead of using the InnoDB
undo and redo log. Some convoluted logic was added to the InnoDB
crash recovery, and some extra synchronization (including a redo log
checkpoint) was introduced to make this work. This synchronization
has caused performance problems and race conditions, and the extra
log files cannot be copied or applied by external backup programs.
In order to support crash-upgrade from MariaDB 10.2, we will keep
the logic for parsing and applying the extra log files, but we will
no longer generate those files in TRUNCATE TABLE.
A prerequisite for crash-safe TRUNCATE is a crash-safe RENAME TABLE
(with full redo and undo logging and proper rollback). This will
be implemented in MDEV-14717.
ha_innobase::truncate(): Invoke RENAME, create(), delete_table().
Because RENAME cannot be fully rolled back before MariaDB 10.3
due to missing undo logging, add some explicit rename-back in
case the operation fails.
ha_innobase::delete(): Introduce a variant that takes sqlcom as
a parameter. In TRUNCATE TABLE, we do not want to touch any
FOREIGN KEY constraints.
ha_innobase::create(): Add the parameters file_per_table, trx.
In TRUNCATE, the new table must be created in the same transaction
that renames the old table.
create_table_info_t::create_table_info_t(): Add the parameters
file_per_table, trx.
row_drop_table_for_mysql(): Replace a bool parameter with sqlcom.
row_drop_table_after_create_fail(): New function, wrapping
row_drop_table_for_mysql().
dict_truncate_index_tree_in_mem(), fil_truncate_tablespace(),
fil_prepare_for_truncate(), fil_reinit_space_header_for_table(),
row_truncate_table_for_mysql(), TruncateLogger,
row_truncate_prepare(), row_truncate_rollback(),
row_truncate_complete(), row_truncate_fts(),
row_truncate_update_system_tables(),
row_truncate_foreign_key_checks(), row_truncate_sanity_checks():
Remove.
row_upd_check_references_constraints(): Remove a check for
TRUNCATE, now that the table is no longer truncated in place.
The new test innodb.truncate_foreign uses DEBUG_SYNC to cover some
race-condition like scenarios. The test innodb-innodb.truncate does
not use any synchronization.
We add a redo log subformat to indicate backup-friendly format.
MariaDB 10.4 will remove support for the old TRUNCATE logging,
so crash-upgrade from old 10.2 or 10.3 to 10.4 will involve
limitations.
== Undo tablespace truncation ==
MySQL 5.7 implements undo tablespace truncation. It is only
possible when innodb_undo_tablespaces is set to at least 2.
The logging is implemented similar to the WL#6501 TRUNCATE,
that is, using separate log files and a redo log checkpoint.
We can simply implement undo tablespace truncation within
a single mini-transaction that reinitializes the undo log
tablespace file. Unfortunately, due to the redo log format
of some operations, currently, the total redo log written by
undo tablespace truncation will be more than the combined size
of the truncated undo tablespace. It should be acceptable
to have a little more than 1 megabyte of log in a single
mini-transaction. This will be fixed in MDEV-17138 in
MariaDB Server 10.4.
recv_sys_t: Add truncated_undo_spaces[] to remember for which undo
tablespaces a MLOG_FILE_CREATE2 record was seen.
namespace undo: Remove some unnecessary declarations.
fil_space_t::is_being_truncated: Document that this flag now
only applies to undo tablespaces. Remove some references.
fil_space_t::is_stopping(): Do not refer to is_being_truncated.
This check is for tablespaces of tables. Potentially used
tablespaces are never truncated any more.
buf_dblwr_process(): Suppress the out-of-bounds warning
for undo tablespaces.
fil_truncate_log(): Write a MLOG_FILE_CREATE2 with a nonzero
page number (new size of the tablespace in pages) to inform
crash recovery that the undo tablespace size has been reduced.
fil_op_write_log(): Relax assertions, so that MLOG_FILE_CREATE2
can be written for undo tablespaces (without .ibd file suffix)
for a nonzero page number.
os_file_truncate(): Add the parameter allow_shrink=false
so that undo tablespaces can actually be shrunk using this function.
fil_name_parse(): For undo tablespace truncation,
buffer MLOG_FILE_CREATE2 in truncated_undo_spaces[].
recv_read_in_area(): Avoid reading pages for which no redo log
records remain buffered, after recv_addr_trim() removed them.
trx_rseg_header_create(): Add a FIXME comment that we could write
much less redo log.
trx_undo_truncate_tablespace(): Reinitialize the undo tablespace
in a single mini-transaction, which will be flushed to the redo log
before the file size is trimmed.
recv_addr_trim(): Discard any redo logs for pages that were
logged after the new end of a file, before the truncation LSN.
If the rec_list becomes empty, reduce n_addrs. After removing
any affected records, actually truncate the file.
recv_apply_hashed_log_recs(): Invoke recv_addr_trim() right before
applying any log records. The undo tablespace files must be open
at this point.
buf_flush_or_remove_pages(), buf_flush_dirty_pages(),
buf_LRU_flush_or_remove_pages(): Add a parameter for specifying
the number of the first page to flush or remove (default 0).
trx_purge_initiate_truncate(): Remove the log checkpoints, the
extra logging, and some unnecessary crash points. Merge the code
from trx_undo_truncate_tablespace(). First, flush all to-be-discarded
pages (beyond the new end of the file), then trim the space->size
to make the page allocation deterministic. At the only remaining
crash injection point, flush the redo log, so that the recovery
can be tested.
2018-08-28 12:43:06 +02:00
|
|
|
&& nm_len > sizeof "ib_trunc.log"
|
|
|
|
&& (0 == strncmp(fileinfo.name + nm_len
|
|
|
|
- ((sizeof "trunc.log") - 1),
|
|
|
|
"trunc.log", (sizeof "trunc.log") - 1))
|
|
|
|
&& (0 == strncmp(fileinfo.name, "ib_", 3))) {
|
2016-08-12 10:17:45 +02:00
|
|
|
|
|
|
|
if (fileinfo.size == 0) {
|
|
|
|
/* Truncate log not written. Remove the file. */
|
|
|
|
os_file_delete(
|
|
|
|
innodb_log_file_key, fileinfo.name);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Construct file name by appending directory path */
|
MDEV-13564 Mariabackup does not work with TRUNCATE
Implement undo tablespace truncation via normal redo logging.
Implement TRUNCATE TABLE as a combination of RENAME to #sql-ib name,
CREATE, and DROP.
Note: Orphan #sql-ib*.ibd may be left behind if MariaDB Server 10.2
is killed before the DROP operation is committed. If MariaDB Server 10.2
is killed during TRUNCATE, it is also possible that the old table
was renamed to #sql-ib*.ibd but the data dictionary will refer to the
table using the original name.
In MariaDB Server 10.3, RENAME inside InnoDB is transactional,
and #sql-* tables will be dropped on startup. So, this new TRUNCATE
will be fully crash-safe in 10.3.
ha_mroonga::wrapper_truncate(): Pass table options to the underlying
storage engine, now that ha_innobase::truncate() will need them.
rpl_slave_state::truncate_state_table(): Before truncating
mysql.gtid_slave_pos, evict any cached table handles from
the table definition cache, so that there will be no stale
references to the old table after truncating.
== TRUNCATE TABLE ==
WL#6501 in MySQL 5.7 introduced separate log files for implementing
atomic and crash-safe TRUNCATE TABLE, instead of using the InnoDB
undo and redo log. Some convoluted logic was added to the InnoDB
crash recovery, and some extra synchronization (including a redo log
checkpoint) was introduced to make this work. This synchronization
has caused performance problems and race conditions, and the extra
log files cannot be copied or applied by external backup programs.
In order to support crash-upgrade from MariaDB 10.2, we will keep
the logic for parsing and applying the extra log files, but we will
no longer generate those files in TRUNCATE TABLE.
A prerequisite for crash-safe TRUNCATE is a crash-safe RENAME TABLE
(with full redo and undo logging and proper rollback). This will
be implemented in MDEV-14717.
ha_innobase::truncate(): Invoke RENAME, create(), delete_table().
Because RENAME cannot be fully rolled back before MariaDB 10.3
due to missing undo logging, add some explicit rename-back in
case the operation fails.
ha_innobase::delete(): Introduce a variant that takes sqlcom as
a parameter. In TRUNCATE TABLE, we do not want to touch any
FOREIGN KEY constraints.
ha_innobase::create(): Add the parameters file_per_table, trx.
In TRUNCATE, the new table must be created in the same transaction
that renames the old table.
create_table_info_t::create_table_info_t(): Add the parameters
file_per_table, trx.
row_drop_table_for_mysql(): Replace a bool parameter with sqlcom.
row_drop_table_after_create_fail(): New function, wrapping
row_drop_table_for_mysql().
dict_truncate_index_tree_in_mem(), fil_truncate_tablespace(),
fil_prepare_for_truncate(), fil_reinit_space_header_for_table(),
row_truncate_table_for_mysql(), TruncateLogger,
row_truncate_prepare(), row_truncate_rollback(),
row_truncate_complete(), row_truncate_fts(),
row_truncate_update_system_tables(),
row_truncate_foreign_key_checks(), row_truncate_sanity_checks():
Remove.
row_upd_check_references_constraints(): Remove a check for
TRUNCATE, now that the table is no longer truncated in place.
The new test innodb.truncate_foreign uses DEBUG_SYNC to cover some
race-condition like scenarios. The test innodb-innodb.truncate does
not use any synchronization.
We add a redo log subformat to indicate backup-friendly format.
MariaDB 10.4 will remove support for the old TRUNCATE logging,
so crash-upgrade from old 10.2 or 10.3 to 10.4 will involve
limitations.
== Undo tablespace truncation ==
MySQL 5.7 implements undo tablespace truncation. It is only
possible when innodb_undo_tablespaces is set to at least 2.
The logging is implemented similar to the WL#6501 TRUNCATE,
that is, using separate log files and a redo log checkpoint.
We can simply implement undo tablespace truncation within
a single mini-transaction that reinitializes the undo log
tablespace file. Unfortunately, due to the redo log format
of some operations, currently, the total redo log written by
undo tablespace truncation will be more than the combined size
of the truncated undo tablespace. It should be acceptable
to have a little more than 1 megabyte of log in a single
mini-transaction. This will be fixed in MDEV-17138 in
MariaDB Server 10.4.
recv_sys_t: Add truncated_undo_spaces[] to remember for which undo
tablespaces a MLOG_FILE_CREATE2 record was seen.
namespace undo: Remove some unnecessary declarations.
fil_space_t::is_being_truncated: Document that this flag now
only applies to undo tablespaces. Remove some references.
fil_space_t::is_stopping(): Do not refer to is_being_truncated.
This check is for tablespaces of tables. Potentially used
tablespaces are never truncated any more.
buf_dblwr_process(): Suppress the out-of-bounds warning
for undo tablespaces.
fil_truncate_log(): Write a MLOG_FILE_CREATE2 with a nonzero
page number (new size of the tablespace in pages) to inform
crash recovery that the undo tablespace size has been reduced.
fil_op_write_log(): Relax assertions, so that MLOG_FILE_CREATE2
can be written for undo tablespaces (without .ibd file suffix)
for a nonzero page number.
os_file_truncate(): Add the parameter allow_shrink=false
so that undo tablespaces can actually be shrunk using this function.
fil_name_parse(): For undo tablespace truncation,
buffer MLOG_FILE_CREATE2 in truncated_undo_spaces[].
recv_read_in_area(): Avoid reading pages for which no redo log
records remain buffered, after recv_addr_trim() removed them.
trx_rseg_header_create(): Add a FIXME comment that we could write
much less redo log.
trx_undo_truncate_tablespace(): Reinitialize the undo tablespace
in a single mini-transaction, which will be flushed to the redo log
before the file size is trimmed.
recv_addr_trim(): Discard any redo logs for pages that were
logged after the new end of a file, before the truncation LSN.
If the rec_list becomes empty, reduce n_addrs. After removing
any affected records, actually truncate the file.
recv_apply_hashed_log_recs(): Invoke recv_addr_trim() right before
applying any log records. The undo tablespace files must be open
at this point.
buf_flush_or_remove_pages(), buf_flush_dirty_pages(),
buf_LRU_flush_or_remove_pages(): Add a parameter for specifying
the number of the first page to flush or remove (default 0).
trx_purge_initiate_truncate(): Remove the log checkpoints, the
extra logging, and some unnecessary crash points. Merge the code
from trx_undo_truncate_tablespace(). First, flush all to-be-discarded
pages (beyond the new end of the file), then trim the space->size
to make the page allocation deterministic. At the only remaining
crash injection point, flush the redo log, so that the recovery
can be tested.
2018-08-28 12:43:06 +02:00
|
|
|
ulint sz = dir_len + 22 + 22 + sizeof "ib_trunc.log";
|
2016-08-12 10:17:45 +02:00
|
|
|
char* log_file_name = UT_NEW_ARRAY_NOKEY(char, sz);
|
|
|
|
if (log_file_name == NULL) {
|
|
|
|
err = DB_OUT_OF_MEMORY;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
memset(log_file_name, 0, sz);
|
|
|
|
|
|
|
|
strncpy(log_file_name, dir_path, dir_len);
|
|
|
|
ulint log_file_name_len = strlen(log_file_name);
|
|
|
|
if (log_file_name[log_file_name_len - 1]
|
|
|
|
!= OS_PATH_SEPARATOR) {
|
|
|
|
|
|
|
|
log_file_name[log_file_name_len]
|
|
|
|
= OS_PATH_SEPARATOR;
|
|
|
|
log_file_name_len = strlen(log_file_name);
|
|
|
|
}
|
|
|
|
strcat(log_file_name, fileinfo.name);
|
|
|
|
log_files.push_back(log_file_name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
os_file_closedir(dir);
|
|
|
|
|
|
|
|
return(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Parse the log file and populate table to truncate information.
|
|
|
|
(Add this table to truncate information to central vector that is then
|
|
|
|
used by truncate fix-up routine to fix-up truncate action of the table.)
|
|
|
|
|
|
|
|
@param log_file_name log file to parse
|
|
|
|
@return DB_SUCCESS or error code. */
|
|
|
|
dberr_t
|
|
|
|
TruncateLogParser::parse(
|
|
|
|
const char* log_file_name)
|
|
|
|
{
|
|
|
|
dberr_t err = DB_SUCCESS;
|
|
|
|
truncate_t* truncate = NULL;
|
|
|
|
|
|
|
|
/* Open the file and read magic-number to findout if truncate action
|
|
|
|
was completed. */
|
|
|
|
bool ret;
|
|
|
|
os_file_t handle = os_file_create_simple(
|
|
|
|
innodb_log_file_key, log_file_name,
|
|
|
|
OS_FILE_OPEN, OS_FILE_READ_ONLY, srv_read_only_mode, &ret);
|
|
|
|
if (!ret) {
|
|
|
|
ib::error() << "Error opening truncate log file: "
|
|
|
|
<< log_file_name;
|
|
|
|
return(DB_IO_ERROR);
|
|
|
|
}
|
|
|
|
|
|
|
|
ulint sz = UNIV_PAGE_SIZE;
|
|
|
|
void* buf = ut_zalloc_nokey(sz + UNIV_PAGE_SIZE);
|
|
|
|
if (buf == 0) {
|
|
|
|
os_file_close(handle);
|
|
|
|
return(DB_OUT_OF_MEMORY);
|
|
|
|
}
|
|
|
|
|
|
|
|
IORequest request(IORequest::READ);
|
|
|
|
|
|
|
|
/* Align the memory for file i/o if we might have O_DIRECT set*/
|
|
|
|
byte* log_buf = static_cast<byte*>(ut_align(buf, UNIV_PAGE_SIZE));
|
|
|
|
|
|
|
|
do {
|
|
|
|
err = os_file_read(request, handle, log_buf, 0, sz);
|
|
|
|
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
os_file_close(handle);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
MDEV-13564: Implement innodb_unsafe_truncate=ON for compatibility
While MariaDB Server 10.2 is not really guaranteed to be compatible
with Percona XtraBackup 2.4 (for example, the MySQL 5.7 undo log format
change that could be present in XtraBackup, but was reverted from
MariaDB in MDEV-12289), we do not want to disrupt users who have
deployed xtrabackup and MariaDB Server 10.2 in their environments.
With this change, MariaDB 10.2 will continue to use the backup-unsafe
TRUNCATE TABLE code, so that neither the undo log nor the redo log
formats will change in an incompatible way.
Undo tablespace truncation will keep using the redo log only. Recovery
or backup with old code will fail to shrink the undo tablespace files,
but the contents will be recovered just fine.
In the MariaDB Server 10.2 series only, we introduce the configuration
parameter innodb_unsafe_truncate and make it ON by default. To allow
MariaDB Backup (mariabackup) to work properly with TRUNCATE TABLE
operations, use loose_innodb_unsafe_truncate=OFF.
MariaDB Server 10.3.10 and later releases will always use the
backup-safe TRUNCATE TABLE, and this parameter will not be
added there.
recv_recovery_rollback_active(): Skip row_mysql_drop_garbage_tables()
unless innodb_unsafe_truncate=OFF. It is too unsafe to drop orphan
tables if RENAME operations are not transactional within InnoDB.
LOG_HEADER_FORMAT_10_3: Replaces LOG_HEADER_FORMAT_CURRENT.
log_init(), log_group_file_header_flush(),
srv_prepare_to_delete_redo_log_files(),
innobase_start_or_create_for_mysql(): Choose the redo log format
and subformat based on the value of innodb_unsafe_truncate.
2018-10-10 18:19:29 +02:00
|
|
|
if (!memcmp(log_buf, magic, sizeof magic)) {
|
2016-08-12 10:17:45 +02:00
|
|
|
|
|
|
|
/* Truncate action completed. Avoid parsing the file. */
|
|
|
|
os_file_close(handle);
|
|
|
|
|
|
|
|
os_file_delete(innodb_log_file_key, log_file_name);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (truncate == NULL) {
|
|
|
|
truncate = UT_NEW_NOKEY(truncate_t(log_file_name));
|
|
|
|
if (truncate == NULL) {
|
|
|
|
os_file_close(handle);
|
|
|
|
err = DB_OUT_OF_MEMORY;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
err = truncate->parse(log_buf + 4, log_buf + sz - 4);
|
|
|
|
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
|
|
|
|
ut_ad(err == DB_FAIL);
|
|
|
|
|
|
|
|
ut_free(buf);
|
|
|
|
buf = 0;
|
|
|
|
|
|
|
|
sz *= 2;
|
|
|
|
|
|
|
|
buf = ut_zalloc_nokey(sz + UNIV_PAGE_SIZE);
|
|
|
|
|
|
|
|
if (buf == 0) {
|
|
|
|
os_file_close(handle);
|
|
|
|
err = DB_OUT_OF_MEMORY;
|
|
|
|
UT_DELETE(truncate);
|
|
|
|
truncate = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
log_buf = static_cast<byte*>(
|
|
|
|
ut_align(buf, UNIV_PAGE_SIZE));
|
|
|
|
}
|
|
|
|
} while (err != DB_SUCCESS);
|
|
|
|
|
|
|
|
ut_free(buf);
|
|
|
|
|
|
|
|
if (err == DB_SUCCESS && truncate != NULL) {
|
|
|
|
truncate_t::add(truncate);
|
|
|
|
os_file_close(handle);
|
|
|
|
}
|
|
|
|
|
|
|
|
return(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Scan and Parse truncate log files.
|
|
|
|
|
|
|
|
@param dir_path look for log directory in following path
|
|
|
|
@return DB_SUCCESS or error code. */
|
|
|
|
dberr_t
|
|
|
|
TruncateLogParser::scan_and_parse(
|
|
|
|
const char* dir_path)
|
|
|
|
{
|
|
|
|
dberr_t err;
|
|
|
|
trunc_log_files_t log_files;
|
|
|
|
|
|
|
|
/* Scan and trace all the truncate log files. */
|
|
|
|
err = TruncateLogParser::scan(dir_path, log_files);
|
|
|
|
|
|
|
|
/* Parse truncate lof files if scan was successful. */
|
|
|
|
if (err == DB_SUCCESS) {
|
|
|
|
|
|
|
|
for (ulint i = 0;
|
|
|
|
i < log_files.size() && err == DB_SUCCESS;
|
|
|
|
i++) {
|
|
|
|
err = TruncateLogParser::parse(log_files[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
trunc_log_files_t::const_iterator end = log_files.end();
|
|
|
|
for (trunc_log_files_t::const_iterator it = log_files.begin();
|
|
|
|
it != end;
|
|
|
|
++it) {
|
|
|
|
if (*it != NULL) {
|
|
|
|
UT_DELETE_ARRAY(*it);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
log_files.clear();
|
|
|
|
|
|
|
|
return(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Callback to drop indexes during TRUNCATE */
|
|
|
|
class DropIndex : public Callback {
|
|
|
|
|
|
|
|
public:
|
|
|
|
/**
|
|
|
|
Constructor
|
|
|
|
|
|
|
|
@param[in,out] table Table to truncate
|
|
|
|
@param[in] noredo whether to disable redo logging */
|
|
|
|
DropIndex(dict_table_t* table, bool noredo)
|
|
|
|
:
|
|
|
|
Callback(table->id, noredo),
|
|
|
|
m_table(table)
|
|
|
|
{
|
|
|
|
/* No op */
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
@param mtr mini-transaction covering the read
|
|
|
|
@param pcur persistent cursor used for reading
|
|
|
|
@return DB_SUCCESS or error code */
|
|
|
|
dberr_t operator()(mtr_t* mtr, btr_pcur_t* pcur) const;
|
|
|
|
|
|
|
|
private:
|
|
|
|
/** Table to be truncated */
|
|
|
|
dict_table_t* m_table;
|
|
|
|
};
|
|
|
|
|
|
|
|
/** Callback to create the indexes during TRUNCATE */
|
|
|
|
class CreateIndex : public Callback {
|
|
|
|
|
|
|
|
public:
|
|
|
|
/**
|
|
|
|
Constructor
|
|
|
|
|
|
|
|
@param[in,out] table Table to truncate
|
|
|
|
@param[in] noredo whether to disable redo logging */
|
|
|
|
CreateIndex(dict_table_t* table, bool noredo)
|
|
|
|
:
|
|
|
|
Callback(table->id, noredo),
|
|
|
|
m_table(table)
|
|
|
|
{
|
|
|
|
/* No op */
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Create the new index and update the root page number in the
|
|
|
|
SysIndex table.
|
|
|
|
|
|
|
|
@param mtr mini-transaction covering the read
|
|
|
|
@param pcur persistent cursor used for reading
|
|
|
|
@return DB_SUCCESS or error code */
|
|
|
|
dberr_t operator()(mtr_t* mtr, btr_pcur_t* pcur) const;
|
|
|
|
|
|
|
|
private:
|
|
|
|
// Disably copying
|
|
|
|
CreateIndex(const CreateIndex&);
|
|
|
|
CreateIndex& operator=(const CreateIndex&);
|
|
|
|
|
|
|
|
private:
|
|
|
|
/** Table to be truncated */
|
|
|
|
dict_table_t* m_table;
|
|
|
|
};
|
|
|
|
|
|
|
|
/** Check for presence of table-id in SYS_XXXX tables. */
|
|
|
|
class TableLocator : public Callback {
|
|
|
|
|
|
|
|
public:
|
|
|
|
/**
|
|
|
|
Constructor
|
|
|
|
@param table_id table_id to look for */
|
|
|
|
explicit TableLocator(table_id_t table_id)
|
|
|
|
:
|
|
|
|
Callback(table_id, false),
|
|
|
|
m_table_found()
|
|
|
|
{
|
|
|
|
/* No op */
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
@return true if table is found */
|
|
|
|
bool is_table_found() const
|
|
|
|
{
|
|
|
|
return(m_table_found);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Look for table-id in SYS_XXXX tables without loading the table.
|
|
|
|
|
|
|
|
@param mtr mini-transaction covering the read
|
|
|
|
@param pcur persistent cursor used for reading
|
|
|
|
@return DB_SUCCESS or error code */
|
|
|
|
dberr_t operator()(mtr_t* mtr, btr_pcur_t* pcur);
|
|
|
|
|
|
|
|
private:
|
|
|
|
// Disably copying
|
|
|
|
TableLocator(const TableLocator&);
|
|
|
|
TableLocator& operator=(const TableLocator&);
|
|
|
|
|
|
|
|
private:
|
|
|
|
/** Set to true if table is present */
|
|
|
|
bool m_table_found;
|
|
|
|
};
|
|
|
|
|
MDEV-13564: Implement innodb_unsafe_truncate=ON for compatibility
While MariaDB Server 10.2 is not really guaranteed to be compatible
with Percona XtraBackup 2.4 (for example, the MySQL 5.7 undo log format
change that could be present in XtraBackup, but was reverted from
MariaDB in MDEV-12289), we do not want to disrupt users who have
deployed xtrabackup and MariaDB Server 10.2 in their environments.
With this change, MariaDB 10.2 will continue to use the backup-unsafe
TRUNCATE TABLE code, so that neither the undo log nor the redo log
formats will change in an incompatible way.
Undo tablespace truncation will keep using the redo log only. Recovery
or backup with old code will fail to shrink the undo tablespace files,
but the contents will be recovered just fine.
In the MariaDB Server 10.2 series only, we introduce the configuration
parameter innodb_unsafe_truncate and make it ON by default. To allow
MariaDB Backup (mariabackup) to work properly with TRUNCATE TABLE
operations, use loose_innodb_unsafe_truncate=OFF.
MariaDB Server 10.3.10 and later releases will always use the
backup-safe TRUNCATE TABLE, and this parameter will not be
added there.
recv_recovery_rollback_active(): Skip row_mysql_drop_garbage_tables()
unless innodb_unsafe_truncate=OFF. It is too unsafe to drop orphan
tables if RENAME operations are not transactional within InnoDB.
LOG_HEADER_FORMAT_10_3: Replaces LOG_HEADER_FORMAT_CURRENT.
log_init(), log_group_file_header_flush(),
srv_prepare_to_delete_redo_log_files(),
innobase_start_or_create_for_mysql(): Choose the redo log format
and subformat based on the value of innodb_unsafe_truncate.
2018-10-10 18:19:29 +02:00
|
|
|
/**
|
|
|
|
@param mtr mini-transaction covering the read
|
|
|
|
@param pcur persistent cursor used for reading
|
|
|
|
@return DB_SUCCESS or error code */
|
|
|
|
dberr_t
|
|
|
|
TruncateLogger::operator()(mtr_t* mtr, btr_pcur_t* pcur)
|
|
|
|
{
|
|
|
|
ulint len;
|
|
|
|
const byte* field;
|
|
|
|
rec_t* rec = btr_pcur_get_rec(pcur);
|
|
|
|
truncate_t::index_t index;
|
|
|
|
|
|
|
|
field = rec_get_nth_field_old(
|
|
|
|
rec, DICT_FLD__SYS_INDEXES__TYPE, &len);
|
|
|
|
ut_ad(len == 4);
|
|
|
|
index.m_type = mach_read_from_4(field);
|
|
|
|
|
|
|
|
field = rec_get_nth_field_old(rec, DICT_FLD__SYS_INDEXES__ID, &len);
|
|
|
|
ut_ad(len == 8);
|
|
|
|
index.m_id = mach_read_from_8(field);
|
|
|
|
|
|
|
|
field = rec_get_nth_field_old(
|
|
|
|
rec, DICT_FLD__SYS_INDEXES__PAGE_NO, &len);
|
|
|
|
ut_ad(len == 4);
|
|
|
|
index.m_root_page_no = mach_read_from_4(field);
|
|
|
|
|
|
|
|
/* For compressed tables we need to store extra meta-data
|
|
|
|
required during btr_create(). */
|
|
|
|
if (FSP_FLAGS_GET_ZIP_SSIZE(m_flags)) {
|
|
|
|
|
|
|
|
const dict_index_t* dict_index = find(index.m_id);
|
|
|
|
|
|
|
|
if (dict_index != NULL) {
|
|
|
|
|
|
|
|
dberr_t err = index.set(dict_index);
|
|
|
|
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
m_truncate.clear();
|
|
|
|
return(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
ib::warn() << "Index id " << index.m_id
|
|
|
|
<< " not found";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m_truncate.add(index);
|
|
|
|
|
|
|
|
return(DB_SUCCESS);
|
|
|
|
}
|
|
|
|
|
2016-08-12 10:17:45 +02:00
|
|
|
/**
|
|
|
|
Drop an index in the table.
|
|
|
|
|
|
|
|
@param mtr mini-transaction covering the read
|
|
|
|
@param pcur persistent cursor used for reading
|
|
|
|
@return DB_SUCCESS or error code */
|
|
|
|
dberr_t
|
|
|
|
DropIndex::operator()(mtr_t* mtr, btr_pcur_t* pcur) const
|
|
|
|
{
|
|
|
|
rec_t* rec = btr_pcur_get_rec(pcur);
|
|
|
|
|
|
|
|
bool freed = dict_drop_index_tree(rec, pcur, mtr);
|
|
|
|
|
|
|
|
#ifdef UNIV_DEBUG
|
|
|
|
{
|
|
|
|
ulint len;
|
|
|
|
const byte* field;
|
|
|
|
ulint index_type;
|
|
|
|
|
|
|
|
field = rec_get_nth_field_old(
|
|
|
|
btr_pcur_get_rec(pcur), DICT_FLD__SYS_INDEXES__TYPE,
|
|
|
|
&len);
|
|
|
|
ut_ad(len == 4);
|
|
|
|
|
|
|
|
index_type = mach_read_from_4(field);
|
|
|
|
|
|
|
|
if (index_type & DICT_CLUSTERED) {
|
|
|
|
/* Clustered index */
|
|
|
|
DBUG_EXECUTE_IF("ib_trunc_crash_on_drop_of_clust_index",
|
|
|
|
log_buffer_flush_to_disk();
|
|
|
|
os_thread_sleep(2000000);
|
|
|
|
DBUG_SUICIDE(););
|
|
|
|
} else if (index_type & DICT_UNIQUE) {
|
|
|
|
/* Unique index */
|
|
|
|
DBUG_EXECUTE_IF("ib_trunc_crash_on_drop_of_uniq_index",
|
|
|
|
log_buffer_flush_to_disk();
|
|
|
|
os_thread_sleep(2000000);
|
|
|
|
DBUG_SUICIDE(););
|
|
|
|
} else if (index_type == 0) {
|
|
|
|
/* Secondary index */
|
|
|
|
DBUG_EXECUTE_IF("ib_trunc_crash_on_drop_of_sec_index",
|
|
|
|
log_buffer_flush_to_disk();
|
|
|
|
os_thread_sleep(2000000);
|
|
|
|
DBUG_SUICIDE(););
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif /* UNIV_DEBUG */
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF("ib_err_trunc_drop_index",
|
|
|
|
freed = false;);
|
|
|
|
|
|
|
|
if (freed) {
|
|
|
|
|
|
|
|
/* We will need to commit and restart the
|
|
|
|
mini-transaction in order to avoid deadlocks.
|
|
|
|
The dict_drop_index_tree() call has freed
|
|
|
|
a page in this mini-transaction, and the rest
|
|
|
|
of this loop could latch another index page.*/
|
|
|
|
const mtr_log_t log_mode = mtr->get_log_mode();
|
|
|
|
mtr_commit(mtr);
|
|
|
|
|
|
|
|
mtr_start(mtr);
|
|
|
|
mtr->set_log_mode(log_mode);
|
|
|
|
|
|
|
|
btr_pcur_restore_position(BTR_MODIFY_LEAF, pcur, mtr);
|
|
|
|
} else {
|
|
|
|
/* Check if the .ibd file is missing. */
|
|
|
|
bool found;
|
|
|
|
|
|
|
|
fil_space_get_page_size(m_table->space, &found);
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF("ib_err_trunc_drop_index",
|
|
|
|
found = false;);
|
|
|
|
|
|
|
|
if (!found) {
|
|
|
|
return(DB_ERROR);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return(DB_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Create the new index and update the root page number in the
|
|
|
|
SysIndex table.
|
|
|
|
|
|
|
|
@param mtr mini-transaction covering the read
|
|
|
|
@param pcur persistent cursor used for reading
|
|
|
|
@return DB_SUCCESS or error code */
|
|
|
|
dberr_t
|
|
|
|
CreateIndex::operator()(mtr_t* mtr, btr_pcur_t* pcur) const
|
|
|
|
{
|
|
|
|
ulint root_page_no;
|
|
|
|
|
|
|
|
root_page_no = dict_recreate_index_tree(m_table, pcur, mtr);
|
|
|
|
|
|
|
|
#ifdef UNIV_DEBUG
|
|
|
|
{
|
|
|
|
ulint len;
|
|
|
|
const byte* field;
|
|
|
|
ulint index_type;
|
|
|
|
|
|
|
|
field = rec_get_nth_field_old(
|
|
|
|
btr_pcur_get_rec(pcur), DICT_FLD__SYS_INDEXES__TYPE,
|
|
|
|
&len);
|
|
|
|
ut_ad(len == 4);
|
|
|
|
|
|
|
|
index_type = mach_read_from_4(field);
|
|
|
|
|
|
|
|
if (index_type & DICT_CLUSTERED) {
|
|
|
|
/* Clustered index */
|
|
|
|
DBUG_EXECUTE_IF(
|
|
|
|
"ib_trunc_crash_on_create_of_clust_index",
|
|
|
|
log_buffer_flush_to_disk();
|
|
|
|
os_thread_sleep(2000000);
|
|
|
|
DBUG_SUICIDE(););
|
|
|
|
} else if (index_type & DICT_UNIQUE) {
|
|
|
|
/* Unique index */
|
|
|
|
DBUG_EXECUTE_IF(
|
|
|
|
"ib_trunc_crash_on_create_of_uniq_index",
|
|
|
|
log_buffer_flush_to_disk();
|
|
|
|
os_thread_sleep(2000000);
|
|
|
|
DBUG_SUICIDE(););
|
|
|
|
} else if (index_type == 0) {
|
|
|
|
/* Secondary index */
|
|
|
|
DBUG_EXECUTE_IF(
|
|
|
|
"ib_trunc_crash_on_create_of_sec_index",
|
|
|
|
log_buffer_flush_to_disk();
|
|
|
|
os_thread_sleep(2000000);
|
|
|
|
DBUG_SUICIDE(););
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif /* UNIV_DEBUG */
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF("ib_err_trunc_create_index",
|
|
|
|
root_page_no = FIL_NULL;);
|
|
|
|
|
|
|
|
if (root_page_no != FIL_NULL) {
|
|
|
|
|
|
|
|
rec_t* rec = btr_pcur_get_rec(pcur);
|
|
|
|
|
|
|
|
page_rec_write_field(
|
|
|
|
rec, DICT_FLD__SYS_INDEXES__PAGE_NO,
|
|
|
|
root_page_no, mtr);
|
|
|
|
|
|
|
|
/* We will need to commit and restart the
|
|
|
|
mini-transaction in order to avoid deadlocks.
|
|
|
|
The dict_create_index_tree() call has allocated
|
|
|
|
a page in this mini-transaction, and the rest of
|
|
|
|
this loop could latch another index page. */
|
|
|
|
mtr_commit(mtr);
|
|
|
|
|
|
|
|
mtr_start(mtr);
|
|
|
|
|
|
|
|
btr_pcur_restore_position(BTR_MODIFY_LEAF, pcur, mtr);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
bool found;
|
|
|
|
fil_space_get_page_size(m_table->space, &found);
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF("ib_err_trunc_create_index",
|
|
|
|
found = false;);
|
|
|
|
|
|
|
|
if (!found) {
|
|
|
|
return(DB_ERROR);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return(DB_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Look for table-id in SYS_XXXX tables without loading the table.
|
|
|
|
|
|
|
|
@param mtr mini-transaction covering the read
|
|
|
|
@param pcur persistent cursor used for reading
|
|
|
|
@return DB_SUCCESS */
|
|
|
|
dberr_t
|
|
|
|
TableLocator::operator()(mtr_t* mtr, btr_pcur_t* pcur)
|
|
|
|
{
|
|
|
|
m_table_found = true;
|
|
|
|
|
|
|
|
return(DB_SUCCESS);
|
|
|
|
}
|
|
|
|
|
MDEV-13564: Implement innodb_unsafe_truncate=ON for compatibility
While MariaDB Server 10.2 is not really guaranteed to be compatible
with Percona XtraBackup 2.4 (for example, the MySQL 5.7 undo log format
change that could be present in XtraBackup, but was reverted from
MariaDB in MDEV-12289), we do not want to disrupt users who have
deployed xtrabackup and MariaDB Server 10.2 in their environments.
With this change, MariaDB 10.2 will continue to use the backup-unsafe
TRUNCATE TABLE code, so that neither the undo log nor the redo log
formats will change in an incompatible way.
Undo tablespace truncation will keep using the redo log only. Recovery
or backup with old code will fail to shrink the undo tablespace files,
but the contents will be recovered just fine.
In the MariaDB Server 10.2 series only, we introduce the configuration
parameter innodb_unsafe_truncate and make it ON by default. To allow
MariaDB Backup (mariabackup) to work properly with TRUNCATE TABLE
operations, use loose_innodb_unsafe_truncate=OFF.
MariaDB Server 10.3.10 and later releases will always use the
backup-safe TRUNCATE TABLE, and this parameter will not be
added there.
recv_recovery_rollback_active(): Skip row_mysql_drop_garbage_tables()
unless innodb_unsafe_truncate=OFF. It is too unsafe to drop orphan
tables if RENAME operations are not transactional within InnoDB.
LOG_HEADER_FORMAT_10_3: Replaces LOG_HEADER_FORMAT_CURRENT.
log_init(), log_group_file_header_flush(),
srv_prepare_to_delete_redo_log_files(),
innobase_start_or_create_for_mysql(): Choose the redo log format
and subformat based on the value of innodb_unsafe_truncate.
2018-10-10 18:19:29 +02:00
|
|
|
/**
|
|
|
|
Rollback the transaction and release the index locks.
|
|
|
|
Drop indexes if table is corrupted so that drop/create
|
|
|
|
sequence works as expected.
|
|
|
|
|
|
|
|
@param table table to truncate
|
|
|
|
@param trx transaction covering the TRUNCATE
|
|
|
|
@param new_id new table id that was suppose to get assigned
|
|
|
|
to the table if truncate executed successfully.
|
|
|
|
@param has_internal_doc_id indicate existence of fts index
|
|
|
|
@param no_redo if true, turn-off redo logging
|
|
|
|
@param corrupted table corrupted status
|
|
|
|
@param unlock_index if true then unlock indexes before action */
|
|
|
|
static
|
|
|
|
void
|
|
|
|
row_truncate_rollback(
|
|
|
|
dict_table_t* table,
|
|
|
|
trx_t* trx,
|
|
|
|
table_id_t new_id,
|
|
|
|
bool has_internal_doc_id,
|
|
|
|
bool no_redo,
|
|
|
|
bool corrupted,
|
|
|
|
bool unlock_index)
|
|
|
|
{
|
|
|
|
if (unlock_index) {
|
|
|
|
dict_table_x_unlock_indexes(table);
|
|
|
|
}
|
|
|
|
|
|
|
|
trx->error_state = DB_SUCCESS;
|
|
|
|
|
|
|
|
trx_rollback_to_savepoint(trx, NULL);
|
|
|
|
|
|
|
|
trx->error_state = DB_SUCCESS;
|
|
|
|
|
|
|
|
if (corrupted && !dict_table_is_temporary(table)) {
|
|
|
|
|
|
|
|
/* Cleanup action to ensure we don't left over stale entries
|
|
|
|
if we are marking table as corrupted. This will ensure
|
|
|
|
it can be recovered using drop/create sequence. */
|
|
|
|
dict_table_x_lock_indexes(table);
|
|
|
|
|
|
|
|
DropIndex dropIndex(table, no_redo);
|
|
|
|
|
|
|
|
SysIndexIterator().for_each(dropIndex);
|
|
|
|
|
|
|
|
dict_table_x_unlock_indexes(table);
|
|
|
|
|
|
|
|
for (dict_index_t* index = UT_LIST_GET_FIRST(table->indexes);
|
|
|
|
index != NULL;
|
|
|
|
index = UT_LIST_GET_NEXT(indexes, index)) {
|
|
|
|
|
|
|
|
dict_set_corrupted(index, trx, "TRUNCATE TABLE");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (has_internal_doc_id) {
|
|
|
|
|
|
|
|
ut_ad(!trx_is_started(trx));
|
|
|
|
|
|
|
|
table_id_t id = table->id;
|
|
|
|
|
|
|
|
table->id = new_id;
|
|
|
|
|
|
|
|
fts_drop_tables(trx, table);
|
|
|
|
|
|
|
|
table->id = id;
|
|
|
|
|
|
|
|
ut_ad(trx_is_started(trx));
|
|
|
|
|
|
|
|
trx_commit_for_mysql(trx);
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (corrupted && dict_table_is_temporary(table)) {
|
|
|
|
|
|
|
|
dict_table_x_lock_indexes(table);
|
|
|
|
|
|
|
|
for (dict_index_t* index = UT_LIST_GET_FIRST(table->indexes);
|
|
|
|
index != NULL;
|
|
|
|
index = UT_LIST_GET_NEXT(indexes, index)) {
|
|
|
|
|
|
|
|
dict_drop_index_tree_in_mem(index, index->page);
|
|
|
|
|
|
|
|
index->page = FIL_NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
dict_table_x_unlock_indexes(table);
|
|
|
|
}
|
|
|
|
|
|
|
|
table->corrupted = corrupted;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Finish the TRUNCATE operations for both commit and rollback.
|
|
|
|
|
|
|
|
@param table table being truncated
|
|
|
|
@param trx transaction covering the truncate
|
|
|
|
@param fsp_flags tablespace flags
|
|
|
|
@param logger table to truncate information logger
|
|
|
|
@param err status of truncate operation
|
|
|
|
|
|
|
|
@return DB_SUCCESS or error code */
|
|
|
|
static MY_ATTRIBUTE((warn_unused_result))
|
|
|
|
dberr_t
|
|
|
|
row_truncate_complete(
|
|
|
|
dict_table_t* table,
|
|
|
|
trx_t* trx,
|
|
|
|
ulint fsp_flags,
|
|
|
|
TruncateLogger* &logger,
|
|
|
|
dberr_t err)
|
|
|
|
{
|
|
|
|
bool is_file_per_table = dict_table_is_file_per_table(table);
|
|
|
|
|
|
|
|
/* Add the table back to FTS optimize background thread. */
|
|
|
|
if (table->fts) {
|
|
|
|
fts_optimize_add_table(table);
|
|
|
|
}
|
|
|
|
|
|
|
|
row_mysql_unlock_data_dictionary(trx);
|
|
|
|
|
|
|
|
DEBUG_SYNC_C("ib_trunc_table_trunc_completing");
|
|
|
|
|
|
|
|
if (!dict_table_is_temporary(table)) {
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF("ib_trunc_crash_before_log_removal",
|
|
|
|
log_buffer_flush_to_disk();
|
|
|
|
os_thread_sleep(500000);
|
|
|
|
DBUG_SUICIDE(););
|
|
|
|
|
|
|
|
/* Note: We don't log-checkpoint instead we have written
|
|
|
|
a special REDO log record MLOG_TRUNCATE that is used to
|
|
|
|
avoid applying REDO records before truncate for crash
|
|
|
|
that happens post successful truncate completion. */
|
|
|
|
|
|
|
|
if (logger != NULL) {
|
|
|
|
logger->done();
|
|
|
|
UT_DELETE(logger);
|
|
|
|
logger = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If non-temp file-per-table tablespace... */
|
|
|
|
if (is_file_per_table
|
|
|
|
&& !dict_table_is_temporary(table)
|
|
|
|
&& fsp_flags != ULINT_UNDEFINED) {
|
|
|
|
|
|
|
|
/* This function will reset back the stop_new_ops
|
|
|
|
and is_being_truncated so that fil-ops can re-start. */
|
|
|
|
dberr_t err2 = truncate_t::truncate(
|
|
|
|
table->space,
|
|
|
|
table->data_dir_path,
|
|
|
|
table->name.m_name, fsp_flags, false);
|
|
|
|
|
|
|
|
if (err2 != DB_SUCCESS) {
|
|
|
|
return(err2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err == DB_SUCCESS) {
|
|
|
|
dict_stats_update(table, DICT_STATS_EMPTY_TABLE);
|
|
|
|
}
|
|
|
|
|
|
|
|
trx->op_info = "";
|
|
|
|
|
|
|
|
/* For temporary tables or if there was an error, we need to reset
|
|
|
|
the dict operation flags. */
|
|
|
|
trx->ddl = false;
|
|
|
|
trx->dict_operation = TRX_DICT_OP_NONE;
|
|
|
|
|
|
|
|
ut_ad(!trx_is_started(trx));
|
|
|
|
|
|
|
|
srv_wake_master_thread();
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF("ib_trunc_crash_after_truncate_done",
|
|
|
|
DBUG_SUICIDE(););
|
|
|
|
|
|
|
|
return(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Handle FTS truncate issues.
|
|
|
|
@param table table being truncated
|
|
|
|
@param new_id new id for the table
|
|
|
|
@param trx transaction covering the truncate
|
|
|
|
@return DB_SUCCESS or error code. */
|
|
|
|
static MY_ATTRIBUTE((warn_unused_result))
|
|
|
|
dberr_t
|
|
|
|
row_truncate_fts(
|
|
|
|
dict_table_t* table,
|
|
|
|
table_id_t new_id,
|
|
|
|
trx_t* trx)
|
|
|
|
{
|
|
|
|
dict_table_t fts_table;
|
|
|
|
|
|
|
|
fts_table.id = new_id;
|
|
|
|
fts_table.name = table->name;
|
|
|
|
fts_table.flags2 = table->flags2;
|
|
|
|
fts_table.flags = table->flags;
|
|
|
|
fts_table.space = table->space;
|
|
|
|
|
|
|
|
/* table->data_dir_path is used for FTS AUX table
|
|
|
|
creation. */
|
|
|
|
if (DICT_TF_HAS_DATA_DIR(table->flags)
|
|
|
|
&& table->data_dir_path == NULL) {
|
|
|
|
dict_get_and_save_data_dir_path(table, true);
|
|
|
|
ut_ad(table->data_dir_path != NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
fts_table.data_dir_path = table->data_dir_path;
|
|
|
|
|
|
|
|
dberr_t err;
|
|
|
|
|
|
|
|
err = fts_create_common_tables(
|
|
|
|
trx, &fts_table, table->name.m_name, TRUE);
|
|
|
|
|
|
|
|
for (ulint i = 0;
|
|
|
|
i < ib_vector_size(table->fts->indexes) && err == DB_SUCCESS;
|
|
|
|
i++) {
|
|
|
|
|
|
|
|
dict_index_t* fts_index;
|
|
|
|
|
|
|
|
fts_index = static_cast<dict_index_t*>(
|
|
|
|
ib_vector_getp(table->fts->indexes, i));
|
|
|
|
|
|
|
|
err = fts_create_index_tables_low(
|
|
|
|
trx, fts_index, table->name.m_name, new_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF("ib_err_trunc_during_fts_trunc",
|
|
|
|
err = DB_ERROR;);
|
|
|
|
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
|
|
|
|
trx->error_state = DB_SUCCESS;
|
|
|
|
trx_rollback_to_savepoint(trx, NULL);
|
|
|
|
trx->error_state = DB_SUCCESS;
|
|
|
|
|
|
|
|
ib::error() << "Unable to truncate FTS index for table "
|
|
|
|
<< table->name;
|
|
|
|
} else {
|
|
|
|
|
|
|
|
ut_ad(trx_is_started(trx));
|
|
|
|
}
|
|
|
|
|
|
|
|
return(err);
|
|
|
|
}
|
|
|
|
|
2016-08-12 10:17:45 +02:00
|
|
|
/**
|
|
|
|
Update system table to reflect new table id.
|
|
|
|
@param old_table_id old table id
|
|
|
|
@param new_table_id new table id
|
|
|
|
@param reserve_dict_mutex if TRUE, acquire/release
|
|
|
|
dict_sys->mutex around call to pars_sql.
|
|
|
|
@param trx transaction
|
|
|
|
@return error code or DB_SUCCESS */
|
2016-09-06 08:43:16 +02:00
|
|
|
static MY_ATTRIBUTE((warn_unused_result))
|
2016-08-12 10:17:45 +02:00
|
|
|
dberr_t
|
|
|
|
row_truncate_update_table_id(
|
|
|
|
table_id_t old_table_id,
|
|
|
|
table_id_t new_table_id,
|
|
|
|
ibool reserve_dict_mutex,
|
|
|
|
trx_t* trx)
|
|
|
|
{
|
|
|
|
pars_info_t* info = NULL;
|
|
|
|
dberr_t err = DB_SUCCESS;
|
|
|
|
|
|
|
|
/* Scan the SYS_XXXX table and update to reflect new table-id. */
|
|
|
|
info = pars_info_create();
|
|
|
|
pars_info_add_ull_literal(info, "old_id", old_table_id);
|
|
|
|
pars_info_add_ull_literal(info, "new_id", new_table_id);
|
|
|
|
|
|
|
|
err = que_eval_sql(
|
|
|
|
info,
|
|
|
|
"PROCEDURE RENUMBER_TABLE_ID_PROC () IS\n"
|
|
|
|
"BEGIN\n"
|
|
|
|
"UPDATE SYS_TABLES"
|
|
|
|
" SET ID = :new_id\n"
|
|
|
|
" WHERE ID = :old_id;\n"
|
|
|
|
"UPDATE SYS_COLUMNS SET TABLE_ID = :new_id\n"
|
|
|
|
" WHERE TABLE_ID = :old_id;\n"
|
|
|
|
"UPDATE SYS_INDEXES"
|
|
|
|
" SET TABLE_ID = :new_id\n"
|
|
|
|
" WHERE TABLE_ID = :old_id;\n"
|
|
|
|
"UPDATE SYS_VIRTUAL"
|
|
|
|
" SET TABLE_ID = :new_id\n"
|
|
|
|
" WHERE TABLE_ID = :old_id;\n"
|
|
|
|
"END;\n", reserve_dict_mutex, trx);
|
|
|
|
|
|
|
|
return(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Get the table id to truncate.
|
|
|
|
@param truncate_t old/new table id of table to truncate
|
|
|
|
@return table_id_t table_id to use in SYS_XXXX table update. */
|
2016-09-06 08:43:16 +02:00
|
|
|
static MY_ATTRIBUTE((warn_unused_result))
|
2016-08-12 10:17:45 +02:00
|
|
|
table_id_t
|
|
|
|
row_truncate_get_trunc_table_id(
|
|
|
|
const truncate_t& truncate)
|
|
|
|
{
|
|
|
|
TableLocator tableLocator(truncate.old_table_id());
|
|
|
|
|
|
|
|
SysIndexIterator().for_each(tableLocator);
|
|
|
|
|
|
|
|
return(tableLocator.is_table_found() ?
|
|
|
|
truncate.old_table_id(): truncate.new_table_id());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Update system table to reflect new table id and root page number.
|
|
|
|
@param truncate_t old/new table id of table to truncate
|
|
|
|
and updated root_page_no of indexes.
|
|
|
|
@param new_table_id new table id
|
|
|
|
@param reserve_dict_mutex if TRUE, acquire/release
|
|
|
|
dict_sys->mutex around call to pars_sql.
|
|
|
|
@param mark_index_corrupted if true, then mark index corrupted.
|
|
|
|
@return error code or DB_SUCCESS */
|
2016-09-06 08:43:16 +02:00
|
|
|
static MY_ATTRIBUTE((warn_unused_result))
|
2016-08-12 10:17:45 +02:00
|
|
|
dberr_t
|
|
|
|
row_truncate_update_sys_tables_during_fix_up(
|
|
|
|
const truncate_t& truncate,
|
|
|
|
table_id_t new_table_id,
|
|
|
|
ibool reserve_dict_mutex,
|
|
|
|
bool mark_index_corrupted)
|
|
|
|
{
|
|
|
|
trx_t* trx = trx_allocate_for_background();
|
|
|
|
|
|
|
|
trx_set_dict_operation(trx, TRX_DICT_OP_TABLE);
|
|
|
|
|
|
|
|
table_id_t table_id = row_truncate_get_trunc_table_id(truncate);
|
|
|
|
|
|
|
|
/* Step-1: Update the root-page-no */
|
|
|
|
|
|
|
|
dberr_t err;
|
|
|
|
|
|
|
|
err = truncate.update_root_page_no(
|
|
|
|
trx, table_id, reserve_dict_mutex, mark_index_corrupted);
|
|
|
|
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
return(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Step-2: Update table-id. */
|
|
|
|
|
|
|
|
err = row_truncate_update_table_id(
|
|
|
|
table_id, new_table_id, reserve_dict_mutex, trx);
|
|
|
|
|
|
|
|
if (err == DB_SUCCESS) {
|
2016-09-06 08:43:16 +02:00
|
|
|
dict_mutex_enter_for_mysql();
|
|
|
|
|
|
|
|
/* Remove the table with old table_id from cache. */
|
|
|
|
dict_table_t* old_table = dict_table_open_on_id(
|
|
|
|
table_id, true, DICT_TABLE_OP_NORMAL);
|
|
|
|
|
|
|
|
if (old_table != NULL) {
|
|
|
|
dict_table_close(old_table, true, false);
|
|
|
|
dict_table_remove_from_cache(old_table);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Open table with new table_id and set table as
|
|
|
|
corrupted if it has FTS index. */
|
|
|
|
|
|
|
|
dict_table_t* table = dict_table_open_on_id(
|
|
|
|
new_table_id, true, DICT_TABLE_OP_NORMAL);
|
|
|
|
ut_ad(table->id == new_table_id);
|
|
|
|
|
|
|
|
bool has_internal_doc_id =
|
|
|
|
dict_table_has_fts_index(table)
|
|
|
|
|| DICT_TF2_FLAG_IS_SET(
|
|
|
|
table, DICT_TF2_FTS_HAS_DOC_ID);
|
|
|
|
|
|
|
|
if (has_internal_doc_id) {
|
|
|
|
trx->dict_operation_lock_mode = RW_X_LATCH;
|
|
|
|
fts_check_corrupt(table, trx);
|
|
|
|
trx->dict_operation_lock_mode = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
dict_table_close(table, true, false);
|
|
|
|
dict_mutex_exit_for_mysql();
|
2016-08-12 10:17:45 +02:00
|
|
|
}
|
|
|
|
|
2016-09-06 08:43:16 +02:00
|
|
|
trx_commit_for_mysql(trx);
|
|
|
|
trx_free_for_background(trx);
|
|
|
|
|
2016-08-12 10:17:45 +02:00
|
|
|
return(err);
|
|
|
|
}
|
|
|
|
|
MDEV-13564: Implement innodb_unsafe_truncate=ON for compatibility
While MariaDB Server 10.2 is not really guaranteed to be compatible
with Percona XtraBackup 2.4 (for example, the MySQL 5.7 undo log format
change that could be present in XtraBackup, but was reverted from
MariaDB in MDEV-12289), we do not want to disrupt users who have
deployed xtrabackup and MariaDB Server 10.2 in their environments.
With this change, MariaDB 10.2 will continue to use the backup-unsafe
TRUNCATE TABLE code, so that neither the undo log nor the redo log
formats will change in an incompatible way.
Undo tablespace truncation will keep using the redo log only. Recovery
or backup with old code will fail to shrink the undo tablespace files,
but the contents will be recovered just fine.
In the MariaDB Server 10.2 series only, we introduce the configuration
parameter innodb_unsafe_truncate and make it ON by default. To allow
MariaDB Backup (mariabackup) to work properly with TRUNCATE TABLE
operations, use loose_innodb_unsafe_truncate=OFF.
MariaDB Server 10.3.10 and later releases will always use the
backup-safe TRUNCATE TABLE, and this parameter will not be
added there.
recv_recovery_rollback_active(): Skip row_mysql_drop_garbage_tables()
unless innodb_unsafe_truncate=OFF. It is too unsafe to drop orphan
tables if RENAME operations are not transactional within InnoDB.
LOG_HEADER_FORMAT_10_3: Replaces LOG_HEADER_FORMAT_CURRENT.
log_init(), log_group_file_header_flush(),
srv_prepare_to_delete_redo_log_files(),
innobase_start_or_create_for_mysql(): Choose the redo log format
and subformat based on the value of innodb_unsafe_truncate.
2018-10-10 18:19:29 +02:00
|
|
|
/**
|
|
|
|
Truncate also results in assignment of new table id, update the system
|
|
|
|
SYSTEM TABLES with the new id.
|
|
|
|
@param table, table being truncated
|
|
|
|
@param new_id, new table id
|
|
|
|
@param has_internal_doc_id, has doc col (fts)
|
|
|
|
@param no_redo if true, turn-off redo logging
|
|
|
|
@param trx transaction handle
|
|
|
|
@return error code or DB_SUCCESS */
|
|
|
|
static MY_ATTRIBUTE((warn_unused_result))
|
|
|
|
dberr_t
|
|
|
|
row_truncate_update_system_tables(
|
|
|
|
dict_table_t* table,
|
|
|
|
table_id_t new_id,
|
|
|
|
bool has_internal_doc_id,
|
|
|
|
bool no_redo,
|
|
|
|
trx_t* trx)
|
|
|
|
{
|
|
|
|
dberr_t err = DB_SUCCESS;
|
|
|
|
|
|
|
|
ut_a(!dict_table_is_temporary(table));
|
|
|
|
|
|
|
|
err = row_truncate_update_table_id(table->id, new_id, FALSE, trx);
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF("ib_err_trunc_during_sys_table_update",
|
|
|
|
err = DB_ERROR;);
|
|
|
|
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
|
|
|
|
row_truncate_rollback(
|
|
|
|
table, trx, new_id, has_internal_doc_id,
|
|
|
|
no_redo, true, false);
|
|
|
|
|
|
|
|
ib::error() << "Unable to assign a new identifier to table "
|
|
|
|
<< table->name << " after truncating it. Marked the"
|
|
|
|
" table as corrupted. In-memory representation is now"
|
|
|
|
" different from the on-disk representation.";
|
|
|
|
err = DB_ERROR;
|
|
|
|
} else {
|
|
|
|
/* Drop the old FTS index */
|
|
|
|
if (has_internal_doc_id) {
|
|
|
|
|
|
|
|
ut_ad(trx_is_started(trx));
|
|
|
|
|
|
|
|
fts_drop_tables(trx, table);
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF("ib_truncate_crash_while_fts_cleanup",
|
|
|
|
DBUG_SUICIDE(););
|
|
|
|
|
|
|
|
ut_ad(trx_is_started(trx));
|
|
|
|
}
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF("ib_trunc_crash_after_fts_drop",
|
|
|
|
log_buffer_flush_to_disk();
|
|
|
|
os_thread_sleep(2000000);
|
|
|
|
DBUG_SUICIDE(););
|
|
|
|
|
|
|
|
dict_table_change_id_in_cache(table, new_id);
|
|
|
|
|
|
|
|
/* Reset the Doc ID in cache to 0 */
|
|
|
|
if (has_internal_doc_id && table->fts->cache != NULL) {
|
|
|
|
DBUG_EXECUTE_IF("ib_trunc_sleep_before_fts_cache_clear",
|
|
|
|
os_thread_sleep(10000000););
|
|
|
|
|
|
|
|
table->fts->fts_status |= TABLE_DICT_LOCKED;
|
|
|
|
fts_update_next_doc_id(trx, table, NULL, 0);
|
|
|
|
fts_cache_clear(table->fts->cache);
|
|
|
|
fts_cache_init(table->fts->cache);
|
|
|
|
table->fts->fts_status &= uint(~TABLE_DICT_LOCKED);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Prepare for the truncate process. On success all of the table's indexes will
|
|
|
|
be locked in X mode.
|
|
|
|
@param table table to truncate
|
|
|
|
@param flags tablespace flags
|
|
|
|
@return error code or DB_SUCCESS */
|
|
|
|
static MY_ATTRIBUTE((warn_unused_result))
|
|
|
|
dberr_t
|
|
|
|
row_truncate_prepare(dict_table_t* table, ulint* flags)
|
|
|
|
{
|
|
|
|
ut_ad(!dict_table_is_temporary(table));
|
|
|
|
ut_ad(dict_table_is_file_per_table(table));
|
|
|
|
|
|
|
|
*flags = fil_space_get_flags(table->space);
|
|
|
|
|
|
|
|
ut_ad(!dict_table_is_temporary(table));
|
|
|
|
|
|
|
|
dict_get_and_save_data_dir_path(table, true);
|
|
|
|
|
|
|
|
if (*flags != ULINT_UNDEFINED) {
|
|
|
|
|
|
|
|
dberr_t err = fil_prepare_for_truncate(table->space);
|
|
|
|
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
return(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return(DB_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Do foreign key checks before starting TRUNCATE.
|
|
|
|
@param table table being truncated
|
|
|
|
@param trx transaction covering the truncate
|
|
|
|
@return DB_SUCCESS or error code */
|
|
|
|
static MY_ATTRIBUTE((warn_unused_result))
|
|
|
|
dberr_t
|
|
|
|
row_truncate_foreign_key_checks(
|
|
|
|
const dict_table_t* table,
|
|
|
|
const trx_t* trx)
|
|
|
|
{
|
|
|
|
/* Check if the table is referenced by foreign key constraints from
|
|
|
|
some other table (not the table itself) */
|
|
|
|
|
|
|
|
dict_foreign_set::iterator it
|
|
|
|
= std::find_if(table->referenced_set.begin(),
|
|
|
|
table->referenced_set.end(),
|
|
|
|
dict_foreign_different_tables());
|
|
|
|
|
|
|
|
if (!srv_read_only_mode
|
|
|
|
&& it != table->referenced_set.end()
|
|
|
|
&& trx->check_foreigns) {
|
|
|
|
|
|
|
|
dict_foreign_t* foreign = *it;
|
|
|
|
|
|
|
|
FILE* ef = dict_foreign_err_file;
|
|
|
|
|
|
|
|
/* We only allow truncating a referenced table if
|
|
|
|
FOREIGN_KEY_CHECKS is set to 0 */
|
|
|
|
|
|
|
|
mutex_enter(&dict_foreign_err_mutex);
|
|
|
|
|
|
|
|
rewind(ef);
|
|
|
|
|
|
|
|
ut_print_timestamp(ef);
|
|
|
|
|
|
|
|
fputs(" Cannot truncate table ", ef);
|
|
|
|
ut_print_name(ef, trx, table->name.m_name);
|
|
|
|
fputs(" by DROP+CREATE\n"
|
|
|
|
"InnoDB: because it is referenced by ", ef);
|
|
|
|
ut_print_name(ef, trx, foreign->foreign_table_name);
|
|
|
|
putc('\n', ef);
|
|
|
|
|
|
|
|
mutex_exit(&dict_foreign_err_mutex);
|
|
|
|
|
|
|
|
return(DB_ERROR);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* TODO: could we replace the counter n_foreign_key_checks_running
|
|
|
|
with lock checks on the table? Acquire here an exclusive lock on the
|
|
|
|
table, and rewrite lock0lock.cc and the lock wait in srv0srv.cc so that
|
|
|
|
they can cope with the table having been truncated here? Foreign key
|
|
|
|
checks take an IS or IX lock on the table. */
|
|
|
|
|
|
|
|
if (table->n_foreign_key_checks_running > 0) {
|
|
|
|
ib::warn() << "Cannot truncate table " << table->name
|
|
|
|
<< " because there is a foreign key check running on"
|
|
|
|
" it.";
|
|
|
|
|
|
|
|
return(DB_ERROR);
|
|
|
|
}
|
|
|
|
|
|
|
|
return(DB_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Do some sanity checks before starting the actual TRUNCATE.
|
|
|
|
@param table table being truncated
|
|
|
|
@return DB_SUCCESS or error code */
|
|
|
|
static MY_ATTRIBUTE((warn_unused_result))
|
|
|
|
dberr_t
|
|
|
|
row_truncate_sanity_checks(
|
|
|
|
const dict_table_t* table)
|
|
|
|
{
|
|
|
|
if (dict_table_is_discarded(table)) {
|
|
|
|
|
|
|
|
return(DB_TABLESPACE_DELETED);
|
|
|
|
|
|
|
|
} else if (!table->is_readable()) {
|
|
|
|
if (fil_space_get(table->space) == NULL) {
|
|
|
|
return(DB_TABLESPACE_NOT_FOUND);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
return(DB_DECRYPTION_FAILED);
|
|
|
|
}
|
|
|
|
} else if (dict_table_is_corrupted(table)) {
|
|
|
|
|
|
|
|
return(DB_TABLE_CORRUPT);
|
|
|
|
}
|
|
|
|
|
|
|
|
return(DB_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Truncates a table for MySQL.
|
|
|
|
@param table table being truncated
|
|
|
|
@param trx transaction covering the truncate
|
|
|
|
@return error code or DB_SUCCESS */
|
|
|
|
dberr_t row_truncate_table_for_mysql(dict_table_t* table, trx_t* trx)
|
|
|
|
{
|
|
|
|
bool is_file_per_table = dict_table_is_file_per_table(table);
|
|
|
|
dberr_t err;
|
|
|
|
#ifdef UNIV_DEBUG
|
|
|
|
ulint old_space = table->space;
|
|
|
|
#endif /* UNIV_DEBUG */
|
|
|
|
TruncateLogger* logger = NULL;
|
|
|
|
|
|
|
|
/* Understanding the truncate flow.
|
|
|
|
|
|
|
|
Step-1: Perform intiial sanity check to ensure table can be truncated.
|
|
|
|
This would include check for tablespace discard status, ibd file
|
|
|
|
missing, etc ....
|
|
|
|
|
|
|
|
Step-2: Start transaction (only for non-temp table as temp-table don't
|
|
|
|
modify any data on disk doesn't need transaction object).
|
|
|
|
|
|
|
|
Step-3: Validate ownership of needed locks (Exclusive lock).
|
|
|
|
Ownership will also ensure there is no active SQL queries, INSERT,
|
|
|
|
SELECT, .....
|
|
|
|
|
|
|
|
Step-4: Stop all the background process associated with table.
|
|
|
|
|
|
|
|
Step-5: There are few foreign key related constraint under which
|
|
|
|
we can't truncate table (due to referential integrity unless it is
|
|
|
|
turned off). Ensure this condition is satisfied.
|
|
|
|
|
|
|
|
Step-6: Truncate operation can be rolled back in case of error
|
|
|
|
till some point. Associate rollback segment to record undo log.
|
|
|
|
|
|
|
|
Step-7: Generate new table-id.
|
|
|
|
Why we need new table-id ?
|
|
|
|
Purge and rollback case: we assign a new table id for the table.
|
|
|
|
Since purge and rollback look for the table based on the table id,
|
|
|
|
they see the table as 'dropped' and discard their operations.
|
|
|
|
|
|
|
|
Step-8: Log information about tablespace which includes
|
|
|
|
table and index information. If there is a crash in the next step
|
|
|
|
then during recovery we will attempt to fixup the operation.
|
|
|
|
|
|
|
|
Step-9: Drop all indexes (this include freeing of the pages
|
|
|
|
associated with them).
|
|
|
|
|
|
|
|
Step-10: Re-create new indexes.
|
|
|
|
|
|
|
|
Step-11: Update new table-id to in-memory cache (dictionary),
|
|
|
|
on-disk (INNODB_SYS_TABLES). INNODB_SYS_INDEXES also needs to
|
|
|
|
be updated to reflect updated root-page-no of new index created
|
|
|
|
and updated table-id.
|
|
|
|
|
|
|
|
Step-12: Cleanup Stage. Reset auto-inc value to 1.
|
|
|
|
Release all the locks.
|
|
|
|
Commit the transaction. Update trx operation state.
|
|
|
|
|
|
|
|
Notes:
|
|
|
|
- On error, log checkpoint is done followed writing of magic number to
|
|
|
|
truncate log file. If servers crashes after truncate, fix-up action
|
|
|
|
will not be applied.
|
|
|
|
|
|
|
|
- log checkpoint is done before starting truncate table to ensure
|
|
|
|
that previous REDO log entries are not applied if current truncate
|
|
|
|
crashes. Consider following use-case:
|
|
|
|
- create table .... insert/load table .... truncate table (crash)
|
|
|
|
- on restart table is restored .... truncate table (crash)
|
|
|
|
- on restart (assuming default log checkpoint is not done) will have
|
|
|
|
2 REDO log entries for same table. (Note 2 REDO log entries
|
|
|
|
for different table is not an issue).
|
|
|
|
For system-tablespace we can't truncate the tablespace so we need
|
|
|
|
to initiate a local cleanup that involves dropping of indexes and
|
|
|
|
re-creating them. If we apply stale entry we might end-up issuing
|
|
|
|
drop on wrong indexes.
|
|
|
|
|
|
|
|
- Insert buffer: TRUNCATE TABLE is analogous to DROP TABLE,
|
|
|
|
so we do not have to remove insert buffer records, as the
|
|
|
|
insert buffer works at a low level. If a freed page is later
|
|
|
|
reallocated, the allocator will remove the ibuf entries for
|
|
|
|
it. When we prepare to truncate *.ibd files, we remove all entries
|
|
|
|
for the table in the insert buffer tree. This is not strictly
|
|
|
|
necessary, but we can free up some space in the system tablespace.
|
|
|
|
|
|
|
|
- Linear readahead and random readahead: we use the same
|
|
|
|
method as in 3) to discard ongoing operations. (This is only
|
|
|
|
relevant for TRUNCATE TABLE by TRUNCATE TABLESPACE.)
|
|
|
|
Ensure that the table will be dropped by trx_rollback_active() in
|
|
|
|
case of a crash.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------------*/
|
|
|
|
/* Step-1: Perform intiial sanity check to ensure table can be
|
|
|
|
truncated. This would include check for tablespace discard status,
|
|
|
|
ibd file missing, etc .... */
|
|
|
|
err = row_truncate_sanity_checks(table);
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
return(err);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Step-2: Start transaction (only for non-temp table as temp-table
|
|
|
|
don't modify any data on disk doesn't need transaction object). */
|
|
|
|
if (!dict_table_is_temporary(table)) {
|
|
|
|
if (table->fts) {
|
|
|
|
fts_optimize_remove_table(table);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Avoid transaction overhead for temporary table DDL. */
|
|
|
|
trx_start_for_ddl(trx, TRX_DICT_OP_TABLE);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEBUG_SYNC_C("row_trunc_before_dict_lock");
|
|
|
|
|
|
|
|
/* Step-3: Validate ownership of needed locks (Exclusive lock).
|
|
|
|
Ownership will also ensure there is no active SQL queries, INSERT,
|
|
|
|
SELECT, .....*/
|
|
|
|
trx->op_info = "truncating table";
|
|
|
|
ut_a(trx->dict_operation_lock_mode == 0);
|
|
|
|
row_mysql_lock_data_dictionary(trx);
|
|
|
|
ut_ad(mutex_own(&dict_sys->mutex));
|
|
|
|
ut_ad(rw_lock_own(dict_operation_lock, RW_LOCK_X));
|
|
|
|
|
|
|
|
/* Step-4: Stop all the background process associated with table. */
|
|
|
|
dict_stats_wait_bg_to_stop_using_table(table, trx);
|
|
|
|
|
|
|
|
/* Step-5: There are few foreign key related constraint under which
|
|
|
|
we can't truncate table (due to referential integrity unless it is
|
|
|
|
turned off). Ensure this condition is satisfied. */
|
|
|
|
ulint fsp_flags = ULINT_UNDEFINED;
|
|
|
|
err = row_truncate_foreign_key_checks(table, trx);
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
trx_rollback_to_savepoint(trx, NULL);
|
|
|
|
return(row_truncate_complete(
|
|
|
|
table, trx, fsp_flags, logger, err));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Remove all locks except the table-level X lock. */
|
|
|
|
lock_remove_all_on_table(table, FALSE);
|
|
|
|
trx->table_id = table->id;
|
|
|
|
trx_set_dict_operation(trx, TRX_DICT_OP_TABLE);
|
|
|
|
|
|
|
|
/* Step-6: Truncate operation can be rolled back in case of error
|
|
|
|
till some point. Associate rollback segment to record undo log. */
|
|
|
|
if (!dict_table_is_temporary(table)) {
|
|
|
|
mutex_enter(&trx->undo_mutex);
|
|
|
|
|
|
|
|
trx_undo_t** pundo = &trx->rsegs.m_redo.update_undo;
|
|
|
|
err = trx_undo_assign_undo(
|
|
|
|
trx, trx->rsegs.m_redo.rseg, pundo, TRX_UNDO_UPDATE);
|
|
|
|
|
|
|
|
mutex_exit(&trx->undo_mutex);
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF("ib_err_trunc_assigning_undo_log",
|
|
|
|
err = DB_ERROR;);
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
trx_rollback_to_savepoint(trx, NULL);
|
|
|
|
return(row_truncate_complete(
|
|
|
|
table, trx, fsp_flags, logger, err));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Step-7: Generate new table-id.
|
|
|
|
Why we need new table-id ?
|
|
|
|
Purge and rollback: we assign a new table id for the
|
|
|
|
table. Since purge and rollback look for the table based on
|
|
|
|
the table id, they see the table as 'dropped' and discard
|
|
|
|
their operations. */
|
|
|
|
table_id_t new_id;
|
|
|
|
dict_hdr_get_new_id(&new_id, NULL, NULL, table, false);
|
|
|
|
|
|
|
|
/* Check if table involves FTS index. */
|
|
|
|
bool has_internal_doc_id =
|
|
|
|
dict_table_has_fts_index(table)
|
|
|
|
|| DICT_TF2_FLAG_IS_SET(table, DICT_TF2_FTS_HAS_DOC_ID);
|
|
|
|
|
|
|
|
bool no_redo = is_file_per_table && !has_internal_doc_id;
|
|
|
|
|
|
|
|
/* Step-8: Log information about tablespace which includes
|
|
|
|
table and index information. If there is a crash in the next step
|
|
|
|
then during recovery we will attempt to fixup the operation. */
|
|
|
|
|
|
|
|
/* Lock all index trees for this table, as we will truncate
|
|
|
|
the table/index and possibly change their metadata. All
|
|
|
|
DML/DDL are blocked by table level X lock, with a few exceptions
|
|
|
|
such as queries into information schema about the table,
|
|
|
|
MySQL could try to access index stats for this kind of query,
|
|
|
|
we need to use index locks to sync up */
|
|
|
|
dict_table_x_lock_indexes(table);
|
|
|
|
|
|
|
|
if (!dict_table_is_temporary(table)) {
|
|
|
|
|
|
|
|
if (is_file_per_table) {
|
|
|
|
|
|
|
|
err = row_truncate_prepare(table, &fsp_flags);
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF("ib_err_trunc_preparing_for_truncate",
|
|
|
|
err = DB_ERROR;);
|
|
|
|
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
row_truncate_rollback(
|
|
|
|
table, trx, new_id,
|
|
|
|
has_internal_doc_id,
|
|
|
|
no_redo, false, true);
|
|
|
|
return(row_truncate_complete(
|
|
|
|
table, trx, fsp_flags, logger, err));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fsp_flags = fil_space_get_flags(table->space);
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF("ib_err_trunc_preparing_for_truncate",
|
|
|
|
fsp_flags = ULINT_UNDEFINED;);
|
|
|
|
|
|
|
|
if (fsp_flags == ULINT_UNDEFINED) {
|
|
|
|
row_truncate_rollback(
|
|
|
|
table, trx, new_id,
|
|
|
|
has_internal_doc_id,
|
|
|
|
no_redo, false, true);
|
|
|
|
return(row_truncate_complete(
|
|
|
|
table, trx, fsp_flags,
|
|
|
|
logger, DB_ERROR));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
logger = UT_NEW_NOKEY(TruncateLogger(
|
|
|
|
table, fsp_flags, new_id));
|
|
|
|
|
|
|
|
err = logger->init();
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
row_truncate_rollback(
|
|
|
|
table, trx, new_id, has_internal_doc_id,
|
|
|
|
no_redo, false, true);
|
|
|
|
return(row_truncate_complete(
|
|
|
|
table, trx, fsp_flags, logger, DB_ERROR));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
err = SysIndexIterator().for_each(*logger);
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
row_truncate_rollback(
|
|
|
|
table, trx, new_id, has_internal_doc_id,
|
|
|
|
no_redo, false, true);
|
|
|
|
return(row_truncate_complete(
|
|
|
|
table, trx, fsp_flags, logger, DB_ERROR));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
ut_ad(logger->debug());
|
|
|
|
|
|
|
|
err = logger->log();
|
|
|
|
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
row_truncate_rollback(
|
|
|
|
table, trx, new_id, has_internal_doc_id,
|
|
|
|
no_redo, false, true);
|
|
|
|
return(row_truncate_complete(
|
|
|
|
table, trx, fsp_flags, logger, DB_ERROR));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF("ib_trunc_crash_after_redo_log_write_complete",
|
|
|
|
log_buffer_flush_to_disk();
|
|
|
|
os_thread_sleep(3000000);
|
|
|
|
DBUG_SUICIDE(););
|
|
|
|
|
|
|
|
/* Step-9: Drop all indexes (free index pages associated with these
|
|
|
|
indexes) */
|
|
|
|
if (!dict_table_is_temporary(table)) {
|
|
|
|
|
|
|
|
DropIndex dropIndex(table, no_redo);
|
|
|
|
|
|
|
|
err = SysIndexIterator().for_each(dropIndex);
|
|
|
|
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
|
|
|
|
row_truncate_rollback(
|
|
|
|
table, trx, new_id, has_internal_doc_id,
|
|
|
|
no_redo, true, true);
|
|
|
|
|
|
|
|
return(row_truncate_complete(
|
|
|
|
table, trx, fsp_flags, logger, err));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* For temporary tables we don't have entries in SYSTEM TABLES*/
|
|
|
|
ut_ad(fsp_is_system_temporary(table->space));
|
|
|
|
for (dict_index_t* index = UT_LIST_GET_FIRST(table->indexes);
|
|
|
|
index != NULL;
|
|
|
|
index = UT_LIST_GET_NEXT(indexes, index)) {
|
|
|
|
|
|
|
|
err = dict_truncate_index_tree_in_mem(index);
|
|
|
|
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
row_truncate_rollback(
|
|
|
|
table, trx, new_id, has_internal_doc_id,
|
|
|
|
no_redo, true, true);
|
|
|
|
return(row_truncate_complete(
|
|
|
|
table, trx, fsp_flags, logger, err));
|
|
|
|
}
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF(
|
|
|
|
"ib_trunc_crash_during_drop_index_temp_table",
|
|
|
|
log_buffer_flush_to_disk();
|
|
|
|
os_thread_sleep(2000000);
|
|
|
|
DBUG_SUICIDE(););
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_file_per_table && fsp_flags != ULINT_UNDEFINED) {
|
|
|
|
/* A single-table tablespace has initially
|
|
|
|
FIL_IBD_FILE_INITIAL_SIZE number of pages allocated and an
|
|
|
|
extra page is allocated for each of the indexes present. But in
|
|
|
|
the case of clust index 2 pages are allocated and as one is
|
|
|
|
covered in the calculation as part of table->indexes.count we
|
|
|
|
take care of the other page by adding 1. */
|
|
|
|
ulint space_size = table->indexes.count +
|
|
|
|
FIL_IBD_FILE_INITIAL_SIZE + 1;
|
|
|
|
|
|
|
|
if (has_internal_doc_id) {
|
|
|
|
/* Since aux tables are created for fts indexes and
|
|
|
|
they use seperate tablespaces. */
|
|
|
|
space_size -= ib_vector_size(table->fts->indexes);
|
|
|
|
}
|
|
|
|
|
|
|
|
fil_reinit_space_header_for_table(table, space_size, trx);
|
|
|
|
}
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF("ib_trunc_crash_with_intermediate_log_checkpoint",
|
|
|
|
log_buffer_flush_to_disk();
|
|
|
|
os_thread_sleep(2000000);
|
|
|
|
log_checkpoint(TRUE, TRUE);
|
|
|
|
os_thread_sleep(1000000);
|
|
|
|
DBUG_SUICIDE(););
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF("ib_trunc_crash_drop_reinit_done_create_to_start",
|
|
|
|
log_buffer_flush_to_disk();
|
|
|
|
os_thread_sleep(2000000);
|
|
|
|
DBUG_SUICIDE(););
|
|
|
|
|
|
|
|
/* Step-10: Re-create new indexes. */
|
|
|
|
if (!dict_table_is_temporary(table)) {
|
|
|
|
|
|
|
|
CreateIndex createIndex(table, no_redo);
|
|
|
|
|
|
|
|
err = SysIndexIterator().for_each(createIndex);
|
|
|
|
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
|
|
|
|
row_truncate_rollback(
|
|
|
|
table, trx, new_id, has_internal_doc_id,
|
|
|
|
no_redo, true, true);
|
|
|
|
|
|
|
|
return(row_truncate_complete(
|
|
|
|
table, trx, fsp_flags, logger, err));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Done with index truncation, release index tree locks,
|
|
|
|
subsequent work relates to table level metadata change */
|
|
|
|
dict_table_x_unlock_indexes(table);
|
|
|
|
|
|
|
|
if (has_internal_doc_id) {
|
|
|
|
|
|
|
|
err = row_truncate_fts(table, new_id, trx);
|
|
|
|
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
|
|
|
|
row_truncate_rollback(
|
|
|
|
table, trx, new_id, has_internal_doc_id,
|
|
|
|
no_redo, true, false);
|
|
|
|
|
|
|
|
return(row_truncate_complete(
|
|
|
|
table, trx, fsp_flags, logger, err));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Step-11: Update new table-id to in-memory cache (dictionary),
|
|
|
|
on-disk (INNODB_SYS_TABLES). INNODB_SYS_INDEXES also needs to
|
|
|
|
be updated to reflect updated root-page-no of new index created
|
|
|
|
and updated table-id. */
|
|
|
|
if (dict_table_is_temporary(table)) {
|
|
|
|
|
|
|
|
dict_table_change_id_in_cache(table, new_id);
|
|
|
|
err = DB_SUCCESS;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
/* If this fails then we are in an inconsistent state and
|
|
|
|
the results are undefined. */
|
|
|
|
ut_ad(old_space == table->space);
|
|
|
|
|
|
|
|
err = row_truncate_update_system_tables(
|
|
|
|
table, new_id, has_internal_doc_id, no_redo, trx);
|
|
|
|
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
return(row_truncate_complete(
|
|
|
|
table, trx, fsp_flags, logger, err));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF("ib_trunc_crash_on_updating_dict_sys_info",
|
|
|
|
log_buffer_flush_to_disk();
|
|
|
|
os_thread_sleep(2000000);
|
|
|
|
DBUG_SUICIDE(););
|
|
|
|
|
|
|
|
/* Step-12: Cleanup Stage. Reset auto-inc value to 1.
|
|
|
|
Release all the locks.
|
|
|
|
Commit the transaction. Update trx operation state. */
|
|
|
|
dict_table_autoinc_lock(table);
|
|
|
|
dict_table_autoinc_initialize(table, 1);
|
|
|
|
dict_table_autoinc_unlock(table);
|
|
|
|
|
|
|
|
if (trx_is_started(trx)) {
|
|
|
|
|
|
|
|
trx_commit_for_mysql(trx);
|
|
|
|
}
|
|
|
|
|
|
|
|
return(row_truncate_complete(table, trx, fsp_flags, logger, err));
|
|
|
|
}
|
|
|
|
|
2016-08-12 10:17:45 +02:00
|
|
|
/**
|
|
|
|
Fix the table truncate by applying information parsed from TRUNCATE log.
|
|
|
|
Fix-up includes re-creating table (drop and re-create indexes)
|
|
|
|
@return error code or DB_SUCCESS */
|
|
|
|
dberr_t
|
|
|
|
truncate_t::fixup_tables_in_system_tablespace()
|
|
|
|
{
|
|
|
|
dberr_t err = DB_SUCCESS;
|
|
|
|
|
|
|
|
/* Using the info cached during REDO log scan phase fix the
|
|
|
|
table truncate. */
|
|
|
|
|
|
|
|
for (tables_t::iterator it = s_tables.begin();
|
|
|
|
it != s_tables.end();) {
|
|
|
|
|
|
|
|
if ((*it)->m_space_id == TRX_SYS_SPACE) {
|
|
|
|
/* Step-1: Drop and re-create indexes. */
|
|
|
|
ib::info() << "Completing truncate for table with "
|
|
|
|
"id (" << (*it)->m_old_table_id << ") "
|
|
|
|
"residing in the system tablespace.";
|
|
|
|
|
|
|
|
err = fil_recreate_table(
|
|
|
|
(*it)->m_space_id,
|
|
|
|
(*it)->m_format_flags,
|
|
|
|
(*it)->m_tablespace_flags,
|
|
|
|
(*it)->m_tablename,
|
|
|
|
**it);
|
|
|
|
|
|
|
|
/* Step-2: Update the SYS_XXXX tables to reflect
|
|
|
|
this new table_id and root_page_no. */
|
|
|
|
table_id_t new_id;
|
|
|
|
|
|
|
|
dict_hdr_get_new_id(&new_id, NULL, NULL, NULL, true);
|
|
|
|
|
|
|
|
err = row_truncate_update_sys_tables_during_fix_up(
|
|
|
|
**it, new_id, TRUE,
|
|
|
|
(err == DB_SUCCESS) ? false : true);
|
|
|
|
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
os_file_delete(
|
|
|
|
innodb_log_file_key, (*it)->m_log_file_name);
|
|
|
|
UT_DELETE(*it);
|
|
|
|
it = s_tables.erase(it);
|
|
|
|
} else {
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Also clear the map used to track tablespace truncated. */
|
|
|
|
s_truncated_tables.clear();
|
|
|
|
|
|
|
|
return(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Fix the table truncate by applying information parsed from TRUNCATE log.
|
|
|
|
Fix-up includes re-creating tablespace.
|
|
|
|
@return error code or DB_SUCCESS */
|
|
|
|
dberr_t
|
|
|
|
truncate_t::fixup_tables_in_non_system_tablespace()
|
|
|
|
{
|
|
|
|
dberr_t err = DB_SUCCESS;
|
|
|
|
|
|
|
|
/* Using the info cached during REDO log scan phase fix the
|
|
|
|
table truncate. */
|
|
|
|
tables_t::iterator end = s_tables.end();
|
|
|
|
|
|
|
|
for (tables_t::iterator it = s_tables.begin(); it != end; ++it) {
|
|
|
|
|
|
|
|
/* All tables in the system tablespace have already been
|
|
|
|
done and erased from this list. */
|
|
|
|
ut_a((*it)->m_space_id != TRX_SYS_SPACE);
|
|
|
|
|
2017-01-16 15:02:42 +01:00
|
|
|
/* Drop tablespace, drop indexes and re-create indexes. */
|
2016-08-12 10:17:45 +02:00
|
|
|
|
2017-01-16 15:02:42 +01:00
|
|
|
ib::info() << "Completing truncate for table with "
|
|
|
|
"id (" << (*it)->m_old_table_id << ") "
|
|
|
|
"residing in file-per-table tablespace with "
|
|
|
|
"id (" << (*it)->m_space_id << ")";
|
2016-08-12 10:17:45 +02:00
|
|
|
|
2017-01-16 15:02:42 +01:00
|
|
|
if (!fil_space_get((*it)->m_space_id)) {
|
2016-09-06 08:43:16 +02:00
|
|
|
|
2017-01-16 15:02:42 +01:00
|
|
|
/* Create the database directory for name,
|
|
|
|
if it does not exist yet */
|
|
|
|
fil_create_directory_for_tablename(
|
|
|
|
(*it)->m_tablename);
|
2016-08-12 10:17:45 +02:00
|
|
|
|
2017-01-16 15:02:42 +01:00
|
|
|
err = fil_ibd_create(
|
2016-08-12 10:17:45 +02:00
|
|
|
(*it)->m_space_id,
|
|
|
|
(*it)->m_tablename,
|
2017-01-16 15:02:42 +01:00
|
|
|
(*it)->m_dir_path,
|
|
|
|
(*it)->m_tablespace_flags,
|
|
|
|
FIL_IBD_FILE_INITIAL_SIZE,
|
|
|
|
(*it)->m_encryption,
|
|
|
|
(*it)->m_key_id);
|
2016-08-12 10:17:45 +02:00
|
|
|
|
2017-01-16 15:02:42 +01:00
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
/* If checkpoint is not yet done
|
|
|
|
and table is dropped and then we might
|
|
|
|
still have REDO entries for this table
|
|
|
|
which are INVALID. Ignore them. */
|
|
|
|
ib::warn() << "Failed to create"
|
|
|
|
" tablespace for "
|
|
|
|
<< (*it)->m_space_id
|
|
|
|
<< " space-id";
|
|
|
|
err = DB_ERROR;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2016-08-12 10:17:45 +02:00
|
|
|
|
2017-01-16 15:02:42 +01:00
|
|
|
ut_ad(fil_space_get((*it)->m_space_id));
|
2016-08-12 10:17:45 +02:00
|
|
|
|
2017-01-16 15:02:42 +01:00
|
|
|
err = fil_recreate_tablespace(
|
|
|
|
(*it)->m_space_id,
|
|
|
|
(*it)->m_format_flags,
|
|
|
|
(*it)->m_tablespace_flags,
|
|
|
|
(*it)->m_tablename,
|
|
|
|
**it, log_get_lsn());
|
2016-08-12 10:17:45 +02:00
|
|
|
|
|
|
|
/* Step-2: Update the SYS_XXXX tables to reflect new
|
|
|
|
table-id and root_page_no. */
|
|
|
|
table_id_t new_id;
|
|
|
|
|
|
|
|
dict_hdr_get_new_id(&new_id, NULL, NULL, NULL, true);
|
|
|
|
|
|
|
|
err = row_truncate_update_sys_tables_during_fix_up(
|
|
|
|
**it, new_id, TRUE, (err == DB_SUCCESS) ? false : true);
|
|
|
|
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err == DB_SUCCESS && s_tables.size() > 0) {
|
|
|
|
|
|
|
|
log_make_checkpoint_at(LSN_MAX, TRUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (ulint i = 0; i < s_tables.size(); ++i) {
|
|
|
|
os_file_delete(
|
|
|
|
innodb_log_file_key, s_tables[i]->m_log_file_name);
|
|
|
|
UT_DELETE(s_tables[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
s_tables.clear();
|
|
|
|
|
|
|
|
return(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Constructor
|
|
|
|
|
|
|
|
@param old_table_id old table id assigned to table before truncate
|
|
|
|
@param new_table_id new table id that will be assigned to table
|
|
|
|
after truncate
|
|
|
|
@param dir_path directory path */
|
|
|
|
|
|
|
|
truncate_t::truncate_t(
|
|
|
|
table_id_t old_table_id,
|
|
|
|
table_id_t new_table_id,
|
|
|
|
const char* dir_path)
|
|
|
|
:
|
|
|
|
m_space_id(),
|
|
|
|
m_old_table_id(old_table_id),
|
|
|
|
m_new_table_id(new_table_id),
|
|
|
|
m_dir_path(),
|
|
|
|
m_tablename(),
|
|
|
|
m_tablespace_flags(),
|
|
|
|
m_format_flags(),
|
|
|
|
m_indexes(),
|
|
|
|
m_log_lsn(),
|
|
|
|
m_log_file_name(),
|
|
|
|
/* JAN: TODO: Encryption */
|
2017-03-30 12:48:42 +02:00
|
|
|
m_encryption(FIL_ENCRYPTION_DEFAULT),
|
2016-08-12 10:17:45 +02:00
|
|
|
m_key_id(FIL_DEFAULT_ENCRYPTION_KEY)
|
|
|
|
{
|
|
|
|
if (dir_path != NULL) {
|
|
|
|
m_dir_path = mem_strdup(dir_path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Consturctor
|
|
|
|
|
|
|
|
@param log_file_name parse the log file during recovery to populate
|
|
|
|
information related to table to truncate */
|
|
|
|
truncate_t::truncate_t(
|
|
|
|
const char* log_file_name)
|
|
|
|
:
|
|
|
|
m_space_id(),
|
|
|
|
m_old_table_id(),
|
|
|
|
m_new_table_id(),
|
|
|
|
m_dir_path(),
|
|
|
|
m_tablename(),
|
|
|
|
m_tablespace_flags(),
|
|
|
|
m_format_flags(),
|
|
|
|
m_indexes(),
|
|
|
|
m_log_lsn(),
|
|
|
|
m_log_file_name(),
|
|
|
|
/* JAN: TODO: Encryption */
|
2017-03-30 12:48:42 +02:00
|
|
|
m_encryption(FIL_ENCRYPTION_DEFAULT),
|
2016-08-12 10:17:45 +02:00
|
|
|
m_key_id(FIL_DEFAULT_ENCRYPTION_KEY)
|
|
|
|
|
|
|
|
{
|
|
|
|
m_log_file_name = mem_strdup(log_file_name);
|
|
|
|
if (m_log_file_name == NULL) {
|
|
|
|
ib::fatal() << "Failed creating truncate_t; out of memory";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Constructor */
|
|
|
|
|
|
|
|
truncate_t::index_t::index_t()
|
|
|
|
:
|
|
|
|
m_id(),
|
|
|
|
m_type(),
|
|
|
|
m_root_page_no(FIL_NULL),
|
|
|
|
m_new_root_page_no(FIL_NULL),
|
|
|
|
m_n_fields(),
|
|
|
|
m_trx_id_pos(ULINT_UNDEFINED),
|
|
|
|
m_fields()
|
|
|
|
{
|
|
|
|
/* Do nothing */
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Destructor */
|
|
|
|
|
|
|
|
truncate_t::~truncate_t()
|
|
|
|
{
|
|
|
|
if (m_dir_path != NULL) {
|
|
|
|
ut_free(m_dir_path);
|
|
|
|
m_dir_path = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_tablename != NULL) {
|
|
|
|
ut_free(m_tablename);
|
|
|
|
m_tablename = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_log_file_name != NULL) {
|
|
|
|
ut_free(m_log_file_name);
|
|
|
|
m_log_file_name = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_indexes.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
@return number of indexes parsed from the log record */
|
|
|
|
|
|
|
|
size_t
|
|
|
|
truncate_t::indexes() const
|
|
|
|
{
|
|
|
|
return(m_indexes.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Update root page number in SYS_XXXX tables.
|
|
|
|
|
|
|
|
@param trx transaction object
|
|
|
|
@param table_id table id for which information needs to
|
|
|
|
be updated.
|
|
|
|
@param reserve_dict_mutex if TRUE, acquire/release
|
|
|
|
dict_sys->mutex around call to pars_sql.
|
|
|
|
@param mark_index_corrupted if true, then mark index corrupted.
|
|
|
|
@return DB_SUCCESS or error code */
|
|
|
|
|
|
|
|
dberr_t
|
|
|
|
truncate_t::update_root_page_no(
|
|
|
|
trx_t* trx,
|
|
|
|
table_id_t table_id,
|
|
|
|
ibool reserve_dict_mutex,
|
|
|
|
bool mark_index_corrupted) const
|
|
|
|
{
|
|
|
|
indexes_t::const_iterator end = m_indexes.end();
|
|
|
|
|
|
|
|
dberr_t err = DB_SUCCESS;
|
|
|
|
|
|
|
|
for (indexes_t::const_iterator it = m_indexes.begin();
|
|
|
|
it != end;
|
|
|
|
++it) {
|
|
|
|
|
|
|
|
pars_info_t* info = pars_info_create();
|
|
|
|
|
|
|
|
pars_info_add_int4_literal(
|
|
|
|
info, "page_no", it->m_new_root_page_no);
|
|
|
|
|
|
|
|
pars_info_add_ull_literal(info, "table_id", table_id);
|
|
|
|
|
|
|
|
pars_info_add_ull_literal(
|
|
|
|
info, "index_id",
|
|
|
|
(mark_index_corrupted ? -1 : it->m_id));
|
|
|
|
|
|
|
|
err = que_eval_sql(
|
|
|
|
info,
|
|
|
|
"PROCEDURE RENUMBER_IDX_PAGE_NO_PROC () IS\n"
|
|
|
|
"BEGIN\n"
|
|
|
|
"UPDATE SYS_INDEXES"
|
|
|
|
" SET PAGE_NO = :page_no\n"
|
|
|
|
" WHERE TABLE_ID = :table_id"
|
|
|
|
" AND ID = :index_id;\n"
|
|
|
|
"END;\n", reserve_dict_mutex, trx);
|
|
|
|
|
|
|
|
if (err != DB_SUCCESS) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Check whether a tablespace was truncated during recovery
|
|
|
|
@param space_id tablespace id to check
|
|
|
|
@return true if the tablespace was truncated */
|
|
|
|
|
|
|
|
bool
|
|
|
|
truncate_t::is_tablespace_truncated(ulint space_id)
|
|
|
|
{
|
|
|
|
tables_t::iterator end = s_tables.end();
|
|
|
|
|
|
|
|
for (tables_t::iterator it = s_tables.begin(); it != end; ++it) {
|
|
|
|
|
|
|
|
if ((*it)->m_space_id == space_id) {
|
|
|
|
|
|
|
|
return(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Was tablespace truncated (on crash before checkpoint).
|
|
|
|
If the MLOG_TRUNCATE redo-record is still available then tablespace
|
|
|
|
was truncated and checkpoint is yet to happen.
|
|
|
|
@param[in] space_id tablespace id to check.
|
|
|
|
@return true if tablespace is was truncated. */
|
|
|
|
bool
|
|
|
|
truncate_t::was_tablespace_truncated(ulint space_id)
|
|
|
|
{
|
|
|
|
return(s_truncated_tables.find(space_id) != s_truncated_tables.end());
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Get the lsn associated with space.
|
|
|
|
@param[in] space_id tablespace id to check.
|
|
|
|
@return associated lsn. */
|
|
|
|
lsn_t
|
|
|
|
truncate_t::get_truncated_tablespace_init_lsn(ulint space_id)
|
|
|
|
{
|
|
|
|
ut_ad(was_tablespace_truncated(space_id));
|
|
|
|
|
|
|
|
return(s_truncated_tables.find(space_id)->second);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Parses log record during recovery
|
|
|
|
@param start_ptr buffer containing log body to parse
|
|
|
|
@param end_ptr buffer end
|
|
|
|
|
|
|
|
@return DB_SUCCESS or error code */
|
|
|
|
|
|
|
|
dberr_t
|
|
|
|
truncate_t::parse(
|
|
|
|
byte* start_ptr,
|
|
|
|
const byte* end_ptr)
|
|
|
|
{
|
|
|
|
/* Parse lsn, space-id, format-flags and tablespace-flags. */
|
|
|
|
if (end_ptr < start_ptr + (8 + 4 + 4 + 4)) {
|
|
|
|
return(DB_FAIL);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_log_lsn = mach_read_from_8(start_ptr);
|
|
|
|
start_ptr += 8;
|
|
|
|
|
|
|
|
m_space_id = mach_read_from_4(start_ptr);
|
|
|
|
start_ptr += 4;
|
|
|
|
|
|
|
|
m_format_flags = mach_read_from_4(start_ptr);
|
|
|
|
start_ptr += 4;
|
|
|
|
|
|
|
|
m_tablespace_flags = mach_read_from_4(start_ptr);
|
|
|
|
start_ptr += 4;
|
|
|
|
|
|
|
|
/* Parse table-name. */
|
|
|
|
if (end_ptr < start_ptr + (2)) {
|
|
|
|
return(DB_FAIL);
|
|
|
|
}
|
|
|
|
|
|
|
|
ulint n_tablename_len = mach_read_from_2(start_ptr);
|
|
|
|
start_ptr += 2;
|
|
|
|
|
|
|
|
if (n_tablename_len > 0) {
|
|
|
|
if (end_ptr < start_ptr + n_tablename_len) {
|
|
|
|
return(DB_FAIL);
|
|
|
|
}
|
|
|
|
m_tablename = mem_strdup(reinterpret_cast<char*>(start_ptr));
|
|
|
|
ut_ad(m_tablename[n_tablename_len - 1] == 0);
|
|
|
|
start_ptr += n_tablename_len;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Parse and read old/new table-id, number of indexes */
|
|
|
|
if (end_ptr < start_ptr + (8 + 8 + 2 + 2)) {
|
|
|
|
return(DB_FAIL);
|
|
|
|
}
|
|
|
|
|
|
|
|
ut_ad(m_indexes.empty());
|
|
|
|
|
|
|
|
m_old_table_id = mach_read_from_8(start_ptr);
|
|
|
|
start_ptr += 8;
|
|
|
|
|
|
|
|
m_new_table_id = mach_read_from_8(start_ptr);
|
|
|
|
start_ptr += 8;
|
|
|
|
|
|
|
|
ulint n_indexes = mach_read_from_2(start_ptr);
|
|
|
|
start_ptr += 2;
|
|
|
|
|
|
|
|
/* Parse the remote directory from TRUNCATE log record */
|
|
|
|
{
|
|
|
|
ulint n_tabledirpath_len = mach_read_from_2(start_ptr);
|
|
|
|
start_ptr += 2;
|
|
|
|
|
|
|
|
if (end_ptr < start_ptr + n_tabledirpath_len) {
|
|
|
|
return(DB_FAIL);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (n_tabledirpath_len > 0) {
|
|
|
|
|
|
|
|
m_dir_path = mem_strdup(reinterpret_cast<char*>(start_ptr));
|
|
|
|
ut_ad(m_dir_path[n_tabledirpath_len - 1] == 0);
|
|
|
|
start_ptr += n_tabledirpath_len;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Parse index ids and types from TRUNCATE log record */
|
|
|
|
for (ulint i = 0; i < n_indexes; ++i) {
|
|
|
|
index_t index;
|
|
|
|
|
|
|
|
if (end_ptr < start_ptr + (8 + 4 + 4 + 4)) {
|
|
|
|
return(DB_FAIL);
|
|
|
|
}
|
|
|
|
|
|
|
|
index.m_id = mach_read_from_8(start_ptr);
|
|
|
|
start_ptr += 8;
|
|
|
|
|
|
|
|
index.m_type = mach_read_from_4(start_ptr);
|
|
|
|
start_ptr += 4;
|
|
|
|
|
|
|
|
index.m_root_page_no = mach_read_from_4(start_ptr);
|
|
|
|
start_ptr += 4;
|
|
|
|
|
|
|
|
index.m_trx_id_pos = mach_read_from_4(start_ptr);
|
|
|
|
start_ptr += 4;
|
|
|
|
|
2016-09-06 08:43:16 +02:00
|
|
|
if (!(index.m_type & DICT_FTS)) {
|
|
|
|
m_indexes.push_back(index);
|
|
|
|
}
|
2016-08-12 10:17:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ut_ad(!m_indexes.empty());
|
|
|
|
|
2017-01-19 11:06:13 +01:00
|
|
|
if (FSP_FLAGS_GET_ZIP_SSIZE(m_tablespace_flags)) {
|
2016-08-12 10:17:45 +02:00
|
|
|
|
|
|
|
/* Parse the number of index fields from TRUNCATE log record */
|
|
|
|
for (ulint i = 0; i < m_indexes.size(); ++i) {
|
|
|
|
|
|
|
|
if (end_ptr < start_ptr + (2 + 2)) {
|
|
|
|
return(DB_FAIL);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_indexes[i].m_n_fields = mach_read_from_2(start_ptr);
|
|
|
|
start_ptr += 2;
|
|
|
|
|
|
|
|
ulint len = mach_read_from_2(start_ptr);
|
|
|
|
start_ptr += 2;
|
|
|
|
|
|
|
|
if (end_ptr < start_ptr + len) {
|
|
|
|
return(DB_FAIL);
|
|
|
|
}
|
|
|
|
|
|
|
|
index_t& index = m_indexes[i];
|
|
|
|
|
|
|
|
/* Should be NUL terminated. */
|
|
|
|
ut_ad((start_ptr)[len - 1] == 0);
|
|
|
|
|
|
|
|
index_t::fields_t::iterator end;
|
|
|
|
|
|
|
|
end = index.m_fields.end();
|
|
|
|
|
|
|
|
index.m_fields.insert(
|
|
|
|
end, start_ptr, &(start_ptr)[len]);
|
|
|
|
|
|
|
|
start_ptr += len;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return(DB_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Parse log record from REDO log file during recovery.
|
|
|
|
@param[in,out] start_ptr buffer containing log body to parse
|
|
|
|
@param[in] end_ptr buffer end
|
|
|
|
@param[in] space_id tablespace identifier
|
|
|
|
@return parsed upto or NULL. */
|
|
|
|
byte*
|
|
|
|
truncate_t::parse_redo_entry(
|
|
|
|
byte* start_ptr,
|
|
|
|
const byte* end_ptr,
|
|
|
|
ulint space_id)
|
|
|
|
{
|
|
|
|
lsn_t lsn;
|
|
|
|
|
|
|
|
/* Parse space-id, lsn */
|
|
|
|
if (end_ptr < (start_ptr + 8)) {
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
lsn = mach_read_from_8(start_ptr);
|
|
|
|
start_ptr += 8;
|
|
|
|
|
|
|
|
/* Tablespace can't exist in both state.
|
|
|
|
(scheduled-for-truncate, was-truncated). */
|
|
|
|
if (!is_tablespace_truncated(space_id)) {
|
|
|
|
|
|
|
|
truncated_tables_t::iterator it =
|
|
|
|
s_truncated_tables.find(space_id);
|
|
|
|
|
|
|
|
if (it == s_truncated_tables.end()) {
|
|
|
|
s_truncated_tables.insert(
|
|
|
|
std::pair<ulint, lsn_t>(space_id, lsn));
|
|
|
|
} else {
|
|
|
|
it->second = lsn;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return(start_ptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Set the truncate log values for a compressed table.
|
|
|
|
@param index index from which recreate infoormation needs to be extracted
|
|
|
|
@return DB_SUCCESS or error code */
|
|
|
|
|
|
|
|
dberr_t
|
|
|
|
truncate_t::index_t::set(
|
|
|
|
const dict_index_t* index)
|
|
|
|
{
|
|
|
|
/* Get trx-id column position (set only for clustered index) */
|
|
|
|
if (dict_index_is_clust(index)) {
|
|
|
|
m_trx_id_pos = dict_index_get_sys_col_pos(index, DATA_TRX_ID);
|
|
|
|
ut_ad(m_trx_id_pos > 0);
|
|
|
|
ut_ad(m_trx_id_pos != ULINT_UNDEFINED);
|
|
|
|
} else {
|
|
|
|
m_trx_id_pos = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Original logic set this field differently if page is not leaf.
|
|
|
|
For truncate case this being first page to get created it is
|
|
|
|
always a leaf page and so we don't need that condition here. */
|
|
|
|
m_n_fields = dict_index_get_n_fields(index);
|
|
|
|
|
|
|
|
/* See requirements of page_zip_fields_encode for size. */
|
|
|
|
ulint encoded_buf_size = (m_n_fields + 1) * 2;
|
|
|
|
byte* encoded_buf = UT_NEW_ARRAY_NOKEY(byte, encoded_buf_size);
|
|
|
|
|
|
|
|
if (encoded_buf == NULL) {
|
|
|
|
return(DB_OUT_OF_MEMORY);
|
|
|
|
}
|
|
|
|
|
|
|
|
ulint len = page_zip_fields_encode(
|
|
|
|
m_n_fields, index, m_trx_id_pos, encoded_buf);
|
|
|
|
ut_a(len <= encoded_buf_size);
|
|
|
|
|
|
|
|
/* Append the encoded fields data. */
|
|
|
|
m_fields.insert(m_fields.end(), &encoded_buf[0], &encoded_buf[len]);
|
|
|
|
|
|
|
|
/* NUL terminate the encoded data */
|
|
|
|
m_fields.push_back(0);
|
|
|
|
|
|
|
|
UT_DELETE_ARRAY(encoded_buf);
|
|
|
|
|
|
|
|
return(DB_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Create an index for a table.
|
|
|
|
@param[in] table_name table name, for which to create
|
|
|
|
the index
|
|
|
|
@param[in] space_id space id where we have to
|
|
|
|
create the index
|
|
|
|
@param[in] page_size page size of the .ibd file
|
|
|
|
@param[in] index_type type of index to truncate
|
|
|
|
@param[in] index_id id of index to truncate
|
|
|
|
@param[in] btr_redo_create_info control info for ::btr_create()
|
|
|
|
@param[in,out] mtr mini-transaction covering the
|
|
|
|
create index
|
|
|
|
@return root page no or FIL_NULL on failure */
|
|
|
|
ulint
|
|
|
|
truncate_t::create_index(
|
|
|
|
const char* table_name,
|
|
|
|
ulint space_id,
|
|
|
|
const page_size_t& page_size,
|
|
|
|
ulint index_type,
|
|
|
|
index_id_t index_id,
|
|
|
|
const btr_create_t& btr_redo_create_info,
|
|
|
|
mtr_t* mtr) const
|
|
|
|
{
|
|
|
|
ulint root_page_no = btr_create(
|
|
|
|
index_type, space_id, page_size, index_id,
|
|
|
|
NULL, &btr_redo_create_info, mtr);
|
|
|
|
|
|
|
|
if (root_page_no == FIL_NULL) {
|
|
|
|
|
|
|
|
ib::info() << "innodb_force_recovery was set to "
|
|
|
|
<< srv_force_recovery << ". Continuing crash recovery"
|
|
|
|
" even though we failed to create index " << index_id
|
|
|
|
<< " for compressed table '" << table_name << "' with"
|
|
|
|
" tablespace " << space_id << " during recovery";
|
|
|
|
}
|
|
|
|
|
|
|
|
return(root_page_no);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Check if index has been modified since TRUNCATE log snapshot
|
|
|
|
was recorded.
|
|
|
|
@param space_id space_id where table/indexes resides.
|
|
|
|
@param root_page_no root page of index that needs to be verified.
|
|
|
|
@return true if modified else false */
|
|
|
|
|
|
|
|
bool
|
|
|
|
truncate_t::is_index_modified_since_logged(
|
|
|
|
ulint space_id,
|
|
|
|
ulint root_page_no) const
|
|
|
|
{
|
|
|
|
mtr_t mtr;
|
|
|
|
bool found;
|
|
|
|
const page_size_t& page_size = fil_space_get_page_size(space_id,
|
|
|
|
&found);
|
|
|
|
dberr_t err = DB_SUCCESS;
|
|
|
|
|
|
|
|
ut_ad(found);
|
|
|
|
|
|
|
|
mtr_start(&mtr);
|
|
|
|
|
|
|
|
/* Root page could be in free state if truncate crashed after drop_index
|
|
|
|
and page was not allocated for any other object. */
|
|
|
|
buf_block_t* block= buf_page_get_gen(
|
|
|
|
page_id_t(space_id, root_page_no), page_size, RW_X_LATCH, NULL,
|
|
|
|
BUF_GET_POSSIBLY_FREED, __FILE__, __LINE__, &mtr, &err);
|
|
|
|
|
|
|
|
page_t* root = buf_block_get_frame(block);
|
|
|
|
|
|
|
|
#ifdef UNIV_DEBUG
|
|
|
|
/* If the root page has been freed as part of truncate drop_index action
|
|
|
|
and not yet allocated for any object still the pagelsn > snapshot lsn */
|
|
|
|
if (block->page.file_page_was_freed) {
|
|
|
|
ut_ad(mach_read_from_8(root + FIL_PAGE_LSN) > m_log_lsn);
|
|
|
|
}
|
|
|
|
#endif /* UNIV_DEBUG */
|
|
|
|
|
|
|
|
lsn_t page_lsn = mach_read_from_8(root + FIL_PAGE_LSN);
|
|
|
|
|
|
|
|
mtr_commit(&mtr);
|
|
|
|
|
|
|
|
if (page_lsn > m_log_lsn) {
|
|
|
|
return(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
return(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Drop indexes for a table.
|
|
|
|
@param space_id space_id where table/indexes resides. */
|
|
|
|
|
|
|
|
void
|
|
|
|
truncate_t::drop_indexes(
|
|
|
|
ulint space_id) const
|
|
|
|
{
|
|
|
|
mtr_t mtr;
|
|
|
|
ulint root_page_no = FIL_NULL;
|
|
|
|
|
|
|
|
indexes_t::const_iterator end = m_indexes.end();
|
|
|
|
|
|
|
|
for (indexes_t::const_iterator it = m_indexes.begin();
|
|
|
|
it != end;
|
|
|
|
++it) {
|
|
|
|
|
|
|
|
root_page_no = it->m_root_page_no;
|
|
|
|
|
|
|
|
bool found;
|
|
|
|
const page_size_t& page_size
|
|
|
|
= fil_space_get_page_size(space_id, &found);
|
|
|
|
|
|
|
|
ut_ad(found);
|
|
|
|
|
|
|
|
if (is_index_modified_since_logged(
|
|
|
|
space_id, root_page_no)) {
|
|
|
|
/* Page has been modified since TRUNCATE log snapshot
|
|
|
|
was recorded so not safe to drop the index. */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
mtr_start(&mtr);
|
|
|
|
|
|
|
|
if (space_id != TRX_SYS_SPACE) {
|
|
|
|
/* Do not log changes for single-table
|
|
|
|
tablespaces, we are in recovery mode. */
|
|
|
|
mtr_set_log_mode(&mtr, MTR_LOG_NO_REDO);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (root_page_no != FIL_NULL) {
|
|
|
|
const page_id_t root_page_id(space_id, root_page_no);
|
|
|
|
|
|
|
|
btr_free_if_exists(
|
|
|
|
root_page_id, page_size, it->m_id, &mtr);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If tree is already freed then we might return immediately
|
|
|
|
in which case we need to release the lock we have acquired
|
|
|
|
on root_page. */
|
|
|
|
mtr_commit(&mtr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Create the indexes for a table
|
|
|
|
@param[in] table_name table name, for which to create the indexes
|
|
|
|
@param[in] space_id space id where we have to create the indexes
|
|
|
|
@param[in] page_size page size of the .ibd file
|
|
|
|
@param[in] flags tablespace flags
|
|
|
|
@param[in] format_flags page format flags
|
|
|
|
@return DB_SUCCESS or error code. */
|
|
|
|
dberr_t
|
|
|
|
truncate_t::create_indexes(
|
|
|
|
const char* table_name,
|
|
|
|
ulint space_id,
|
|
|
|
const page_size_t& page_size,
|
|
|
|
ulint flags,
|
|
|
|
ulint format_flags)
|
|
|
|
{
|
|
|
|
mtr_t mtr;
|
|
|
|
|
|
|
|
mtr_start(&mtr);
|
|
|
|
|
|
|
|
if (space_id != TRX_SYS_SPACE) {
|
|
|
|
/* Do not log changes for single-table tablespaces, we
|
|
|
|
are in recovery mode. */
|
|
|
|
mtr_set_log_mode(&mtr, MTR_LOG_NO_REDO);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Create all new index trees with table format, index ids, index
|
|
|
|
types, number of index fields and index field information taken
|
|
|
|
out from the TRUNCATE log record. */
|
|
|
|
|
|
|
|
ulint root_page_no = FIL_NULL;
|
|
|
|
indexes_t::iterator end = m_indexes.end();
|
|
|
|
for (indexes_t::iterator it = m_indexes.begin();
|
|
|
|
it != end;
|
|
|
|
++it) {
|
|
|
|
|
|
|
|
btr_create_t btr_redo_create_info(
|
2017-01-19 11:06:13 +01:00
|
|
|
FSP_FLAGS_GET_ZIP_SSIZE(flags)
|
2016-08-12 10:17:45 +02:00
|
|
|
? &it->m_fields[0] : NULL);
|
|
|
|
|
|
|
|
btr_redo_create_info.format_flags = format_flags;
|
|
|
|
|
2017-01-19 11:06:13 +01:00
|
|
|
if (FSP_FLAGS_GET_ZIP_SSIZE(flags)) {
|
2016-08-12 10:17:45 +02:00
|
|
|
|
|
|
|
btr_redo_create_info.n_fields = it->m_n_fields;
|
|
|
|
/* Skip the NUL appended field */
|
|
|
|
btr_redo_create_info.field_len =
|
|
|
|
it->m_fields.size() - 1;
|
|
|
|
btr_redo_create_info.trx_id_pos = it->m_trx_id_pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
root_page_no = create_index(
|
|
|
|
table_name, space_id, page_size, it->m_type, it->m_id,
|
|
|
|
btr_redo_create_info, &mtr);
|
|
|
|
|
|
|
|
if (root_page_no == FIL_NULL) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
it->m_new_root_page_no = root_page_no;
|
|
|
|
}
|
|
|
|
|
|
|
|
mtr_commit(&mtr);
|
|
|
|
|
|
|
|
return(root_page_no == FIL_NULL ? DB_ERROR : DB_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Write a TRUNCATE log record for fixing up table if truncate crashes.
|
|
|
|
@param start_ptr buffer to write log record
|
|
|
|
@param end_ptr buffer end
|
|
|
|
@param space_id space id
|
|
|
|
@param tablename the table name in the usual databasename/tablename
|
|
|
|
format of InnoDB
|
|
|
|
@param flags tablespace flags
|
|
|
|
@param format_flags page format
|
|
|
|
@param lsn lsn while logging
|
|
|
|
@return DB_SUCCESS or error code */
|
|
|
|
|
|
|
|
dberr_t
|
|
|
|
truncate_t::write(
|
|
|
|
byte* start_ptr,
|
|
|
|
byte* end_ptr,
|
|
|
|
ulint space_id,
|
|
|
|
const char* tablename,
|
|
|
|
ulint flags,
|
|
|
|
ulint format_flags,
|
|
|
|
lsn_t lsn) const
|
|
|
|
{
|
|
|
|
if (end_ptr < start_ptr) {
|
|
|
|
return(DB_FAIL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* LSN, Type, Space-ID, format-flag (also know as log_flag.
|
|
|
|
Stored in page_no field), tablespace flags */
|
|
|
|
if (end_ptr < (start_ptr + (8 + 4 + 4 + 4))) {
|
|
|
|
return(DB_FAIL);
|
|
|
|
}
|
|
|
|
|
|
|
|
mach_write_to_8(start_ptr, lsn);
|
|
|
|
start_ptr += 8;
|
|
|
|
|
|
|
|
mach_write_to_4(start_ptr, space_id);
|
|
|
|
start_ptr += 4;
|
|
|
|
|
|
|
|
mach_write_to_4(start_ptr, format_flags);
|
|
|
|
start_ptr += 4;
|
|
|
|
|
|
|
|
mach_write_to_4(start_ptr, flags);
|
|
|
|
start_ptr += 4;
|
|
|
|
|
|
|
|
/* Name of the table. */
|
|
|
|
/* Include the NUL in the log record. */
|
|
|
|
ulint len = strlen(tablename) + 1;
|
|
|
|
if (end_ptr < (start_ptr + (len + 2))) {
|
|
|
|
return(DB_FAIL);
|
|
|
|
}
|
|
|
|
|
|
|
|
mach_write_to_2(start_ptr, len);
|
|
|
|
start_ptr += 2;
|
|
|
|
|
|
|
|
memcpy(start_ptr, tablename, len - 1);
|
|
|
|
start_ptr += len;
|
|
|
|
|
|
|
|
DBUG_EXECUTE_IF("ib_trunc_crash_while_writing_redo_log",
|
|
|
|
DBUG_SUICIDE(););
|
|
|
|
|
|
|
|
/* Old/New Table-ID, Number of Indexes and Tablespace dir-path-name. */
|
|
|
|
/* Write the remote directory of the table into mtr log */
|
|
|
|
len = m_dir_path != NULL ? strlen(m_dir_path) + 1 : 0;
|
|
|
|
if (end_ptr < (start_ptr + (len + 8 + 8 + 2 + 2))) {
|
|
|
|
return(DB_FAIL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Write out old-table-id. */
|
|
|
|
mach_write_to_8(start_ptr, m_old_table_id);
|
|
|
|
start_ptr += 8;
|
|
|
|
|
|
|
|
/* Write out new-table-id. */
|
|
|
|
mach_write_to_8(start_ptr, m_new_table_id);
|
|
|
|
start_ptr += 8;
|
|
|
|
|
|
|
|
/* Write out the number of indexes. */
|
|
|
|
mach_write_to_2(start_ptr, m_indexes.size());
|
|
|
|
start_ptr += 2;
|
|
|
|
|
|
|
|
/* Write the length (NUL included) of the .ibd path. */
|
|
|
|
mach_write_to_2(start_ptr, len);
|
|
|
|
start_ptr += 2;
|
|
|
|
|
|
|
|
if (m_dir_path != NULL) {
|
|
|
|
memcpy(start_ptr, m_dir_path, len - 1);
|
|
|
|
start_ptr += len;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Indexes information (id, type) */
|
|
|
|
/* Write index ids, type, root-page-no into mtr log */
|
|
|
|
for (ulint i = 0; i < m_indexes.size(); ++i) {
|
|
|
|
|
|
|
|
if (end_ptr < (start_ptr + (8 + 4 + 4 + 4))) {
|
|
|
|
return(DB_FAIL);
|
|
|
|
}
|
|
|
|
|
|
|
|
mach_write_to_8(start_ptr, m_indexes[i].m_id);
|
|
|
|
start_ptr += 8;
|
|
|
|
|
|
|
|
mach_write_to_4(start_ptr, m_indexes[i].m_type);
|
|
|
|
start_ptr += 4;
|
|
|
|
|
|
|
|
mach_write_to_4(start_ptr, m_indexes[i].m_root_page_no);
|
|
|
|
start_ptr += 4;
|
|
|
|
|
|
|
|
mach_write_to_4(start_ptr, m_indexes[i].m_trx_id_pos);
|
|
|
|
start_ptr += 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If tablespace compressed then field info of each index. */
|
2017-01-19 11:06:13 +01:00
|
|
|
if (FSP_FLAGS_GET_ZIP_SSIZE(flags)) {
|
2016-08-12 10:17:45 +02:00
|
|
|
|
|
|
|
for (ulint i = 0; i < m_indexes.size(); ++i) {
|
|
|
|
|
|
|
|
ulint len = m_indexes[i].m_fields.size();
|
|
|
|
if (end_ptr < (start_ptr + (len + 2 + 2))) {
|
|
|
|
return(DB_FAIL);
|
|
|
|
}
|
|
|
|
|
|
|
|
mach_write_to_2(
|
|
|
|
start_ptr, m_indexes[i].m_n_fields);
|
|
|
|
start_ptr += 2;
|
|
|
|
|
|
|
|
mach_write_to_2(start_ptr, len);
|
|
|
|
start_ptr += 2;
|
|
|
|
|
|
|
|
const byte* ptr = &m_indexes[i].m_fields[0];
|
|
|
|
memcpy(start_ptr, ptr, len - 1);
|
|
|
|
start_ptr += len;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return(DB_SUCCESS);
|
|
|
|
}
|