2013-03-26 00:03:13 +02:00
|
|
|
/*****************************************************************************
|
|
|
|
|
2018-04-21 17:37:24 +02:00
|
|
|
Copyright (c) 2011, 2018, Oracle and/or its affiliates. All Rights Reserved.
|
2022-09-22 11:18:00 +03:00
|
|
|
Copyright (c) 2017, 2022, MariaDB Corporation.
|
2013-03-26 00:03:13 +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.,
|
2019-05-11 19:25:02 +03:00
|
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
/**************************************************//**
|
|
|
|
@file row/row0log.cc
|
|
|
|
Modification log for online index creation and online table rebuild
|
|
|
|
|
|
|
|
Created 2011-05-26 Marko Makela
|
|
|
|
*******************************************************/
|
|
|
|
|
|
|
|
#include "row0log.h"
|
|
|
|
#include "row0row.h"
|
|
|
|
#include "row0ins.h"
|
|
|
|
#include "row0upd.h"
|
|
|
|
#include "row0merge.h"
|
|
|
|
#include "row0ext.h"
|
2017-09-16 22:31:53 +03:00
|
|
|
#include "log0crypt.h"
|
2013-03-26 00:03:13 +02:00
|
|
|
#include "data0data.h"
|
|
|
|
#include "que0que.h"
|
2016-08-12 11:17:45 +03:00
|
|
|
#include "srv0mon.h"
|
2013-03-26 00:03:13 +02:00
|
|
|
#include "handler0alter.h"
|
2016-08-12 11:17:45 +03:00
|
|
|
#include "ut0stage.h"
|
|
|
|
#include "trx0rec.h"
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2018-06-25 14:52:38 +05:30
|
|
|
#include <sql_class.h>
|
2016-08-12 11:17:45 +03:00
|
|
|
#include <algorithm>
|
|
|
|
#include <map>
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2018-12-27 00:34:18 +04:00
|
|
|
Atomic_counter<ulint> onlineddl_rowlog_rows;
|
2014-09-30 14:50:34 +03:00
|
|
|
ulint onlineddl_rowlog_pct_used;
|
|
|
|
ulint onlineddl_pct_progress;
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
/** Table row modification operations during online table rebuild.
|
|
|
|
Delete-marked records are not copied to the rebuilt table. */
|
|
|
|
enum row_tab_op {
|
|
|
|
/** Insert a record */
|
|
|
|
ROW_T_INSERT = 0x41,
|
|
|
|
/** Update a record in place */
|
|
|
|
ROW_T_UPDATE,
|
|
|
|
/** Delete (purge) a record */
|
|
|
|
ROW_T_DELETE
|
|
|
|
};
|
|
|
|
|
|
|
|
/** Index record modification operations during online index creation */
|
|
|
|
enum row_op {
|
|
|
|
/** Insert a record */
|
|
|
|
ROW_OP_INSERT = 0x61,
|
|
|
|
/** Delete a record */
|
|
|
|
ROW_OP_DELETE
|
|
|
|
};
|
|
|
|
|
|
|
|
/** Size of the modification log entry header, in bytes */
|
|
|
|
#define ROW_LOG_HEADER_SIZE 2/*op, extra_size*/
|
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
/** Log block for modifications during online ALTER TABLE */
|
2013-03-26 00:03:13 +02:00
|
|
|
struct row_log_buf_t {
|
|
|
|
byte* block; /*!< file block buffer */
|
2017-04-25 16:49:27 +10:00
|
|
|
size_t size; /*!< length of block in bytes */
|
2016-08-12 11:17:45 +03:00
|
|
|
ut_new_pfx_t block_pfx; /*!< opaque descriptor of "block". Set
|
|
|
|
by ut_allocator::allocate_large() and fed to
|
|
|
|
ut_allocator::deallocate_large(). */
|
2013-03-26 00:03:13 +02:00
|
|
|
mrec_buf_t buf; /*!< buffer for accessing a record
|
|
|
|
that spans two blocks */
|
|
|
|
ulint blocks; /*!< current position in blocks */
|
2014-05-05 18:20:28 +02:00
|
|
|
ulint bytes; /*!< current position within block */
|
2014-02-01 09:33:26 +01:00
|
|
|
ulonglong total; /*!< logical position, in bytes from
|
|
|
|
the start of the row_log_table log;
|
|
|
|
0 for row_log_online_op() and
|
|
|
|
row_log_apply(). */
|
2013-03-26 00:03:13 +02:00
|
|
|
};
|
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
/** Tracks BLOB allocation during online ALTER TABLE */
|
|
|
|
class row_log_table_blob_t {
|
|
|
|
public:
|
|
|
|
/** Constructor (declaring a BLOB freed)
|
2016-08-12 11:17:45 +03:00
|
|
|
@param offset_arg row_log_t::tail::total */
|
2014-02-01 09:33:26 +01:00
|
|
|
#ifdef UNIV_DEBUG
|
|
|
|
row_log_table_blob_t(ulonglong offset_arg) :
|
|
|
|
old_offset (0), free_offset (offset_arg),
|
|
|
|
offset (BLOB_FREED) {}
|
|
|
|
#else /* UNIV_DEBUG */
|
|
|
|
row_log_table_blob_t() :
|
|
|
|
offset (BLOB_FREED) {}
|
|
|
|
#endif /* UNIV_DEBUG */
|
|
|
|
|
|
|
|
/** Declare a BLOB freed again.
|
2016-08-12 11:17:45 +03:00
|
|
|
@param offset_arg row_log_t::tail::total */
|
2014-02-01 09:33:26 +01:00
|
|
|
#ifdef UNIV_DEBUG
|
|
|
|
void blob_free(ulonglong offset_arg)
|
|
|
|
#else /* UNIV_DEBUG */
|
|
|
|
void blob_free()
|
|
|
|
#endif /* UNIV_DEBUG */
|
|
|
|
{
|
|
|
|
ut_ad(offset < offset_arg);
|
|
|
|
ut_ad(offset != BLOB_FREED);
|
|
|
|
ut_d(old_offset = offset);
|
|
|
|
ut_d(free_offset = offset_arg);
|
|
|
|
offset = BLOB_FREED;
|
|
|
|
}
|
|
|
|
/** Declare a freed BLOB reused.
|
2016-08-12 11:17:45 +03:00
|
|
|
@param offset_arg row_log_t::tail::total */
|
2014-02-01 09:33:26 +01:00
|
|
|
void blob_alloc(ulonglong offset_arg) {
|
|
|
|
ut_ad(free_offset <= offset_arg);
|
|
|
|
ut_d(old_offset = offset);
|
|
|
|
offset = offset_arg;
|
|
|
|
}
|
|
|
|
/** Determine if a BLOB was freed at a given log position
|
2016-08-12 11:17:45 +03:00
|
|
|
@param offset_arg row_log_t::head::total after the log record
|
2014-02-01 09:33:26 +01:00
|
|
|
@return true if freed */
|
|
|
|
bool is_freed(ulonglong offset_arg) const {
|
|
|
|
/* This is supposed to be the offset at the end of the
|
|
|
|
current log record. */
|
|
|
|
ut_ad(offset_arg > 0);
|
|
|
|
/* We should never get anywhere close the magic value. */
|
|
|
|
ut_ad(offset_arg < BLOB_FREED);
|
|
|
|
return(offset_arg < offset);
|
|
|
|
}
|
|
|
|
private:
|
|
|
|
/** Magic value for a freed BLOB */
|
|
|
|
static const ulonglong BLOB_FREED = ~0ULL;
|
|
|
|
#ifdef UNIV_DEBUG
|
|
|
|
/** Old offset, in case a page was freed, reused, freed, ... */
|
|
|
|
ulonglong old_offset;
|
|
|
|
/** Offset of last blob_free() */
|
|
|
|
ulonglong free_offset;
|
|
|
|
#endif /* UNIV_DEBUG */
|
|
|
|
/** Byte offset to the log file */
|
|
|
|
ulonglong offset;
|
|
|
|
};
|
|
|
|
|
|
|
|
/** @brief Map of off-page column page numbers to 0 or log byte offsets.
|
|
|
|
|
|
|
|
If there is no mapping for a page number, it is safe to access.
|
|
|
|
If a page number maps to 0, it is an off-page column that has been freed.
|
|
|
|
If a page number maps to a nonzero number, the number is a byte offset
|
|
|
|
into the index->online_log, indicating that the page is safe to access
|
|
|
|
when applying log records starting from that offset. */
|
2016-08-12 11:17:45 +03:00
|
|
|
typedef std::map<
|
|
|
|
ulint,
|
|
|
|
row_log_table_blob_t,
|
|
|
|
std::less<ulint>,
|
|
|
|
ut_allocator<std::pair<const ulint, row_log_table_blob_t> > >
|
|
|
|
page_no_map;
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
/** @brief Buffer for logging modifications during online index creation
|
|
|
|
|
|
|
|
All modifications to an index that is being created will be logged by
|
|
|
|
row_log_online_op() to this buffer.
|
|
|
|
|
|
|
|
All modifications to a table that is being rebuilt will be logged by
|
|
|
|
row_log_table_delete(), row_log_table_update(), row_log_table_insert()
|
|
|
|
to this buffer.
|
|
|
|
|
|
|
|
When head.blocks == tail.blocks, the reader will access tail.block
|
|
|
|
directly. When also head.bytes == tail.bytes, both counts will be
|
|
|
|
reset to 0 and the file will be truncated. */
|
|
|
|
struct row_log_t {
|
2018-04-15 15:29:55 +01:00
|
|
|
pfs_os_file_t fd; /*!< file descriptor */
|
2014-02-01 09:33:26 +01:00
|
|
|
ib_mutex_t mutex; /*!< mutex protecting error,
|
2013-03-26 00:03:13 +02:00
|
|
|
max_trx and tail */
|
2014-02-01 09:33:26 +01:00
|
|
|
page_no_map* blobs; /*!< map of page numbers of off-page columns
|
|
|
|
that have been freed during table-rebuilding
|
|
|
|
ALTER TABLE (row_log_table_*); protected by
|
|
|
|
index->lock X-latch only */
|
2013-03-26 00:03:13 +02:00
|
|
|
dict_table_t* table; /*!< table that is being rebuilt,
|
|
|
|
or NULL when this is a secondary
|
|
|
|
index that is being created online */
|
|
|
|
bool same_pk;/*!< whether the definition of the PRIMARY KEY
|
|
|
|
has remained the same */
|
2018-04-24 13:15:35 +05:30
|
|
|
const dtuple_t* defaults;
|
|
|
|
/*!< default values of added, changed columns,
|
|
|
|
or NULL */
|
2013-03-26 00:03:13 +02:00
|
|
|
const ulint* col_map;/*!< mapping of old column numbers to
|
|
|
|
new ones, or NULL if !table */
|
|
|
|
dberr_t error; /*!< error that occurred during online
|
|
|
|
table rebuild */
|
MDEV-13654 Various crashes due to DB_TRX_ID mismatch in table-rebuilding ALTER TABLE…LOCK=NONE
After MDEV-12288 and MDEV-13536, the DB_TRX_ID of old clustered index
records for which no history is available should be reset to 0.
This caused crashes in online table-rebuilding ALTER, because the
row_log_table_apply() is built on the assumption that the PRIMARY KEY
together with DB_TRX_ID,DB_ROLL_PTR identifies the record.
Both when copying the old table and when writing log about changes to
the old table, we must map "old" DB_TRX_ID to 0. "old" here is simply
"older than the trx_id of the ALTER TABLE transaction", because
the MDL_EXCLUSIVE (and exclusive InnoDB table lock) in
ha_innobase::prepare_inplace_alter_table() forces any transactions
accessing the table to commit or rollback. So, we know that we can
safely reset any DB_TRX_ID in the table that is older than the
transaction ID of the ALTER TABLE, because the undo log history would be
lost in a table-rebuilding ALTER.
Note: After a table-rebuilding online ALTER TABLE, the rebuilt table
may end up containing some nonzero DB_TRX_ID columns. The apply logic
identifies the rows by the combination of PRIMARY KEY and DB_TRX_ID.
These nonzero DB_TRX_ID would necessarily refer to concurrent DML
operations that were started during ha_innobase::inplace_alter_table().
row_log_allocate(): Add a parameter for the ALTER TABLE transaction.
row_log_t::min_trx: The ALTER TABLE transaction ID.
trx_id_check(): A debug function to check that DB_TRX_ID makes sense
(is either 0 or bigger than the ALTER TABLE transaction ID).
reset_trx_id[]: The reset DB_TRX_ID,DB_ROLL_PTR columns.
row_log_table_delete(), row_log_table_get_pk(): Reset the
DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE transaction.
row_log_table_apply_delete(), row_log_table_apply_update():
Assert trx_id_check().
row_merge_insert_index_tuples(): Remove the unused parameter trx_id.
row_merge_read_clustered_index(): In a table-rebuilding ALTER,
reset the DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE
transaction. Assert trx_id_check() on clustered index records that
are being buffered.
2017-08-31 18:03:50 +03:00
|
|
|
/** The transaction ID of the ALTER TABLE transaction. Any
|
|
|
|
concurrent DML would necessarily be logged with a larger
|
|
|
|
transaction ID, because ha_innobase::prepare_inplace_alter_table()
|
|
|
|
acts as a barrier that ensures that any concurrent transaction
|
|
|
|
that operates on the table would have been started after
|
|
|
|
ha_innobase::prepare_inplace_alter_table() returns and before
|
|
|
|
ha_innobase::commit_inplace_alter_table(commit=true) is invoked.
|
|
|
|
|
|
|
|
Due to the nondeterministic nature of purge and due to the
|
|
|
|
possibility of upgrading from an earlier version of MariaDB
|
|
|
|
or MySQL, it is possible that row_log_table_low() would be
|
|
|
|
fed DB_TRX_ID that precedes than min_trx. We must normalize
|
|
|
|
such references to reset_trx_id[]. */
|
|
|
|
trx_id_t min_trx;
|
2013-03-26 00:03:13 +02:00
|
|
|
trx_id_t max_trx;/*!< biggest observed trx_id in
|
|
|
|
row_log_online_op();
|
|
|
|
protected by mutex and index->lock S-latch,
|
|
|
|
or by index->lock X-latch only */
|
|
|
|
row_log_buf_t tail; /*!< writer context;
|
|
|
|
protected by mutex and index->lock S-latch,
|
|
|
|
or by index->lock X-latch only */
|
2017-09-14 09:23:20 +03:00
|
|
|
byte* crypt_tail; /*!< writer context;
|
|
|
|
temporary buffer used in encryption,
|
|
|
|
decryption or NULL*/
|
2013-03-26 00:03:13 +02:00
|
|
|
row_log_buf_t head; /*!< reader context; protected by MDL only;
|
|
|
|
modifiable by row_log_apply_ops() */
|
2017-09-14 09:23:20 +03:00
|
|
|
byte* crypt_head; /*!< reader context;
|
|
|
|
temporary buffer used in encryption,
|
|
|
|
decryption or NULL */
|
2016-09-06 09:43:16 +03:00
|
|
|
const char* path; /*!< where to create temporary file during
|
|
|
|
log operation */
|
2018-05-03 15:17:16 +03:00
|
|
|
/** the number of core fields in the clustered index of the
|
|
|
|
source table; before row_log_table_apply() completes, the
|
|
|
|
table could be emptied, so that table->is_instant() no longer holds,
|
|
|
|
but all log records must be in the "instant" format. */
|
|
|
|
unsigned n_core_fields;
|
2018-07-26 22:52:53 +03:00
|
|
|
/** the default values of non-core fields when the operation started */
|
|
|
|
dict_col_t::def_t* non_core_fields;
|
2018-06-25 14:52:38 +05:30
|
|
|
bool allow_not_null; /*!< Whether the alter ignore is being
|
|
|
|
used or if the sql mode is non-strict mode;
|
2018-04-24 13:15:35 +05:30
|
|
|
if not, NULL values will not be converted to
|
|
|
|
defaults */
|
2018-06-25 14:52:38 +05:30
|
|
|
const TABLE* old_table; /*< Use old table in case of error. */
|
2018-05-03 15:17:16 +03:00
|
|
|
|
2018-06-25 18:16:29 +05:30
|
|
|
uint64_t n_rows; /*< Number of rows read from the table */
|
2018-05-03 15:17:16 +03:00
|
|
|
/** Determine whether the log should be in the 'instant ADD' format
|
|
|
|
@param[in] index the clustered index of the source table
|
|
|
|
@return whether to use the 'instant ADD COLUMN' format */
|
|
|
|
bool is_instant(const dict_index_t* index) const
|
|
|
|
{
|
|
|
|
ut_ad(table);
|
|
|
|
ut_ad(n_core_fields <= index->n_fields);
|
|
|
|
return n_core_fields != index->n_fields;
|
|
|
|
}
|
2018-07-26 22:52:53 +03:00
|
|
|
|
|
|
|
const byte* instant_field_value(ulint n, ulint* len) const
|
|
|
|
{
|
|
|
|
ut_ad(n >= n_core_fields);
|
|
|
|
const dict_col_t::def_t& d= non_core_fields[n - n_core_fields];
|
|
|
|
*len = d.len;
|
|
|
|
return static_cast<const byte*>(d.data);
|
|
|
|
}
|
2013-03-26 00:03:13 +02:00
|
|
|
};
|
|
|
|
|
2016-02-16 12:07:18 +01:00
|
|
|
/** Create the file or online log if it does not exist.
|
2016-08-12 11:17:45 +03:00
|
|
|
@param[in,out] log online rebuild log
|
|
|
|
@return true if success, false if not */
|
2016-06-21 14:21:03 +02:00
|
|
|
static MY_ATTRIBUTE((warn_unused_result))
|
2018-04-15 15:29:55 +01:00
|
|
|
pfs_os_file_t
|
2016-02-16 12:07:18 +01:00
|
|
|
row_log_tmpfile(
|
|
|
|
row_log_t* log)
|
|
|
|
{
|
|
|
|
DBUG_ENTER("row_log_tmpfile");
|
2018-04-15 15:29:55 +01:00
|
|
|
if (log->fd == OS_FILE_CLOSED) {
|
2016-09-06 09:43:16 +03:00
|
|
|
log->fd = row_merge_file_create_low(log->path);
|
2016-08-12 11:17:45 +03:00
|
|
|
DBUG_EXECUTE_IF("row_log_tmpfile_fail",
|
2018-04-15 15:29:55 +01:00
|
|
|
if (log->fd != OS_FILE_CLOSED)
|
2016-08-12 11:17:45 +03:00
|
|
|
row_merge_file_destroy_low(log->fd);
|
2018-04-15 15:29:55 +01:00
|
|
|
log->fd = OS_FILE_CLOSED;);
|
|
|
|
if (log->fd != OS_FILE_CLOSED) {
|
2016-08-12 11:17:45 +03:00
|
|
|
MONITOR_ATOMIC_INC(MONITOR_ALTER_TABLE_LOG_FILES);
|
|
|
|
}
|
2016-02-16 12:07:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
DBUG_RETURN(log->fd);
|
|
|
|
}
|
2014-05-05 18:20:28 +02:00
|
|
|
|
|
|
|
/** Allocate the memory for the log buffer.
|
|
|
|
@param[in,out] log_buf Buffer used for log operation
|
|
|
|
@return TRUE if success, false if not */
|
2016-06-21 14:21:03 +02:00
|
|
|
static MY_ATTRIBUTE((warn_unused_result))
|
2014-05-05 18:20:28 +02:00
|
|
|
bool
|
|
|
|
row_log_block_allocate(
|
|
|
|
row_log_buf_t& log_buf)
|
|
|
|
{
|
|
|
|
DBUG_ENTER("row_log_block_allocate");
|
|
|
|
if (log_buf.block == NULL) {
|
2016-08-12 11:17:45 +03:00
|
|
|
DBUG_EXECUTE_IF(
|
|
|
|
"simulate_row_log_allocation_failure",
|
|
|
|
DBUG_RETURN(false);
|
|
|
|
);
|
|
|
|
|
|
|
|
log_buf.block = ut_allocator<byte>(mem_key_row_log_buf)
|
2019-07-26 17:01:36 +02:00
|
|
|
.allocate_large(srv_sort_buf_size,
|
2019-05-27 10:16:23 +03:00
|
|
|
&log_buf.block_pfx);
|
2016-08-12 11:17:45 +03:00
|
|
|
|
|
|
|
if (log_buf.block == NULL) {
|
2014-05-05 18:20:28 +02:00
|
|
|
DBUG_RETURN(false);
|
|
|
|
}
|
2017-04-25 16:49:27 +10:00
|
|
|
log_buf.size = srv_sort_buf_size;
|
2014-05-05 18:20:28 +02:00
|
|
|
}
|
|
|
|
DBUG_RETURN(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Free the log buffer.
|
|
|
|
@param[in,out] log_buf Buffer used for log operation */
|
|
|
|
static
|
|
|
|
void
|
|
|
|
row_log_block_free(
|
|
|
|
row_log_buf_t& log_buf)
|
|
|
|
{
|
|
|
|
DBUG_ENTER("row_log_block_free");
|
|
|
|
if (log_buf.block != NULL) {
|
2016-08-12 11:17:45 +03:00
|
|
|
ut_allocator<byte>(mem_key_row_log_buf).deallocate_large(
|
2019-05-27 10:16:23 +03:00
|
|
|
log_buf.block, &log_buf.block_pfx,
|
2019-07-26 17:01:36 +02:00
|
|
|
log_buf.size);
|
2014-05-05 18:20:28 +02:00
|
|
|
log_buf.block = NULL;
|
|
|
|
}
|
|
|
|
DBUG_VOID_RETURN;
|
|
|
|
}
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
/******************************************************//**
|
|
|
|
Logs an operation to a secondary index that is (or was) being created. */
|
|
|
|
void
|
|
|
|
row_log_online_op(
|
|
|
|
/*==============*/
|
|
|
|
dict_index_t* index, /*!< in/out: index, S or X latched */
|
|
|
|
const dtuple_t* tuple, /*!< in: index tuple */
|
|
|
|
trx_id_t trx_id) /*!< in: transaction ID for insert,
|
|
|
|
or 0 for delete */
|
|
|
|
{
|
|
|
|
byte* b;
|
|
|
|
ulint extra_size;
|
|
|
|
ulint size;
|
|
|
|
ulint mrec_size;
|
|
|
|
ulint avail_size;
|
|
|
|
row_log_t* log;
|
|
|
|
|
|
|
|
ut_ad(dtuple_validate(tuple));
|
|
|
|
ut_ad(dtuple_get_n_fields(tuple) == dict_index_get_n_fields(index));
|
2018-07-23 13:31:10 +03:00
|
|
|
ut_ad(rw_lock_own_flagged(&index->lock,
|
|
|
|
RW_LOCK_FLAG_X | RW_LOCK_FLAG_S));
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2018-05-07 15:30:57 +03:00
|
|
|
if (index->is_corrupted()) {
|
2013-03-26 00:03:13 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ut_ad(dict_index_is_online_ddl(index));
|
|
|
|
|
|
|
|
/* Compute the size of the record. This differs from
|
|
|
|
row_merge_buf_encode(), because here we do not encode
|
|
|
|
extra_size+1 (and reserve 0 as the end-of-chunk marker). */
|
|
|
|
|
2021-03-06 05:55:14 +05:30
|
|
|
size = rec_get_converted_size_temp<false>(
|
Clean up the logging of virtual column values in table-rebuilding online ALTER
In online table-rebuilding ALTER TABLE (LOCK=NONE), virtual column values
are being written to the online_log. WL#8149 in MySQL 5.7 changed some
low-level functions that are also being used outside row0log.cc, causing
performance penalty to other code.
We revert those changes, and introduce separate functions for writing
the virtual column values.
The only functional change should be the one that is mentioned in
MDEV-13795: row_log_table_low_redundant() will no longer write
virtual column values along with old_pk, just like row_log_table_low().
As noted in MDEV-13795, some forms of table-rebuilding ALTER with
virtual columns is broken. At least DROP PRIMARY KEY, ADD PRIMARY KEY
is broken.
rec_get_converted_size_temp(), rec_convert_dtuple_to_temp():
Remove the parameter for passing virtual column values.
rec_get_converted_size_temp_v(), rec_convert_dtuple_to_temp_v():
New functions for appending virtual column values to the online_log.
rec_get_converted_size_comp_prefix_low(),
rec_convert_dtuple_to_rec_comp(): Remove the v_entry parameter,
and do not allow n_fields=0.
2017-09-13 15:41:04 +03:00
|
|
|
index, tuple->fields, tuple->n_fields, &extra_size);
|
2013-03-26 00:03:13 +02:00
|
|
|
ut_ad(size >= extra_size);
|
|
|
|
ut_ad(size <= sizeof log->tail.buf);
|
|
|
|
|
|
|
|
mrec_size = ROW_LOG_HEADER_SIZE
|
|
|
|
+ (extra_size >= 0x80) + size
|
|
|
|
+ (trx_id ? DATA_TRX_ID_LEN : 0);
|
|
|
|
|
|
|
|
log = index->online_log;
|
|
|
|
mutex_enter(&log->mutex);
|
|
|
|
|
|
|
|
if (trx_id > log->max_trx) {
|
|
|
|
log->max_trx = trx_id;
|
|
|
|
}
|
|
|
|
|
2014-05-05 18:20:28 +02:00
|
|
|
if (!row_log_block_allocate(log->tail)) {
|
|
|
|
log->error = DB_OUT_OF_MEMORY;
|
|
|
|
goto err_exit;
|
|
|
|
}
|
|
|
|
|
MDEV-20377: Make WITH_MSAN more usable
MemorySanitizer (clang -fsanitize=memory) requires that all code
be compiled with instrumentation enabled. The only exception is the
C runtime library. Failure to use instrumented libraries will cause
bogus messages about memory being uninitialized.
In WITH_MSAN builds, we must avoid calling getservbyname(),
because even though it is a standard library function, it is
not instrumented, not even in clang 10.
Note: Before MariaDB Server 10.5, ./mtr will typically fail
due to the old PCRE library, which was updated in MDEV-14024.
The following cmake options were tested on 10.5
in commit 94d0bb4dbeb28a94d1f87fdd55f4297ff3df0157:
cmake \
-DCMAKE_C_FLAGS='-march=native -O2' \
-DCMAKE_CXX_FLAGS='-stdlib=libc++ -march=native -O2' \
-DWITH_EMBEDDED_SERVER=OFF -DWITH_UNIT_TESTS=OFF -DCMAKE_BUILD_TYPE=Debug \
-DWITH_INNODB_{BZIP2,LZ4,LZMA,LZO,SNAPPY}=OFF \
-DPLUGIN_{ARCHIVE,TOKUDB,MROONGA,OQGRAPH,ROCKSDB,CONNECT,SPIDER}=NO \
-DWITH_SAFEMALLOC=OFF \
-DWITH_{ZLIB,SSL,PCRE}=bundled \
-DHAVE_LIBAIO_H=0 \
-DWITH_MSAN=ON
MEM_MAKE_DEFINED(): An alias for VALGRIND_MAKE_MEM_DEFINED()
and __msan_unpoison().
MEM_GET_VBITS(), MEM_SET_VBITS(): Aliases for
VALGRIND_GET_VBITS(), VALGRIND_SET_VBITS(), __msan_copy_shadow().
InnoDB: Replace the UNIV_MEM_ macros with corresponding MEM_ macros.
ut_crc32_8_hw(), ut_crc32_64_low_hw(): Use the compiler built-in
functions instead of inline assembler when building WITH_MSAN.
This will require at least -msse4.2 when building for IA-32 or AMD64.
The inline assembler would not be instrumented, and would thus cause
bogus failures.
2020-07-01 17:23:00 +03:00
|
|
|
MEM_UNDEFINED(log->tail.buf, sizeof log->tail.buf);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
ut_ad(log->tail.bytes < srv_sort_buf_size);
|
|
|
|
avail_size = srv_sort_buf_size - log->tail.bytes;
|
|
|
|
|
|
|
|
if (mrec_size > avail_size) {
|
|
|
|
b = log->tail.buf;
|
|
|
|
} else {
|
|
|
|
b = log->tail.block + log->tail.bytes;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (trx_id != 0) {
|
|
|
|
*b++ = ROW_OP_INSERT;
|
|
|
|
trx_write_trx_id(b, trx_id);
|
|
|
|
b += DATA_TRX_ID_LEN;
|
|
|
|
} else {
|
|
|
|
*b++ = ROW_OP_DELETE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (extra_size < 0x80) {
|
|
|
|
*b++ = (byte) extra_size;
|
|
|
|
} else {
|
|
|
|
ut_ad(extra_size < 0x8000);
|
|
|
|
*b++ = (byte) (0x80 | (extra_size >> 8));
|
|
|
|
*b++ = (byte) extra_size;
|
|
|
|
}
|
|
|
|
|
2021-03-06 05:55:14 +05:30
|
|
|
rec_convert_dtuple_to_temp<false>(
|
Clean up the logging of virtual column values in table-rebuilding online ALTER
In online table-rebuilding ALTER TABLE (LOCK=NONE), virtual column values
are being written to the online_log. WL#8149 in MySQL 5.7 changed some
low-level functions that are also being used outside row0log.cc, causing
performance penalty to other code.
We revert those changes, and introduce separate functions for writing
the virtual column values.
The only functional change should be the one that is mentioned in
MDEV-13795: row_log_table_low_redundant() will no longer write
virtual column values along with old_pk, just like row_log_table_low().
As noted in MDEV-13795, some forms of table-rebuilding ALTER with
virtual columns is broken. At least DROP PRIMARY KEY, ADD PRIMARY KEY
is broken.
rec_get_converted_size_temp(), rec_convert_dtuple_to_temp():
Remove the parameter for passing virtual column values.
rec_get_converted_size_temp_v(), rec_convert_dtuple_to_temp_v():
New functions for appending virtual column values to the online_log.
rec_get_converted_size_comp_prefix_low(),
rec_convert_dtuple_to_rec_comp(): Remove the v_entry parameter,
and do not allow n_fields=0.
2017-09-13 15:41:04 +03:00
|
|
|
b + extra_size, index, tuple->fields, tuple->n_fields);
|
2013-03-26 00:03:13 +02:00
|
|
|
b += size;
|
|
|
|
|
|
|
|
if (mrec_size >= avail_size) {
|
|
|
|
const os_offset_t byte_offset
|
|
|
|
= (os_offset_t) log->tail.blocks
|
|
|
|
* srv_sort_buf_size;
|
2017-05-23 11:09:47 +03:00
|
|
|
IORequest request(IORequest::WRITE);
|
2017-09-16 22:31:53 +03:00
|
|
|
byte* buf = log->tail.block;
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
if (byte_offset + srv_sort_buf_size >= srv_online_max_size) {
|
|
|
|
goto write_failed;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mrec_size == avail_size) {
|
2017-09-16 22:31:53 +03:00
|
|
|
ut_ad(b == &buf[srv_sort_buf_size]);
|
2013-03-26 00:03:13 +02:00
|
|
|
} else {
|
|
|
|
ut_ad(b == log->tail.buf + mrec_size);
|
2017-09-16 22:31:53 +03:00
|
|
|
memcpy(buf + log->tail.bytes,
|
2013-03-26 00:03:13 +02:00
|
|
|
log->tail.buf, avail_size);
|
|
|
|
}
|
2016-08-12 11:17:45 +03:00
|
|
|
|
MDEV-20377: Make WITH_MSAN more usable
MemorySanitizer (clang -fsanitize=memory) requires that all code
be compiled with instrumentation enabled. The only exception is the
C runtime library. Failure to use instrumented libraries will cause
bogus messages about memory being uninitialized.
In WITH_MSAN builds, we must avoid calling getservbyname(),
because even though it is a standard library function, it is
not instrumented, not even in clang 10.
Note: Before MariaDB Server 10.5, ./mtr will typically fail
due to the old PCRE library, which was updated in MDEV-14024.
The following cmake options were tested on 10.5
in commit 94d0bb4dbeb28a94d1f87fdd55f4297ff3df0157:
cmake \
-DCMAKE_C_FLAGS='-march=native -O2' \
-DCMAKE_CXX_FLAGS='-stdlib=libc++ -march=native -O2' \
-DWITH_EMBEDDED_SERVER=OFF -DWITH_UNIT_TESTS=OFF -DCMAKE_BUILD_TYPE=Debug \
-DWITH_INNODB_{BZIP2,LZ4,LZMA,LZO,SNAPPY}=OFF \
-DPLUGIN_{ARCHIVE,TOKUDB,MROONGA,OQGRAPH,ROCKSDB,CONNECT,SPIDER}=NO \
-DWITH_SAFEMALLOC=OFF \
-DWITH_{ZLIB,SSL,PCRE}=bundled \
-DHAVE_LIBAIO_H=0 \
-DWITH_MSAN=ON
MEM_MAKE_DEFINED(): An alias for VALGRIND_MAKE_MEM_DEFINED()
and __msan_unpoison().
MEM_GET_VBITS(), MEM_SET_VBITS(): Aliases for
VALGRIND_GET_VBITS(), VALGRIND_SET_VBITS(), __msan_copy_shadow().
InnoDB: Replace the UNIV_MEM_ macros with corresponding MEM_ macros.
ut_crc32_8_hw(), ut_crc32_64_low_hw(): Use the compiler built-in
functions instead of inline assembler when building WITH_MSAN.
This will require at least -msse4.2 when building for IA-32 or AMD64.
The inline assembler would not be instrumented, and would thus cause
bogus failures.
2020-07-01 17:23:00 +03:00
|
|
|
MEM_CHECK_DEFINED(buf, srv_sort_buf_size);
|
2016-02-16 12:07:18 +01:00
|
|
|
|
2018-04-15 15:29:55 +01:00
|
|
|
if (row_log_tmpfile(log) == OS_FILE_CLOSED) {
|
2016-02-16 12:07:18 +01:00
|
|
|
log->error = DB_OUT_OF_MEMORY;
|
|
|
|
goto err_exit;
|
|
|
|
}
|
|
|
|
|
2017-09-14 09:23:20 +03:00
|
|
|
/* If encryption is enabled encrypt buffer before writing it
|
|
|
|
to file system. */
|
|
|
|
if (log_tmp_is_encrypted()) {
|
2017-09-16 22:31:53 +03:00
|
|
|
if (!log_tmp_block_encrypt(
|
|
|
|
buf, srv_sort_buf_size,
|
2019-06-27 16:23:03 +05:30
|
|
|
log->crypt_tail, byte_offset)) {
|
2017-09-14 09:23:20 +03:00
|
|
|
log->error = DB_DECRYPTION_FAILED;
|
2017-09-16 22:31:53 +03:00
|
|
|
goto write_failed;
|
2017-09-14 09:23:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
srv_stats.n_rowlog_blocks_encrypted.inc();
|
|
|
|
buf = log->crypt_tail;
|
|
|
|
}
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
log->tail.blocks++;
|
2019-04-27 20:41:31 +03:00
|
|
|
if (os_file_write(
|
2017-05-23 11:09:47 +03:00
|
|
|
request,
|
|
|
|
"(modification log)",
|
|
|
|
log->fd,
|
2019-04-27 20:41:31 +03:00
|
|
|
buf, byte_offset, srv_sort_buf_size)
|
|
|
|
!= DB_SUCCESS) {
|
2013-03-26 00:03:13 +02:00
|
|
|
write_failed:
|
|
|
|
/* We set the flag directly instead of invoking
|
|
|
|
dict_set_corrupted_index_cache_only(index) here,
|
|
|
|
because the index is not "public" yet. */
|
|
|
|
index->type |= DICT_CORRUPT;
|
|
|
|
}
|
2017-09-14 09:23:20 +03:00
|
|
|
|
MDEV-20377: Make WITH_MSAN more usable
MemorySanitizer (clang -fsanitize=memory) requires that all code
be compiled with instrumentation enabled. The only exception is the
C runtime library. Failure to use instrumented libraries will cause
bogus messages about memory being uninitialized.
In WITH_MSAN builds, we must avoid calling getservbyname(),
because even though it is a standard library function, it is
not instrumented, not even in clang 10.
Note: Before MariaDB Server 10.5, ./mtr will typically fail
due to the old PCRE library, which was updated in MDEV-14024.
The following cmake options were tested on 10.5
in commit 94d0bb4dbeb28a94d1f87fdd55f4297ff3df0157:
cmake \
-DCMAKE_C_FLAGS='-march=native -O2' \
-DCMAKE_CXX_FLAGS='-stdlib=libc++ -march=native -O2' \
-DWITH_EMBEDDED_SERVER=OFF -DWITH_UNIT_TESTS=OFF -DCMAKE_BUILD_TYPE=Debug \
-DWITH_INNODB_{BZIP2,LZ4,LZMA,LZO,SNAPPY}=OFF \
-DPLUGIN_{ARCHIVE,TOKUDB,MROONGA,OQGRAPH,ROCKSDB,CONNECT,SPIDER}=NO \
-DWITH_SAFEMALLOC=OFF \
-DWITH_{ZLIB,SSL,PCRE}=bundled \
-DHAVE_LIBAIO_H=0 \
-DWITH_MSAN=ON
MEM_MAKE_DEFINED(): An alias for VALGRIND_MAKE_MEM_DEFINED()
and __msan_unpoison().
MEM_GET_VBITS(), MEM_SET_VBITS(): Aliases for
VALGRIND_GET_VBITS(), VALGRIND_SET_VBITS(), __msan_copy_shadow().
InnoDB: Replace the UNIV_MEM_ macros with corresponding MEM_ macros.
ut_crc32_8_hw(), ut_crc32_64_low_hw(): Use the compiler built-in
functions instead of inline assembler when building WITH_MSAN.
This will require at least -msse4.2 when building for IA-32 or AMD64.
The inline assembler would not be instrumented, and would thus cause
bogus failures.
2020-07-01 17:23:00 +03:00
|
|
|
MEM_UNDEFINED(log->tail.block, srv_sort_buf_size);
|
|
|
|
MEM_UNDEFINED(buf, srv_sort_buf_size);
|
2017-09-14 09:23:20 +03:00
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
memcpy(log->tail.block, log->tail.buf + avail_size,
|
|
|
|
mrec_size - avail_size);
|
|
|
|
log->tail.bytes = mrec_size - avail_size;
|
|
|
|
} else {
|
|
|
|
log->tail.bytes += mrec_size;
|
|
|
|
ut_ad(b == log->tail.block + log->tail.bytes);
|
|
|
|
}
|
|
|
|
|
MDEV-20377: Make WITH_MSAN more usable
MemorySanitizer (clang -fsanitize=memory) requires that all code
be compiled with instrumentation enabled. The only exception is the
C runtime library. Failure to use instrumented libraries will cause
bogus messages about memory being uninitialized.
In WITH_MSAN builds, we must avoid calling getservbyname(),
because even though it is a standard library function, it is
not instrumented, not even in clang 10.
Note: Before MariaDB Server 10.5, ./mtr will typically fail
due to the old PCRE library, which was updated in MDEV-14024.
The following cmake options were tested on 10.5
in commit 94d0bb4dbeb28a94d1f87fdd55f4297ff3df0157:
cmake \
-DCMAKE_C_FLAGS='-march=native -O2' \
-DCMAKE_CXX_FLAGS='-stdlib=libc++ -march=native -O2' \
-DWITH_EMBEDDED_SERVER=OFF -DWITH_UNIT_TESTS=OFF -DCMAKE_BUILD_TYPE=Debug \
-DWITH_INNODB_{BZIP2,LZ4,LZMA,LZO,SNAPPY}=OFF \
-DPLUGIN_{ARCHIVE,TOKUDB,MROONGA,OQGRAPH,ROCKSDB,CONNECT,SPIDER}=NO \
-DWITH_SAFEMALLOC=OFF \
-DWITH_{ZLIB,SSL,PCRE}=bundled \
-DHAVE_LIBAIO_H=0 \
-DWITH_MSAN=ON
MEM_MAKE_DEFINED(): An alias for VALGRIND_MAKE_MEM_DEFINED()
and __msan_unpoison().
MEM_GET_VBITS(), MEM_SET_VBITS(): Aliases for
VALGRIND_GET_VBITS(), VALGRIND_SET_VBITS(), __msan_copy_shadow().
InnoDB: Replace the UNIV_MEM_ macros with corresponding MEM_ macros.
ut_crc32_8_hw(), ut_crc32_64_low_hw(): Use the compiler built-in
functions instead of inline assembler when building WITH_MSAN.
This will require at least -msse4.2 when building for IA-32 or AMD64.
The inline assembler would not be instrumented, and would thus cause
bogus failures.
2020-07-01 17:23:00 +03:00
|
|
|
MEM_UNDEFINED(log->tail.buf, sizeof log->tail.buf);
|
2014-05-05 18:20:28 +02:00
|
|
|
err_exit:
|
2013-03-26 00:03:13 +02:00
|
|
|
mutex_exit(&log->mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************//**
|
|
|
|
Gets the error status of the online index rebuild log.
|
|
|
|
@return DB_SUCCESS or error code */
|
|
|
|
dberr_t
|
|
|
|
row_log_table_get_error(
|
|
|
|
/*====================*/
|
|
|
|
const dict_index_t* index) /*!< in: clustered index of a table
|
|
|
|
that is being rebuilt online */
|
|
|
|
{
|
|
|
|
ut_ad(dict_index_is_clust(index));
|
|
|
|
ut_ad(dict_index_is_online_ddl(index));
|
|
|
|
return(index->online_log->error);
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************//**
|
|
|
|
Starts logging an operation to a table that is being rebuilt.
|
|
|
|
@return pointer to log, or NULL if no logging is necessary */
|
2016-06-21 14:21:03 +02:00
|
|
|
static MY_ATTRIBUTE((nonnull, warn_unused_result))
|
2013-03-26 00:03:13 +02:00
|
|
|
byte*
|
|
|
|
row_log_table_open(
|
|
|
|
/*===============*/
|
|
|
|
row_log_t* log, /*!< in/out: online rebuild log */
|
|
|
|
ulint size, /*!< in: size of log record */
|
|
|
|
ulint* avail) /*!< out: available size for log record */
|
|
|
|
{
|
|
|
|
mutex_enter(&log->mutex);
|
|
|
|
|
MDEV-20377: Make WITH_MSAN more usable
MemorySanitizer (clang -fsanitize=memory) requires that all code
be compiled with instrumentation enabled. The only exception is the
C runtime library. Failure to use instrumented libraries will cause
bogus messages about memory being uninitialized.
In WITH_MSAN builds, we must avoid calling getservbyname(),
because even though it is a standard library function, it is
not instrumented, not even in clang 10.
Note: Before MariaDB Server 10.5, ./mtr will typically fail
due to the old PCRE library, which was updated in MDEV-14024.
The following cmake options were tested on 10.5
in commit 94d0bb4dbeb28a94d1f87fdd55f4297ff3df0157:
cmake \
-DCMAKE_C_FLAGS='-march=native -O2' \
-DCMAKE_CXX_FLAGS='-stdlib=libc++ -march=native -O2' \
-DWITH_EMBEDDED_SERVER=OFF -DWITH_UNIT_TESTS=OFF -DCMAKE_BUILD_TYPE=Debug \
-DWITH_INNODB_{BZIP2,LZ4,LZMA,LZO,SNAPPY}=OFF \
-DPLUGIN_{ARCHIVE,TOKUDB,MROONGA,OQGRAPH,ROCKSDB,CONNECT,SPIDER}=NO \
-DWITH_SAFEMALLOC=OFF \
-DWITH_{ZLIB,SSL,PCRE}=bundled \
-DHAVE_LIBAIO_H=0 \
-DWITH_MSAN=ON
MEM_MAKE_DEFINED(): An alias for VALGRIND_MAKE_MEM_DEFINED()
and __msan_unpoison().
MEM_GET_VBITS(), MEM_SET_VBITS(): Aliases for
VALGRIND_GET_VBITS(), VALGRIND_SET_VBITS(), __msan_copy_shadow().
InnoDB: Replace the UNIV_MEM_ macros with corresponding MEM_ macros.
ut_crc32_8_hw(), ut_crc32_64_low_hw(): Use the compiler built-in
functions instead of inline assembler when building WITH_MSAN.
This will require at least -msse4.2 when building for IA-32 or AMD64.
The inline assembler would not be instrumented, and would thus cause
bogus failures.
2020-07-01 17:23:00 +03:00
|
|
|
MEM_UNDEFINED(log->tail.buf, sizeof log->tail.buf);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
if (log->error != DB_SUCCESS) {
|
2014-05-05 18:20:28 +02:00
|
|
|
err_exit:
|
2013-03-26 00:03:13 +02:00
|
|
|
mutex_exit(&log->mutex);
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
2014-05-05 18:20:28 +02:00
|
|
|
if (!row_log_block_allocate(log->tail)) {
|
|
|
|
log->error = DB_OUT_OF_MEMORY;
|
|
|
|
goto err_exit;
|
|
|
|
}
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
ut_ad(log->tail.bytes < srv_sort_buf_size);
|
|
|
|
*avail = srv_sort_buf_size - log->tail.bytes;
|
|
|
|
|
|
|
|
if (size > *avail) {
|
2018-01-10 20:54:09 +05:30
|
|
|
/* Make sure log->tail.buf is large enough */
|
|
|
|
ut_ad(size <= sizeof log->tail.buf);
|
2013-03-26 00:03:13 +02:00
|
|
|
return(log->tail.buf);
|
|
|
|
} else {
|
|
|
|
return(log->tail.block + log->tail.bytes);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************//**
|
|
|
|
Stops logging an operation to a table that is being rebuilt. */
|
2016-06-21 14:21:03 +02:00
|
|
|
static MY_ATTRIBUTE((nonnull))
|
2013-03-26 00:03:13 +02:00
|
|
|
void
|
|
|
|
row_log_table_close_func(
|
|
|
|
/*=====================*/
|
2017-09-16 22:31:53 +03:00
|
|
|
dict_index_t* index, /*!< in/out: online rebuilt index */
|
2013-03-26 00:03:13 +02:00
|
|
|
#ifdef UNIV_DEBUG
|
|
|
|
const byte* b, /*!< in: end of log record */
|
|
|
|
#endif /* UNIV_DEBUG */
|
|
|
|
ulint size, /*!< in: size of log record */
|
|
|
|
ulint avail) /*!< in: available size for log record */
|
|
|
|
{
|
2017-09-16 22:31:53 +03:00
|
|
|
row_log_t* log = index->online_log;
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
ut_ad(mutex_own(&log->mutex));
|
|
|
|
|
|
|
|
if (size >= avail) {
|
|
|
|
const os_offset_t byte_offset
|
|
|
|
= (os_offset_t) log->tail.blocks
|
|
|
|
* srv_sort_buf_size;
|
2017-05-23 11:09:47 +03:00
|
|
|
IORequest request(IORequest::WRITE);
|
2017-09-16 22:31:53 +03:00
|
|
|
byte* buf = log->tail.block;
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
if (byte_offset + srv_sort_buf_size >= srv_online_max_size) {
|
|
|
|
goto write_failed;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (size == avail) {
|
2017-09-16 22:31:53 +03:00
|
|
|
ut_ad(b == &buf[srv_sort_buf_size]);
|
2013-03-26 00:03:13 +02:00
|
|
|
} else {
|
|
|
|
ut_ad(b == log->tail.buf + size);
|
2017-09-16 22:31:53 +03:00
|
|
|
memcpy(buf + log->tail.bytes, log->tail.buf, avail);
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
2016-08-12 11:17:45 +03:00
|
|
|
|
MDEV-20377: Make WITH_MSAN more usable
MemorySanitizer (clang -fsanitize=memory) requires that all code
be compiled with instrumentation enabled. The only exception is the
C runtime library. Failure to use instrumented libraries will cause
bogus messages about memory being uninitialized.
In WITH_MSAN builds, we must avoid calling getservbyname(),
because even though it is a standard library function, it is
not instrumented, not even in clang 10.
Note: Before MariaDB Server 10.5, ./mtr will typically fail
due to the old PCRE library, which was updated in MDEV-14024.
The following cmake options were tested on 10.5
in commit 94d0bb4dbeb28a94d1f87fdd55f4297ff3df0157:
cmake \
-DCMAKE_C_FLAGS='-march=native -O2' \
-DCMAKE_CXX_FLAGS='-stdlib=libc++ -march=native -O2' \
-DWITH_EMBEDDED_SERVER=OFF -DWITH_UNIT_TESTS=OFF -DCMAKE_BUILD_TYPE=Debug \
-DWITH_INNODB_{BZIP2,LZ4,LZMA,LZO,SNAPPY}=OFF \
-DPLUGIN_{ARCHIVE,TOKUDB,MROONGA,OQGRAPH,ROCKSDB,CONNECT,SPIDER}=NO \
-DWITH_SAFEMALLOC=OFF \
-DWITH_{ZLIB,SSL,PCRE}=bundled \
-DHAVE_LIBAIO_H=0 \
-DWITH_MSAN=ON
MEM_MAKE_DEFINED(): An alias for VALGRIND_MAKE_MEM_DEFINED()
and __msan_unpoison().
MEM_GET_VBITS(), MEM_SET_VBITS(): Aliases for
VALGRIND_GET_VBITS(), VALGRIND_SET_VBITS(), __msan_copy_shadow().
InnoDB: Replace the UNIV_MEM_ macros with corresponding MEM_ macros.
ut_crc32_8_hw(), ut_crc32_64_low_hw(): Use the compiler built-in
functions instead of inline assembler when building WITH_MSAN.
This will require at least -msse4.2 when building for IA-32 or AMD64.
The inline assembler would not be instrumented, and would thus cause
bogus failures.
2020-07-01 17:23:00 +03:00
|
|
|
MEM_CHECK_DEFINED(buf, srv_sort_buf_size);
|
2016-02-16 12:07:18 +01:00
|
|
|
|
2018-04-15 15:29:55 +01:00
|
|
|
if (row_log_tmpfile(log) == OS_FILE_CLOSED) {
|
2016-02-16 12:07:18 +01:00
|
|
|
log->error = DB_OUT_OF_MEMORY;
|
|
|
|
goto err_exit;
|
|
|
|
}
|
|
|
|
|
2017-09-14 09:23:20 +03:00
|
|
|
/* If encryption is enabled encrypt buffer before writing it
|
|
|
|
to file system. */
|
|
|
|
if (log_tmp_is_encrypted()) {
|
2017-09-16 22:31:53 +03:00
|
|
|
if (!log_tmp_block_encrypt(
|
|
|
|
log->tail.block, srv_sort_buf_size,
|
|
|
|
log->crypt_tail, byte_offset,
|
2018-11-22 17:07:35 +02:00
|
|
|
index->table->space_id)) {
|
2017-09-14 09:23:20 +03:00
|
|
|
log->error = DB_DECRYPTION_FAILED;
|
|
|
|
goto err_exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
srv_stats.n_rowlog_blocks_encrypted.inc();
|
|
|
|
buf = log->crypt_tail;
|
|
|
|
}
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
log->tail.blocks++;
|
2019-04-27 20:41:31 +03:00
|
|
|
if (os_file_write(
|
2017-05-23 11:09:47 +03:00
|
|
|
request,
|
|
|
|
"(modification log)",
|
|
|
|
log->fd,
|
2019-04-27 20:41:31 +03:00
|
|
|
buf, byte_offset, srv_sort_buf_size)
|
|
|
|
!= DB_SUCCESS) {
|
2013-03-26 00:03:13 +02:00
|
|
|
write_failed:
|
|
|
|
log->error = DB_ONLINE_LOG_TOO_BIG;
|
|
|
|
}
|
2020-07-02 16:52:13 +03:00
|
|
|
|
MDEV-20377: Make WITH_MSAN more usable
MemorySanitizer (clang -fsanitize=memory) requires that all code
be compiled with instrumentation enabled. The only exception is the
C runtime library. Failure to use instrumented libraries will cause
bogus messages about memory being uninitialized.
In WITH_MSAN builds, we must avoid calling getservbyname(),
because even though it is a standard library function, it is
not instrumented, not even in clang 10.
Note: Before MariaDB Server 10.5, ./mtr will typically fail
due to the old PCRE library, which was updated in MDEV-14024.
The following cmake options were tested on 10.5
in commit 94d0bb4dbeb28a94d1f87fdd55f4297ff3df0157:
cmake \
-DCMAKE_C_FLAGS='-march=native -O2' \
-DCMAKE_CXX_FLAGS='-stdlib=libc++ -march=native -O2' \
-DWITH_EMBEDDED_SERVER=OFF -DWITH_UNIT_TESTS=OFF -DCMAKE_BUILD_TYPE=Debug \
-DWITH_INNODB_{BZIP2,LZ4,LZMA,LZO,SNAPPY}=OFF \
-DPLUGIN_{ARCHIVE,TOKUDB,MROONGA,OQGRAPH,ROCKSDB,CONNECT,SPIDER}=NO \
-DWITH_SAFEMALLOC=OFF \
-DWITH_{ZLIB,SSL,PCRE}=bundled \
-DHAVE_LIBAIO_H=0 \
-DWITH_MSAN=ON
MEM_MAKE_DEFINED(): An alias for VALGRIND_MAKE_MEM_DEFINED()
and __msan_unpoison().
MEM_GET_VBITS(), MEM_SET_VBITS(): Aliases for
VALGRIND_GET_VBITS(), VALGRIND_SET_VBITS(), __msan_copy_shadow().
InnoDB: Replace the UNIV_MEM_ macros with corresponding MEM_ macros.
ut_crc32_8_hw(), ut_crc32_64_low_hw(): Use the compiler built-in
functions instead of inline assembler when building WITH_MSAN.
This will require at least -msse4.2 when building for IA-32 or AMD64.
The inline assembler would not be instrumented, and would thus cause
bogus failures.
2020-07-01 17:23:00 +03:00
|
|
|
MEM_UNDEFINED(log->tail.block, srv_sort_buf_size);
|
|
|
|
MEM_UNDEFINED(buf, srv_sort_buf_size);
|
2013-03-26 00:03:13 +02:00
|
|
|
memcpy(log->tail.block, log->tail.buf + avail, size - avail);
|
|
|
|
log->tail.bytes = size - avail;
|
|
|
|
} else {
|
|
|
|
log->tail.bytes += size;
|
|
|
|
ut_ad(b == log->tail.block + log->tail.bytes);
|
|
|
|
}
|
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
log->tail.total += size;
|
MDEV-20377: Make WITH_MSAN more usable
MemorySanitizer (clang -fsanitize=memory) requires that all code
be compiled with instrumentation enabled. The only exception is the
C runtime library. Failure to use instrumented libraries will cause
bogus messages about memory being uninitialized.
In WITH_MSAN builds, we must avoid calling getservbyname(),
because even though it is a standard library function, it is
not instrumented, not even in clang 10.
Note: Before MariaDB Server 10.5, ./mtr will typically fail
due to the old PCRE library, which was updated in MDEV-14024.
The following cmake options were tested on 10.5
in commit 94d0bb4dbeb28a94d1f87fdd55f4297ff3df0157:
cmake \
-DCMAKE_C_FLAGS='-march=native -O2' \
-DCMAKE_CXX_FLAGS='-stdlib=libc++ -march=native -O2' \
-DWITH_EMBEDDED_SERVER=OFF -DWITH_UNIT_TESTS=OFF -DCMAKE_BUILD_TYPE=Debug \
-DWITH_INNODB_{BZIP2,LZ4,LZMA,LZO,SNAPPY}=OFF \
-DPLUGIN_{ARCHIVE,TOKUDB,MROONGA,OQGRAPH,ROCKSDB,CONNECT,SPIDER}=NO \
-DWITH_SAFEMALLOC=OFF \
-DWITH_{ZLIB,SSL,PCRE}=bundled \
-DHAVE_LIBAIO_H=0 \
-DWITH_MSAN=ON
MEM_MAKE_DEFINED(): An alias for VALGRIND_MAKE_MEM_DEFINED()
and __msan_unpoison().
MEM_GET_VBITS(), MEM_SET_VBITS(): Aliases for
VALGRIND_GET_VBITS(), VALGRIND_SET_VBITS(), __msan_copy_shadow().
InnoDB: Replace the UNIV_MEM_ macros with corresponding MEM_ macros.
ut_crc32_8_hw(), ut_crc32_64_low_hw(): Use the compiler built-in
functions instead of inline assembler when building WITH_MSAN.
This will require at least -msse4.2 when building for IA-32 or AMD64.
The inline assembler would not be instrumented, and would thus cause
bogus failures.
2020-07-01 17:23:00 +03:00
|
|
|
MEM_UNDEFINED(log->tail.buf, sizeof log->tail.buf);
|
2016-02-16 12:07:18 +01:00
|
|
|
err_exit:
|
2013-03-26 00:03:13 +02:00
|
|
|
mutex_exit(&log->mutex);
|
2014-09-30 14:50:34 +03:00
|
|
|
|
2018-12-27 00:34:18 +04:00
|
|
|
onlineddl_rowlog_rows++;
|
2014-09-30 14:50:34 +03:00
|
|
|
/* 10000 means 100.00%, 4525 means 45.25% */
|
2017-01-25 10:11:37 +02:00
|
|
|
onlineddl_rowlog_pct_used = static_cast<ulint>((log->tail.total * 10000) / srv_online_max_size);
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef UNIV_DEBUG
|
2017-09-16 22:31:53 +03:00
|
|
|
# define row_log_table_close(index, b, size, avail) \
|
|
|
|
row_log_table_close_func(index, b, size, avail)
|
2013-03-26 00:03:13 +02:00
|
|
|
#else /* UNIV_DEBUG */
|
|
|
|
# define row_log_table_close(log, b, size, avail) \
|
2017-09-16 22:31:53 +03:00
|
|
|
row_log_table_close_func(index, size, avail)
|
2013-03-26 00:03:13 +02:00
|
|
|
#endif /* UNIV_DEBUG */
|
|
|
|
|
2016-09-06 09:43:16 +03:00
|
|
|
/** Check whether a virtual column is indexed in the new table being
|
|
|
|
created during alter table
|
|
|
|
@param[in] index cluster index
|
|
|
|
@param[in] v_no virtual column number
|
|
|
|
@return true if it is indexed, else false */
|
|
|
|
bool
|
|
|
|
row_log_col_is_indexed(
|
|
|
|
const dict_index_t* index,
|
|
|
|
ulint v_no)
|
|
|
|
{
|
|
|
|
return(dict_table_get_nth_v_col(
|
|
|
|
index->online_log->table, v_no)->m_col.ord_part);
|
|
|
|
}
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
/******************************************************//**
|
|
|
|
Logs a delete operation to a table that is being rebuilt.
|
|
|
|
This will be merged in row_log_table_apply_delete(). */
|
|
|
|
void
|
|
|
|
row_log_table_delete(
|
|
|
|
/*=================*/
|
|
|
|
const rec_t* rec, /*!< in: clustered index leaf page record,
|
|
|
|
page X-latched */
|
|
|
|
dict_index_t* index, /*!< in/out: clustered index, S-latched
|
|
|
|
or X-latched */
|
2020-04-28 10:46:51 +10:00
|
|
|
const rec_offs* offsets,/*!< in: rec_get_offsets(rec,index) */
|
2014-05-06 21:13:16 +02:00
|
|
|
const byte* sys) /*!< in: DB_TRX_ID,DB_ROLL_PTR that should
|
|
|
|
be logged, or NULL to use those in rec */
|
2013-03-26 00:03:13 +02:00
|
|
|
{
|
|
|
|
ulint old_pk_extra_size;
|
|
|
|
ulint old_pk_size;
|
|
|
|
ulint mrec_size;
|
|
|
|
ulint avail_size;
|
|
|
|
mem_heap_t* heap = NULL;
|
|
|
|
const dtuple_t* old_pk;
|
|
|
|
|
|
|
|
ut_ad(dict_index_is_clust(index));
|
|
|
|
ut_ad(rec_offs_validate(rec, index, offsets));
|
|
|
|
ut_ad(rec_offs_n_fields(offsets) == dict_index_get_n_fields(index));
|
|
|
|
ut_ad(rec_offs_size(offsets) <= sizeof index->online_log->tail.buf);
|
2016-08-12 11:17:45 +03:00
|
|
|
ut_ad(rw_lock_own_flagged(
|
|
|
|
&index->lock,
|
|
|
|
RW_LOCK_FLAG_S | RW_LOCK_FLAG_X | RW_LOCK_FLAG_SX));
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2018-05-07 15:30:57 +03:00
|
|
|
if (index->online_status != ONLINE_INDEX_CREATION
|
|
|
|
|| (index->type & DICT_CORRUPT) || index->table->corrupted
|
2013-03-26 00:03:13 +02:00
|
|
|
|| index->online_log->error != DB_SUCCESS) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
dict_table_t* new_table = index->online_log->table;
|
|
|
|
dict_index_t* new_index = dict_table_get_first_index(new_table);
|
|
|
|
|
|
|
|
ut_ad(dict_index_is_clust(new_index));
|
|
|
|
ut_ad(!dict_index_is_online_ddl(new_index));
|
MDEV-13654 Various crashes due to DB_TRX_ID mismatch in table-rebuilding ALTER TABLE…LOCK=NONE
After MDEV-12288 and MDEV-13536, the DB_TRX_ID of old clustered index
records for which no history is available should be reset to 0.
This caused crashes in online table-rebuilding ALTER, because the
row_log_table_apply() is built on the assumption that the PRIMARY KEY
together with DB_TRX_ID,DB_ROLL_PTR identifies the record.
Both when copying the old table and when writing log about changes to
the old table, we must map "old" DB_TRX_ID to 0. "old" here is simply
"older than the trx_id of the ALTER TABLE transaction", because
the MDL_EXCLUSIVE (and exclusive InnoDB table lock) in
ha_innobase::prepare_inplace_alter_table() forces any transactions
accessing the table to commit or rollback. So, we know that we can
safely reset any DB_TRX_ID in the table that is older than the
transaction ID of the ALTER TABLE, because the undo log history would be
lost in a table-rebuilding ALTER.
Note: After a table-rebuilding online ALTER TABLE, the rebuilt table
may end up containing some nonzero DB_TRX_ID columns. The apply logic
identifies the rows by the combination of PRIMARY KEY and DB_TRX_ID.
These nonzero DB_TRX_ID would necessarily refer to concurrent DML
operations that were started during ha_innobase::inplace_alter_table().
row_log_allocate(): Add a parameter for the ALTER TABLE transaction.
row_log_t::min_trx: The ALTER TABLE transaction ID.
trx_id_check(): A debug function to check that DB_TRX_ID makes sense
(is either 0 or bigger than the ALTER TABLE transaction ID).
reset_trx_id[]: The reset DB_TRX_ID,DB_ROLL_PTR columns.
row_log_table_delete(), row_log_table_get_pk(): Reset the
DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE transaction.
row_log_table_apply_delete(), row_log_table_apply_update():
Assert trx_id_check().
row_merge_insert_index_tuples(): Remove the unused parameter trx_id.
row_merge_read_clustered_index(): In a table-rebuilding ALTER,
reset the DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE
transaction. Assert trx_id_check() on clustered index records that
are being buffered.
2017-08-31 18:03:50 +03:00
|
|
|
ut_ad(index->online_log->min_trx);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2014-05-06 21:13:16 +02:00
|
|
|
/* Create the tuple PRIMARY KEY,DB_TRX_ID,DB_ROLL_PTR in new_table. */
|
2013-03-26 00:03:13 +02:00
|
|
|
if (index->online_log->same_pk) {
|
|
|
|
dtuple_t* tuple;
|
|
|
|
ut_ad(new_index->n_uniq == index->n_uniq);
|
|
|
|
|
2014-05-06 21:13:16 +02:00
|
|
|
/* The PRIMARY KEY and DB_TRX_ID,DB_ROLL_PTR are in the first
|
2013-03-26 00:03:13 +02:00
|
|
|
fields of the record. */
|
|
|
|
heap = mem_heap_create(
|
|
|
|
DATA_TRX_ID_LEN
|
2018-10-10 11:53:23 +03:00
|
|
|
+ DTUPLE_EST_ALLOC(new_index->first_user_field()));
|
|
|
|
old_pk = tuple = dtuple_create(heap,
|
|
|
|
new_index->first_user_field());
|
2013-03-26 00:03:13 +02:00
|
|
|
dict_index_copy_types(tuple, new_index, tuple->n_fields);
|
|
|
|
dtuple_set_n_fields_cmp(tuple, new_index->n_uniq);
|
|
|
|
|
2014-05-06 21:13:16 +02:00
|
|
|
for (ulint i = 0; i < dtuple_get_n_fields(tuple); i++) {
|
2013-03-26 00:03:13 +02:00
|
|
|
ulint len;
|
|
|
|
const void* field = rec_get_nth_field(
|
|
|
|
rec, offsets, i, &len);
|
|
|
|
dfield_t* dfield = dtuple_get_nth_field(
|
|
|
|
tuple, i);
|
|
|
|
ut_ad(len != UNIV_SQL_NULL);
|
|
|
|
ut_ad(!rec_offs_nth_extern(offsets, i));
|
|
|
|
dfield_set_data(dfield, field, len);
|
|
|
|
}
|
|
|
|
|
MDEV-13654 Various crashes due to DB_TRX_ID mismatch in table-rebuilding ALTER TABLE…LOCK=NONE
After MDEV-12288 and MDEV-13536, the DB_TRX_ID of old clustered index
records for which no history is available should be reset to 0.
This caused crashes in online table-rebuilding ALTER, because the
row_log_table_apply() is built on the assumption that the PRIMARY KEY
together with DB_TRX_ID,DB_ROLL_PTR identifies the record.
Both when copying the old table and when writing log about changes to
the old table, we must map "old" DB_TRX_ID to 0. "old" here is simply
"older than the trx_id of the ALTER TABLE transaction", because
the MDL_EXCLUSIVE (and exclusive InnoDB table lock) in
ha_innobase::prepare_inplace_alter_table() forces any transactions
accessing the table to commit or rollback. So, we know that we can
safely reset any DB_TRX_ID in the table that is older than the
transaction ID of the ALTER TABLE, because the undo log history would be
lost in a table-rebuilding ALTER.
Note: After a table-rebuilding online ALTER TABLE, the rebuilt table
may end up containing some nonzero DB_TRX_ID columns. The apply logic
identifies the rows by the combination of PRIMARY KEY and DB_TRX_ID.
These nonzero DB_TRX_ID would necessarily refer to concurrent DML
operations that were started during ha_innobase::inplace_alter_table().
row_log_allocate(): Add a parameter for the ALTER TABLE transaction.
row_log_t::min_trx: The ALTER TABLE transaction ID.
trx_id_check(): A debug function to check that DB_TRX_ID makes sense
(is either 0 or bigger than the ALTER TABLE transaction ID).
reset_trx_id[]: The reset DB_TRX_ID,DB_ROLL_PTR columns.
row_log_table_delete(), row_log_table_get_pk(): Reset the
DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE transaction.
row_log_table_apply_delete(), row_log_table_apply_update():
Assert trx_id_check().
row_merge_insert_index_tuples(): Remove the unused parameter trx_id.
row_merge_read_clustered_index(): In a table-rebuilding ALTER,
reset the DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE
transaction. Assert trx_id_check() on clustered index records that
are being buffered.
2017-08-31 18:03:50 +03:00
|
|
|
dfield_t* db_trx_id = dtuple_get_nth_field(
|
|
|
|
tuple, new_index->n_uniq);
|
|
|
|
|
|
|
|
const bool replace_sys_fields
|
|
|
|
= sys
|
|
|
|
|| trx_read_trx_id(static_cast<byte*>(db_trx_id->data))
|
|
|
|
< index->online_log->min_trx;
|
|
|
|
|
|
|
|
if (replace_sys_fields) {
|
|
|
|
if (!sys || trx_read_trx_id(sys)
|
|
|
|
< index->online_log->min_trx) {
|
|
|
|
sys = reset_trx_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
dfield_set_data(db_trx_id, sys, DATA_TRX_ID_LEN);
|
|
|
|
dfield_set_data(db_trx_id + 1, sys + DATA_TRX_ID_LEN,
|
|
|
|
DATA_ROLL_PTR_LEN);
|
2014-05-06 21:13:16 +02:00
|
|
|
}
|
MDEV-13654 Various crashes due to DB_TRX_ID mismatch in table-rebuilding ALTER TABLE…LOCK=NONE
After MDEV-12288 and MDEV-13536, the DB_TRX_ID of old clustered index
records for which no history is available should be reset to 0.
This caused crashes in online table-rebuilding ALTER, because the
row_log_table_apply() is built on the assumption that the PRIMARY KEY
together with DB_TRX_ID,DB_ROLL_PTR identifies the record.
Both when copying the old table and when writing log about changes to
the old table, we must map "old" DB_TRX_ID to 0. "old" here is simply
"older than the trx_id of the ALTER TABLE transaction", because
the MDL_EXCLUSIVE (and exclusive InnoDB table lock) in
ha_innobase::prepare_inplace_alter_table() forces any transactions
accessing the table to commit or rollback. So, we know that we can
safely reset any DB_TRX_ID in the table that is older than the
transaction ID of the ALTER TABLE, because the undo log history would be
lost in a table-rebuilding ALTER.
Note: After a table-rebuilding online ALTER TABLE, the rebuilt table
may end up containing some nonzero DB_TRX_ID columns. The apply logic
identifies the rows by the combination of PRIMARY KEY and DB_TRX_ID.
These nonzero DB_TRX_ID would necessarily refer to concurrent DML
operations that were started during ha_innobase::inplace_alter_table().
row_log_allocate(): Add a parameter for the ALTER TABLE transaction.
row_log_t::min_trx: The ALTER TABLE transaction ID.
trx_id_check(): A debug function to check that DB_TRX_ID makes sense
(is either 0 or bigger than the ALTER TABLE transaction ID).
reset_trx_id[]: The reset DB_TRX_ID,DB_ROLL_PTR columns.
row_log_table_delete(), row_log_table_get_pk(): Reset the
DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE transaction.
row_log_table_apply_delete(), row_log_table_apply_update():
Assert trx_id_check().
row_merge_insert_index_tuples(): Remove the unused parameter trx_id.
row_merge_read_clustered_index(): In a table-rebuilding ALTER,
reset the DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE
transaction. Assert trx_id_check() on clustered index records that
are being buffered.
2017-08-31 18:03:50 +03:00
|
|
|
|
|
|
|
ut_d(trx_id_check(db_trx_id->data,
|
|
|
|
index->online_log->min_trx));
|
2013-03-26 00:03:13 +02:00
|
|
|
} else {
|
|
|
|
/* The PRIMARY KEY has changed. Translate the tuple. */
|
2014-05-06 21:13:16 +02:00
|
|
|
old_pk = row_log_table_get_pk(
|
|
|
|
rec, index, offsets, NULL, &heap);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
if (!old_pk) {
|
|
|
|
ut_ad(index->online_log->error != DB_SUCCESS);
|
2014-05-06 21:13:16 +02:00
|
|
|
if (heap) {
|
|
|
|
goto func_exit;
|
|
|
|
}
|
2013-03-26 00:03:13 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ut_ad(DATA_TRX_ID_LEN == dtuple_get_nth_field(
|
2014-05-06 21:13:16 +02:00
|
|
|
old_pk, old_pk->n_fields - 2)->len);
|
|
|
|
ut_ad(DATA_ROLL_PTR_LEN == dtuple_get_nth_field(
|
2013-03-26 00:03:13 +02:00
|
|
|
old_pk, old_pk->n_fields - 1)->len);
|
2021-03-06 05:55:14 +05:30
|
|
|
old_pk_size = rec_get_converted_size_temp<false>(
|
Clean up the logging of virtual column values in table-rebuilding online ALTER
In online table-rebuilding ALTER TABLE (LOCK=NONE), virtual column values
are being written to the online_log. WL#8149 in MySQL 5.7 changed some
low-level functions that are also being used outside row0log.cc, causing
performance penalty to other code.
We revert those changes, and introduce separate functions for writing
the virtual column values.
The only functional change should be the one that is mentioned in
MDEV-13795: row_log_table_low_redundant() will no longer write
virtual column values along with old_pk, just like row_log_table_low().
As noted in MDEV-13795, some forms of table-rebuilding ALTER with
virtual columns is broken. At least DROP PRIMARY KEY, ADD PRIMARY KEY
is broken.
rec_get_converted_size_temp(), rec_convert_dtuple_to_temp():
Remove the parameter for passing virtual column values.
rec_get_converted_size_temp_v(), rec_convert_dtuple_to_temp_v():
New functions for appending virtual column values to the online_log.
rec_get_converted_size_comp_prefix_low(),
rec_convert_dtuple_to_rec_comp(): Remove the v_entry parameter,
and do not allow n_fields=0.
2017-09-13 15:41:04 +03:00
|
|
|
new_index, old_pk->fields, old_pk->n_fields,
|
2013-03-26 00:03:13 +02:00
|
|
|
&old_pk_extra_size);
|
|
|
|
ut_ad(old_pk_extra_size < 0x100);
|
|
|
|
|
2018-01-10 20:54:09 +05:30
|
|
|
/* 2 = 1 (extra_size) + at least 1 byte payload */
|
|
|
|
mrec_size = 2 + old_pk_size;
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
if (byte* b = row_log_table_open(index->online_log,
|
|
|
|
mrec_size, &avail_size)) {
|
|
|
|
*b++ = ROW_T_DELETE;
|
|
|
|
*b++ = static_cast<byte>(old_pk_extra_size);
|
|
|
|
|
2021-03-06 05:55:14 +05:30
|
|
|
rec_convert_dtuple_to_temp<false>(
|
2013-03-26 00:03:13 +02:00
|
|
|
b + old_pk_extra_size, new_index,
|
Clean up the logging of virtual column values in table-rebuilding online ALTER
In online table-rebuilding ALTER TABLE (LOCK=NONE), virtual column values
are being written to the online_log. WL#8149 in MySQL 5.7 changed some
low-level functions that are also being used outside row0log.cc, causing
performance penalty to other code.
We revert those changes, and introduce separate functions for writing
the virtual column values.
The only functional change should be the one that is mentioned in
MDEV-13795: row_log_table_low_redundant() will no longer write
virtual column values along with old_pk, just like row_log_table_low().
As noted in MDEV-13795, some forms of table-rebuilding ALTER with
virtual columns is broken. At least DROP PRIMARY KEY, ADD PRIMARY KEY
is broken.
rec_get_converted_size_temp(), rec_convert_dtuple_to_temp():
Remove the parameter for passing virtual column values.
rec_get_converted_size_temp_v(), rec_convert_dtuple_to_temp_v():
New functions for appending virtual column values to the online_log.
rec_get_converted_size_comp_prefix_low(),
rec_convert_dtuple_to_rec_comp(): Remove the v_entry parameter,
and do not allow n_fields=0.
2017-09-13 15:41:04 +03:00
|
|
|
old_pk->fields, old_pk->n_fields);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
b += old_pk_size;
|
|
|
|
|
2017-09-16 22:31:53 +03:00
|
|
|
row_log_table_close(index, b, mrec_size, avail_size);
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
2014-05-06 21:13:16 +02:00
|
|
|
func_exit:
|
2013-03-26 00:03:13 +02:00
|
|
|
mem_heap_free(heap);
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************//**
|
|
|
|
Logs an insert or update to a table that is being rebuilt. */
|
2014-02-01 09:33:26 +01:00
|
|
|
static
|
2013-03-26 00:03:13 +02:00
|
|
|
void
|
|
|
|
row_log_table_low_redundant(
|
|
|
|
/*========================*/
|
|
|
|
const rec_t* rec, /*!< in: clustered index leaf
|
|
|
|
page record in ROW_FORMAT=REDUNDANT,
|
|
|
|
page X-latched */
|
|
|
|
dict_index_t* index, /*!< in/out: clustered index, S-latched
|
|
|
|
or X-latched */
|
|
|
|
bool insert, /*!< in: true if insert,
|
|
|
|
false if update */
|
|
|
|
const dtuple_t* old_pk, /*!< in: old PRIMARY KEY value
|
|
|
|
(if !insert and a PRIMARY KEY
|
|
|
|
is being created) */
|
|
|
|
const dict_index_t* new_index)
|
|
|
|
/*!< in: clustered index of the
|
|
|
|
new table, not latched */
|
|
|
|
{
|
|
|
|
ulint old_pk_size;
|
|
|
|
ulint old_pk_extra_size;
|
|
|
|
ulint size;
|
|
|
|
ulint extra_size;
|
|
|
|
ulint mrec_size;
|
|
|
|
ulint avail_size;
|
|
|
|
mem_heap_t* heap = NULL;
|
|
|
|
dtuple_t* tuple;
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
const ulint n_fields = rec_get_n_fields_old(rec);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
ut_ad(!page_is_comp(page_align(rec)));
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
ut_ad(index->n_fields >= n_fields);
|
|
|
|
ut_ad(index->n_fields == n_fields || index->is_instant());
|
2016-08-12 11:17:45 +03:00
|
|
|
ut_ad(dict_tf2_is_valid(index->table->flags, index->table->flags2));
|
2014-02-01 09:33:26 +01:00
|
|
|
ut_ad(!dict_table_is_comp(index->table)); /* redundant row format */
|
|
|
|
ut_ad(dict_index_is_clust(new_index));
|
2013-03-26 00:03:13 +02:00
|
|
|
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
heap = mem_heap_create(DTUPLE_EST_ALLOC(n_fields));
|
2017-11-10 16:12:45 +02:00
|
|
|
tuple = dtuple_create(heap, n_fields);
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
dict_index_copy_types(tuple, index, n_fields);
|
2016-08-12 11:17:45 +03:00
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
dtuple_set_n_fields_cmp(tuple, dict_index_get_n_unique(index));
|
|
|
|
|
|
|
|
if (rec_get_1byte_offs_flag(rec)) {
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
for (ulint i = 0; i < n_fields; i++) {
|
2013-03-26 00:03:13 +02:00
|
|
|
dfield_t* dfield;
|
|
|
|
ulint len;
|
|
|
|
const void* field;
|
|
|
|
|
|
|
|
dfield = dtuple_get_nth_field(tuple, i);
|
|
|
|
field = rec_get_nth_field_old(rec, i, &len);
|
|
|
|
|
|
|
|
dfield_set_data(dfield, field, len);
|
|
|
|
}
|
|
|
|
} else {
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
for (ulint i = 0; i < n_fields; i++) {
|
2013-03-26 00:03:13 +02:00
|
|
|
dfield_t* dfield;
|
|
|
|
ulint len;
|
|
|
|
const void* field;
|
|
|
|
|
|
|
|
dfield = dtuple_get_nth_field(tuple, i);
|
|
|
|
field = rec_get_nth_field_old(rec, i, &len);
|
|
|
|
|
|
|
|
dfield_set_data(dfield, field, len);
|
|
|
|
|
|
|
|
if (rec_2_is_field_extern(rec, i)) {
|
|
|
|
dfield_set_ext(dfield);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-07 14:35:32 +02:00
|
|
|
dfield_t* db_trx_id = dtuple_get_nth_field(tuple, index->n_uniq);
|
|
|
|
ut_ad(dfield_get_len(db_trx_id) == DATA_TRX_ID_LEN);
|
|
|
|
ut_ad(dfield_get_len(db_trx_id + 1) == DATA_ROLL_PTR_LEN);
|
|
|
|
|
|
|
|
if (trx_read_trx_id(static_cast<const byte*>
|
|
|
|
(dfield_get_data(db_trx_id)))
|
|
|
|
< index->online_log->min_trx) {
|
|
|
|
dfield_set_data(db_trx_id, reset_trx_id, DATA_TRX_ID_LEN);
|
|
|
|
dfield_set_data(db_trx_id + 1, reset_trx_id + DATA_TRX_ID_LEN,
|
|
|
|
DATA_ROLL_PTR_LEN);
|
|
|
|
}
|
|
|
|
|
2018-05-03 15:17:16 +03:00
|
|
|
const bool is_instant = index->online_log->is_instant(index);
|
|
|
|
rec_comp_status_t status = is_instant
|
MDEV-15662 Instant DROP COLUMN or changing the order of columns
Allow ADD COLUMN anywhere in a table, not only adding as the
last column.
Allow instant DROP COLUMN and instant changing the order of columns.
The added columns will always be added last in clustered index records.
In new records, instantly dropped columns will be stored as NULL or
empty when possible.
Information about dropped and reordered columns will be written in
a metadata BLOB (mblob), which is stored before the first 'user' field
in the hidden metadata record at the start of the clustered index.
The presence of mblob is indicated by setting the delete-mark flag in
the metadata record.
The metadata BLOB stores the number of clustered index fields,
followed by an array of column information for each field.
For dropped columns, we store the NOT NULL flag, the fixed length,
and for variable-length columns, whether the maximum length exceeded
255 bytes. For non-dropped columns, we store the column position.
Unlike with MDEV-11369, when a table becomes empty, it cannot
be converted back to the canonical format. The reason for this is
that other threads may hold cached objects such as
row_prebuilt_t::ins_node that could refer to dropped or reordered
index fields.
For instant DROP COLUMN and ROW_FORMAT=COMPACT or ROW_FORMAT=DYNAMIC,
we must store the n_core_null_bytes in the root page, so that the
chain of node pointer records can be followed in order to reach the
leftmost leaf page where the metadata record is located.
If the mblob is present, we will zero-initialize the strings
"infimum" and "supremum" in the root page, and use the last byte of
"supremum" for storing the number of null bytes (which are allocated
but useless on node pointer pages). This is necessary for
btr_cur_instant_init_metadata() to be able to navigate to the mblob.
If the PRIMARY KEY contains any variable-length column and some
nullable columns were instantly dropped, the dict_index_t::n_nullable
in the data dictionary could be smaller than it actually is in the
non-leaf pages. Because of this, the non-leaf pages could use more
bytes for the null flags than the data dictionary expects, and we
could be reading the lengths of the variable-length columns from the
wrong offset, and thus reading the child page number from wrong place.
This is the result of two design mistakes that involve unnecessary
storage of data: First, it is nonsense to store any data fields for
the leftmost node pointer records, because the comparisons would be
resolved by the MIN_REC_FLAG alone. Second, there cannot be any null
fields in the clustered index node pointer fields, but we nevertheless
reserve space for all the null flags.
Limitations (future work):
MDEV-17459 Allow instant ALTER TABLE even if FULLTEXT INDEX exists
MDEV-17468 Avoid table rebuild on operations on generated columns
MDEV-17494 Refuse ALGORITHM=INSTANT when the row size is too large
btr_page_reorganize_low(): Preserve any metadata in the root page.
Call lock_move_reorganize_page() only after restoring the "infimum"
and "supremum" records, to avoid a memcmp() assertion failure.
dict_col_t::DROPPED: Magic value for dict_col_t::ind.
dict_col_t::clear_instant(): Renamed from dict_col_t::remove_instant().
Do not assert that the column was instantly added, because we
sometimes call this unconditionally for all columns.
Convert an instantly added column to a "core column". The old name
remove_instant() could be mistaken to refer to "instant DROP COLUMN".
dict_col_t::is_added(): Rename from dict_col_t::is_instant().
dtype_t::metadata_blob_init(): Initialize the mblob data type.
dtuple_t::is_metadata(), dtuple_t::is_alter_metadata(),
upd_t::is_metadata(), upd_t::is_alter_metadata(): Check if info_bits
refer to a metadata record.
dict_table_t::instant: Metadata about dropped or reordered columns.
dict_table_t::prepare_instant(): Prepare
ha_innobase_inplace_ctx::instant_table for instant ALTER TABLE.
innobase_instant_try() will pass this to dict_table_t::instant_column().
On rollback, dict_table_t::rollback_instant() will be called.
dict_table_t::instant_column(): Renamed from instant_add_column().
Add the parameter col_map so that columns can be reordered.
Copy and adjust v_cols[] as well.
dict_table_t::find(): Find an old column based on a new column number.
dict_table_t::serialise_columns(), dict_table_t::deserialise_columns():
Convert the mblob.
dict_index_t::instant_metadata(): Create the metadata record
for instant ALTER TABLE. Invoke dict_table_t::serialise_columns().
dict_index_t::reconstruct_fields(): Invoked by
dict_table_t::deserialise_columns().
dict_index_t::clear_instant_alter(): Move the fields for the
dropped columns to the end, and sort the surviving index fields
in ascending order of column position.
ha_innobase::check_if_supported_inplace_alter(): Do not allow
adding a FTS_DOC_ID column if a hidden FTS_DOC_ID column exists
due to FULLTEXT INDEX. (This always required ALGORITHM=COPY.)
instant_alter_column_possible(): Add a parameter for InnoDB table,
to check for additional conditions, such as the maximum number of
index fields.
ha_innobase_inplace_ctx::first_alter_pos: The first column whose position
is affected by instant ADD, DROP, or changing the order of columns.
innobase_build_col_map(): Skip added virtual columns.
prepare_inplace_add_virtual(): Correctly compute num_to_add_vcol.
Remove some unnecessary code. Note that the call to
innodb_base_col_setup() should be executed later.
commit_try_norebuild(): If ctx->is_instant(), let the virtual
columns be added or dropped by innobase_instant_try().
innobase_instant_try(): Fill in a zero default value for the
hidden column FTS_DOC_ID (to reduce the work needed in MDEV-17459).
If any columns were dropped or reordered (or added not last),
delete any SYS_COLUMNS records for the following columns, and
insert SYS_COLUMNS records for all subsequent stored columns as well
as for all virtual columns. If any virtual column is dropped, rewrite
all virtual column metadata. Use a shortcut only for adding
virtual columns. This is because innobase_drop_virtual_try()
assumes that the dropped virtual columns still exist in ctx->old_table.
innodb_update_cols(): Renamed from innodb_update_n_cols().
innobase_add_one_virtual(), innobase_insert_sys_virtual(): Change
the return type to bool, and invoke my_error() when detecting an error.
innodb_insert_sys_columns(): Insert a record into SYS_COLUMNS.
Refactored from innobase_add_one_virtual() and innobase_instant_add_col().
innobase_instant_add_col(): Replace the parameter dfield with type.
innobase_instant_drop_cols(): Drop matching columns from SYS_COLUMNS
and all columns from SYS_VIRTUAL.
innobase_add_virtual_try(), innobase_drop_virtual_try(): Let
the caller invoke innodb_update_cols().
innobase_rename_column_try(): Skip dropped columns.
commit_cache_norebuild(): Update table->fts->doc_col.
dict_mem_table_col_rename_low(): Skip dropped columns.
trx_undo_rec_get_partial_row(): Skip dropped columns.
trx_undo_update_rec_get_update(): Handle the metadata BLOB correctly.
trx_undo_page_report_modify(): Avoid out-of-bounds access to record fields.
Log metadata records consistently.
Apparently, the first fields of a clustered index may be updated
in an update_undo vector when the index is ID_IND of SYS_FOREIGN,
as part of renaming the table during ALTER TABLE. Normally, updates of
the PRIMARY KEY should be logged as delete-mark and an insert.
row_undo_mod_parse_undo_rec(), row_purge_parse_undo_rec():
Use trx_undo_metadata.
row_undo_mod_clust_low(): On metadata rollback, roll back the root page too.
row_undo_mod_clust(): Relax an assertion. The delete-mark flag was
repurposed for ALTER TABLE metadata records.
row_rec_to_index_entry_impl(): Add the template parameter mblob
and the optional parameter info_bits for specifying the desired new
info bits. For the metadata tuple, allow conversion between the original
format (ADD COLUMN only) and the generic format (with hidden BLOB).
Add the optional parameter "pad" to determine whether the tuple should
be padded to the index fields (on ALTER TABLE it should), or whether
it should remain at its original size (on rollback).
row_build_index_entry_low(): Clean up the code, removing
redundant variables and conditions. For instantly dropped columns,
generate a dummy value that is NULL, the empty string, or a
fixed length of NUL bytes, depending on the type of the dropped column.
row_upd_clust_rec_by_insert_inherit_func(): On the update of PRIMARY KEY
of a record that contained a dropped column whose value was stored
externally, we will be inserting a dummy NULL or empty string value
to the field of the dropped column. The externally stored column would
eventually be dropped when purge removes the delete-marked record for
the old PRIMARY KEY value.
btr_index_rec_validate(): Recognize the metadata record.
btr_discard_only_page_on_level(): Preserve the generic instant
ALTER TABLE metadata.
btr_set_instant(): Replaces page_set_instant(). This sets a clustered
index root page to the appropriate format, or upgrades from
the MDEV-11369 instant ADD COLUMN to generic ALTER TABLE format.
btr_cur_instant_init_low(): Read and validate the metadata BLOB page
before reconstructing the dictionary information based on it.
btr_cur_instant_init_metadata(): Do not read any lengths from the
metadata record header before reading the BLOB. At this point, we
would not actually know how many nullable fields the metadata record
contains.
btr_cur_instant_root_init(): Initialize n_core_null_bytes in one
of two possible ways.
btr_cur_trim(): Handle the mblob record.
row_metadata_to_tuple(): Convert a metadata record to a data tuple,
based on the new info_bits of the metadata record.
btr_cur_pessimistic_update(): Invoke row_metadata_to_tuple() if needed.
Invoke dtuple_convert_big_rec() for metadata records if the record is
too large, or if the mblob is not yet marked as externally stored.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
When the last user record is deleted, do not delete the
generic instant ALTER TABLE metadata record. Only delete
MDEV-11369 instant ADD COLUMN metadata records.
btr_cur_optimistic_insert(): Avoid unnecessary computation of rec_size.
btr_pcur_store_position(): Allow a logically empty page to contain
a metadata record for generic ALTER TABLE.
REC_INFO_DEFAULT_ROW_ADD: Renamed from REC_INFO_DEFAULT_ROW.
This is for the old instant ADD COLUMN (MDEV-11369) only.
REC_INFO_DEFAULT_ROW_ALTER: The more generic metadata record,
with additional information for dropped or reordered columns.
rec_info_bits_valid(): Remove. The only case when this would fail
is when the record is the generic ALTER TABLE metadata record.
rec_is_alter_metadata(): Check if a record is the metadata record
for instant ALTER TABLE (other than ADD COLUMN). NOTE: This function
must not be invoked on node pointer records, because the delete-mark
flag in those records may be set (it is garbage), and then a debug
assertion could fail because index->is_instant() does not necessarily
hold.
rec_is_add_metadata(): Check if a record is MDEV-11369 ADD COLUMN metadata
record (not more generic instant ALTER TABLE).
rec_get_converted_size_comp_prefix_low(): Assume that the metadata
field will be stored externally. In dtuple_convert_big_rec() during
the rec_get_converted_size() call, it would not be there yet.
rec_get_converted_size_comp(): Replace status,fields,n_fields with tuple.
rec_init_offsets_comp_ordinary(), rec_get_converted_size_comp_prefix_low(),
rec_convert_dtuple_to_rec_comp(): Add template<bool mblob = false>.
With mblob=true, process a record with a metadata BLOB.
rec_copy_prefix_to_buf(): Assert that no fields beyond the key and
system columns are being copied. Exclude the metadata BLOB field.
rec_convert_dtuple_to_metadata_comp(): Convert an alter metadata tuple
into a record.
row_upd_index_replace_metadata(): Apply an update vector to an
alter_metadata tuple.
row_log_allocate(): Replace dict_index_t::is_instant()
with a more appropriate condition that ignores dict_table_t::instant.
Only a table on which the MDEV-11369 ADD COLUMN was performed
can "lose its instantness" when it becomes empty. After
instant DROP COLUMN or reordering columns, we cannot simply
convert the table to the canonical format, because the data
dictionary cache and all possibly existing references to it
from other client connection threads would have to be adjusted.
row_quiesce_write_index_fields(): Do not crash when the table contains
an instantly dropped column.
Thanks to Thirunarayanan Balathandayuthapani for discussing the design
and implementing an initial prototype of this.
Thanks to Matthias Leich for testing.
2018-10-19 16:49:54 +03:00
|
|
|
? REC_STATUS_INSTANT : REC_STATUS_ORDINARY;
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
|
2021-03-06 05:55:14 +05:30
|
|
|
size = rec_get_converted_size_temp<true>(
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
index, tuple->fields, tuple->n_fields, &extra_size, status);
|
2018-05-03 15:17:16 +03:00
|
|
|
if (is_instant) {
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
size++;
|
|
|
|
extra_size++;
|
|
|
|
}
|
2013-03-26 00:03:13 +02:00
|
|
|
|
MDEV-13795/MDEV-14332 Corruption during online table-rebuilding ALTER when VIRTUAL columns exist
When MySQL 5.7 introduced indexed virtual columns, it introduced
several bugs into the online table-rebuilding ALTER, that is,
the row_log_table_apply() family of functions.
The online_log format that was introduced for online table-rebuilding
ALTER in MySQL 5.6 should be sufficient. Ideally, any indexed virtual
column values would be evaluated based on the log records in the temporary
file. There is no need to log virtual column values.
(For ADD INDEX, that is row_log_apply(), we always must log the values of
the keys, no matter if the columns are virtual.)
Because omitting the virtual column values removes any chance of
row_log_table_apply() working with indexed virtual columns, we
will for now refuse LOCK=NONE in table-rebuilding ALTER operations
when indexes on virtual columns exist. This restriction would be
lifted in MDEV-14341.
innobase_indexed_virtual_exist(): New predicate, to determine if
indexed virtual columns exist in a table definition.
ha_innobase::check_if_supported_inplace_alter(): Refuse online rebuild
if indexed virtual columns exist.
rec_get_converted_size_temp_v(), rec_convert_dtuple_to_temp_v(): Remove.
row_log_table_delete(), row_log_table_update(, row_log_table_insert():
Remove parameters for virtual columns.
trx_undo_read_v_rows(): Remove the col_map parameter.
row_log_table_apply(): Do not deal with virtual columns.
2017-11-09 23:01:44 +02:00
|
|
|
mrec_size = ROW_LOG_HEADER_SIZE + size + (extra_size >= 0x80);
|
2016-08-12 11:17:45 +03:00
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
if (insert || index->online_log->same_pk) {
|
|
|
|
ut_ad(!old_pk);
|
|
|
|
old_pk_extra_size = old_pk_size = 0;
|
|
|
|
} else {
|
|
|
|
ut_ad(old_pk);
|
|
|
|
ut_ad(old_pk->n_fields == 2 + old_pk->n_fields_cmp);
|
|
|
|
ut_ad(DATA_TRX_ID_LEN == dtuple_get_nth_field(
|
|
|
|
old_pk, old_pk->n_fields - 2)->len);
|
|
|
|
ut_ad(DATA_ROLL_PTR_LEN == dtuple_get_nth_field(
|
|
|
|
old_pk, old_pk->n_fields - 1)->len);
|
|
|
|
|
2021-03-06 05:55:14 +05:30
|
|
|
old_pk_size = rec_get_converted_size_temp<false>(
|
2013-03-26 00:03:13 +02:00
|
|
|
new_index, old_pk->fields, old_pk->n_fields,
|
Clean up the logging of virtual column values in table-rebuilding online ALTER
In online table-rebuilding ALTER TABLE (LOCK=NONE), virtual column values
are being written to the online_log. WL#8149 in MySQL 5.7 changed some
low-level functions that are also being used outside row0log.cc, causing
performance penalty to other code.
We revert those changes, and introduce separate functions for writing
the virtual column values.
The only functional change should be the one that is mentioned in
MDEV-13795: row_log_table_low_redundant() will no longer write
virtual column values along with old_pk, just like row_log_table_low().
As noted in MDEV-13795, some forms of table-rebuilding ALTER with
virtual columns is broken. At least DROP PRIMARY KEY, ADD PRIMARY KEY
is broken.
rec_get_converted_size_temp(), rec_convert_dtuple_to_temp():
Remove the parameter for passing virtual column values.
rec_get_converted_size_temp_v(), rec_convert_dtuple_to_temp_v():
New functions for appending virtual column values to the online_log.
rec_get_converted_size_comp_prefix_low(),
rec_convert_dtuple_to_rec_comp(): Remove the v_entry parameter,
and do not allow n_fields=0.
2017-09-13 15:41:04 +03:00
|
|
|
&old_pk_extra_size);
|
2013-03-26 00:03:13 +02:00
|
|
|
ut_ad(old_pk_extra_size < 0x100);
|
|
|
|
mrec_size += 1/*old_pk_extra_size*/ + old_pk_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (byte* b = row_log_table_open(index->online_log,
|
|
|
|
mrec_size, &avail_size)) {
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
if (insert) {
|
|
|
|
*b++ = ROW_T_INSERT;
|
|
|
|
} else {
|
|
|
|
*b++ = ROW_T_UPDATE;
|
2013-03-26 00:03:13 +02:00
|
|
|
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
if (old_pk_size) {
|
|
|
|
*b++ = static_cast<byte>(old_pk_extra_size);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2021-03-06 05:55:14 +05:30
|
|
|
rec_convert_dtuple_to_temp<false>(
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
b + old_pk_extra_size, new_index,
|
|
|
|
old_pk->fields, old_pk->n_fields);
|
|
|
|
b += old_pk_size;
|
|
|
|
}
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (extra_size < 0x80) {
|
|
|
|
*b++ = static_cast<byte>(extra_size);
|
|
|
|
} else {
|
|
|
|
ut_ad(extra_size < 0x8000);
|
|
|
|
*b++ = static_cast<byte>(0x80 | (extra_size >> 8));
|
|
|
|
*b++ = static_cast<byte>(extra_size);
|
|
|
|
}
|
|
|
|
|
MDEV-15662 Instant DROP COLUMN or changing the order of columns
Allow ADD COLUMN anywhere in a table, not only adding as the
last column.
Allow instant DROP COLUMN and instant changing the order of columns.
The added columns will always be added last in clustered index records.
In new records, instantly dropped columns will be stored as NULL or
empty when possible.
Information about dropped and reordered columns will be written in
a metadata BLOB (mblob), which is stored before the first 'user' field
in the hidden metadata record at the start of the clustered index.
The presence of mblob is indicated by setting the delete-mark flag in
the metadata record.
The metadata BLOB stores the number of clustered index fields,
followed by an array of column information for each field.
For dropped columns, we store the NOT NULL flag, the fixed length,
and for variable-length columns, whether the maximum length exceeded
255 bytes. For non-dropped columns, we store the column position.
Unlike with MDEV-11369, when a table becomes empty, it cannot
be converted back to the canonical format. The reason for this is
that other threads may hold cached objects such as
row_prebuilt_t::ins_node that could refer to dropped or reordered
index fields.
For instant DROP COLUMN and ROW_FORMAT=COMPACT or ROW_FORMAT=DYNAMIC,
we must store the n_core_null_bytes in the root page, so that the
chain of node pointer records can be followed in order to reach the
leftmost leaf page where the metadata record is located.
If the mblob is present, we will zero-initialize the strings
"infimum" and "supremum" in the root page, and use the last byte of
"supremum" for storing the number of null bytes (which are allocated
but useless on node pointer pages). This is necessary for
btr_cur_instant_init_metadata() to be able to navigate to the mblob.
If the PRIMARY KEY contains any variable-length column and some
nullable columns were instantly dropped, the dict_index_t::n_nullable
in the data dictionary could be smaller than it actually is in the
non-leaf pages. Because of this, the non-leaf pages could use more
bytes for the null flags than the data dictionary expects, and we
could be reading the lengths of the variable-length columns from the
wrong offset, and thus reading the child page number from wrong place.
This is the result of two design mistakes that involve unnecessary
storage of data: First, it is nonsense to store any data fields for
the leftmost node pointer records, because the comparisons would be
resolved by the MIN_REC_FLAG alone. Second, there cannot be any null
fields in the clustered index node pointer fields, but we nevertheless
reserve space for all the null flags.
Limitations (future work):
MDEV-17459 Allow instant ALTER TABLE even if FULLTEXT INDEX exists
MDEV-17468 Avoid table rebuild on operations on generated columns
MDEV-17494 Refuse ALGORITHM=INSTANT when the row size is too large
btr_page_reorganize_low(): Preserve any metadata in the root page.
Call lock_move_reorganize_page() only after restoring the "infimum"
and "supremum" records, to avoid a memcmp() assertion failure.
dict_col_t::DROPPED: Magic value for dict_col_t::ind.
dict_col_t::clear_instant(): Renamed from dict_col_t::remove_instant().
Do not assert that the column was instantly added, because we
sometimes call this unconditionally for all columns.
Convert an instantly added column to a "core column". The old name
remove_instant() could be mistaken to refer to "instant DROP COLUMN".
dict_col_t::is_added(): Rename from dict_col_t::is_instant().
dtype_t::metadata_blob_init(): Initialize the mblob data type.
dtuple_t::is_metadata(), dtuple_t::is_alter_metadata(),
upd_t::is_metadata(), upd_t::is_alter_metadata(): Check if info_bits
refer to a metadata record.
dict_table_t::instant: Metadata about dropped or reordered columns.
dict_table_t::prepare_instant(): Prepare
ha_innobase_inplace_ctx::instant_table for instant ALTER TABLE.
innobase_instant_try() will pass this to dict_table_t::instant_column().
On rollback, dict_table_t::rollback_instant() will be called.
dict_table_t::instant_column(): Renamed from instant_add_column().
Add the parameter col_map so that columns can be reordered.
Copy and adjust v_cols[] as well.
dict_table_t::find(): Find an old column based on a new column number.
dict_table_t::serialise_columns(), dict_table_t::deserialise_columns():
Convert the mblob.
dict_index_t::instant_metadata(): Create the metadata record
for instant ALTER TABLE. Invoke dict_table_t::serialise_columns().
dict_index_t::reconstruct_fields(): Invoked by
dict_table_t::deserialise_columns().
dict_index_t::clear_instant_alter(): Move the fields for the
dropped columns to the end, and sort the surviving index fields
in ascending order of column position.
ha_innobase::check_if_supported_inplace_alter(): Do not allow
adding a FTS_DOC_ID column if a hidden FTS_DOC_ID column exists
due to FULLTEXT INDEX. (This always required ALGORITHM=COPY.)
instant_alter_column_possible(): Add a parameter for InnoDB table,
to check for additional conditions, such as the maximum number of
index fields.
ha_innobase_inplace_ctx::first_alter_pos: The first column whose position
is affected by instant ADD, DROP, or changing the order of columns.
innobase_build_col_map(): Skip added virtual columns.
prepare_inplace_add_virtual(): Correctly compute num_to_add_vcol.
Remove some unnecessary code. Note that the call to
innodb_base_col_setup() should be executed later.
commit_try_norebuild(): If ctx->is_instant(), let the virtual
columns be added or dropped by innobase_instant_try().
innobase_instant_try(): Fill in a zero default value for the
hidden column FTS_DOC_ID (to reduce the work needed in MDEV-17459).
If any columns were dropped or reordered (or added not last),
delete any SYS_COLUMNS records for the following columns, and
insert SYS_COLUMNS records for all subsequent stored columns as well
as for all virtual columns. If any virtual column is dropped, rewrite
all virtual column metadata. Use a shortcut only for adding
virtual columns. This is because innobase_drop_virtual_try()
assumes that the dropped virtual columns still exist in ctx->old_table.
innodb_update_cols(): Renamed from innodb_update_n_cols().
innobase_add_one_virtual(), innobase_insert_sys_virtual(): Change
the return type to bool, and invoke my_error() when detecting an error.
innodb_insert_sys_columns(): Insert a record into SYS_COLUMNS.
Refactored from innobase_add_one_virtual() and innobase_instant_add_col().
innobase_instant_add_col(): Replace the parameter dfield with type.
innobase_instant_drop_cols(): Drop matching columns from SYS_COLUMNS
and all columns from SYS_VIRTUAL.
innobase_add_virtual_try(), innobase_drop_virtual_try(): Let
the caller invoke innodb_update_cols().
innobase_rename_column_try(): Skip dropped columns.
commit_cache_norebuild(): Update table->fts->doc_col.
dict_mem_table_col_rename_low(): Skip dropped columns.
trx_undo_rec_get_partial_row(): Skip dropped columns.
trx_undo_update_rec_get_update(): Handle the metadata BLOB correctly.
trx_undo_page_report_modify(): Avoid out-of-bounds access to record fields.
Log metadata records consistently.
Apparently, the first fields of a clustered index may be updated
in an update_undo vector when the index is ID_IND of SYS_FOREIGN,
as part of renaming the table during ALTER TABLE. Normally, updates of
the PRIMARY KEY should be logged as delete-mark and an insert.
row_undo_mod_parse_undo_rec(), row_purge_parse_undo_rec():
Use trx_undo_metadata.
row_undo_mod_clust_low(): On metadata rollback, roll back the root page too.
row_undo_mod_clust(): Relax an assertion. The delete-mark flag was
repurposed for ALTER TABLE metadata records.
row_rec_to_index_entry_impl(): Add the template parameter mblob
and the optional parameter info_bits for specifying the desired new
info bits. For the metadata tuple, allow conversion between the original
format (ADD COLUMN only) and the generic format (with hidden BLOB).
Add the optional parameter "pad" to determine whether the tuple should
be padded to the index fields (on ALTER TABLE it should), or whether
it should remain at its original size (on rollback).
row_build_index_entry_low(): Clean up the code, removing
redundant variables and conditions. For instantly dropped columns,
generate a dummy value that is NULL, the empty string, or a
fixed length of NUL bytes, depending on the type of the dropped column.
row_upd_clust_rec_by_insert_inherit_func(): On the update of PRIMARY KEY
of a record that contained a dropped column whose value was stored
externally, we will be inserting a dummy NULL or empty string value
to the field of the dropped column. The externally stored column would
eventually be dropped when purge removes the delete-marked record for
the old PRIMARY KEY value.
btr_index_rec_validate(): Recognize the metadata record.
btr_discard_only_page_on_level(): Preserve the generic instant
ALTER TABLE metadata.
btr_set_instant(): Replaces page_set_instant(). This sets a clustered
index root page to the appropriate format, or upgrades from
the MDEV-11369 instant ADD COLUMN to generic ALTER TABLE format.
btr_cur_instant_init_low(): Read and validate the metadata BLOB page
before reconstructing the dictionary information based on it.
btr_cur_instant_init_metadata(): Do not read any lengths from the
metadata record header before reading the BLOB. At this point, we
would not actually know how many nullable fields the metadata record
contains.
btr_cur_instant_root_init(): Initialize n_core_null_bytes in one
of two possible ways.
btr_cur_trim(): Handle the mblob record.
row_metadata_to_tuple(): Convert a metadata record to a data tuple,
based on the new info_bits of the metadata record.
btr_cur_pessimistic_update(): Invoke row_metadata_to_tuple() if needed.
Invoke dtuple_convert_big_rec() for metadata records if the record is
too large, or if the mblob is not yet marked as externally stored.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
When the last user record is deleted, do not delete the
generic instant ALTER TABLE metadata record. Only delete
MDEV-11369 instant ADD COLUMN metadata records.
btr_cur_optimistic_insert(): Avoid unnecessary computation of rec_size.
btr_pcur_store_position(): Allow a logically empty page to contain
a metadata record for generic ALTER TABLE.
REC_INFO_DEFAULT_ROW_ADD: Renamed from REC_INFO_DEFAULT_ROW.
This is for the old instant ADD COLUMN (MDEV-11369) only.
REC_INFO_DEFAULT_ROW_ALTER: The more generic metadata record,
with additional information for dropped or reordered columns.
rec_info_bits_valid(): Remove. The only case when this would fail
is when the record is the generic ALTER TABLE metadata record.
rec_is_alter_metadata(): Check if a record is the metadata record
for instant ALTER TABLE (other than ADD COLUMN). NOTE: This function
must not be invoked on node pointer records, because the delete-mark
flag in those records may be set (it is garbage), and then a debug
assertion could fail because index->is_instant() does not necessarily
hold.
rec_is_add_metadata(): Check if a record is MDEV-11369 ADD COLUMN metadata
record (not more generic instant ALTER TABLE).
rec_get_converted_size_comp_prefix_low(): Assume that the metadata
field will be stored externally. In dtuple_convert_big_rec() during
the rec_get_converted_size() call, it would not be there yet.
rec_get_converted_size_comp(): Replace status,fields,n_fields with tuple.
rec_init_offsets_comp_ordinary(), rec_get_converted_size_comp_prefix_low(),
rec_convert_dtuple_to_rec_comp(): Add template<bool mblob = false>.
With mblob=true, process a record with a metadata BLOB.
rec_copy_prefix_to_buf(): Assert that no fields beyond the key and
system columns are being copied. Exclude the metadata BLOB field.
rec_convert_dtuple_to_metadata_comp(): Convert an alter metadata tuple
into a record.
row_upd_index_replace_metadata(): Apply an update vector to an
alter_metadata tuple.
row_log_allocate(): Replace dict_index_t::is_instant()
with a more appropriate condition that ignores dict_table_t::instant.
Only a table on which the MDEV-11369 ADD COLUMN was performed
can "lose its instantness" when it becomes empty. After
instant DROP COLUMN or reordering columns, we cannot simply
convert the table to the canonical format, because the data
dictionary cache and all possibly existing references to it
from other client connection threads would have to be adjusted.
row_quiesce_write_index_fields(): Do not crash when the table contains
an instantly dropped column.
Thanks to Thirunarayanan Balathandayuthapani for discussing the design
and implementing an initial prototype of this.
Thanks to Matthias Leich for testing.
2018-10-19 16:49:54 +03:00
|
|
|
if (status == REC_STATUS_INSTANT) {
|
2018-05-03 15:17:16 +03:00
|
|
|
ut_ad(is_instant);
|
|
|
|
if (n_fields <= index->online_log->n_core_fields) {
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
status = REC_STATUS_ORDINARY;
|
|
|
|
}
|
|
|
|
*b = status;
|
|
|
|
}
|
|
|
|
|
2021-03-06 05:55:14 +05:30
|
|
|
rec_convert_dtuple_to_temp<true>(
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
b + extra_size, index, tuple->fields, tuple->n_fields,
|
|
|
|
status);
|
2013-03-26 00:03:13 +02:00
|
|
|
b += size;
|
2016-08-12 11:17:45 +03:00
|
|
|
|
2017-09-16 22:31:53 +03:00
|
|
|
row_log_table_close(index, b, mrec_size, avail_size);
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
mem_heap_free(heap);
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************//**
|
|
|
|
Logs an insert or update to a table that is being rebuilt. */
|
2016-09-06 09:43:16 +03:00
|
|
|
static
|
2013-03-26 00:03:13 +02:00
|
|
|
void
|
|
|
|
row_log_table_low(
|
|
|
|
/*==============*/
|
|
|
|
const rec_t* rec, /*!< in: clustered index leaf page record,
|
|
|
|
page X-latched */
|
|
|
|
dict_index_t* index, /*!< in/out: clustered index, S-latched
|
|
|
|
or X-latched */
|
2020-04-28 10:46:51 +10:00
|
|
|
const rec_offs* offsets,/*!< in: rec_get_offsets(rec,index) */
|
2013-03-26 00:03:13 +02:00
|
|
|
bool insert, /*!< in: true if insert, false if update */
|
|
|
|
const dtuple_t* old_pk) /*!< in: old PRIMARY KEY value (if !insert
|
|
|
|
and a PRIMARY KEY is being created) */
|
|
|
|
{
|
|
|
|
ulint old_pk_size;
|
|
|
|
ulint old_pk_extra_size;
|
|
|
|
ulint extra_size;
|
|
|
|
ulint mrec_size;
|
|
|
|
ulint avail_size;
|
2016-08-12 11:17:45 +03:00
|
|
|
const dict_index_t* new_index;
|
2018-07-26 22:52:53 +03:00
|
|
|
row_log_t* log = index->online_log;
|
2016-08-12 11:17:45 +03:00
|
|
|
|
2018-07-26 22:52:53 +03:00
|
|
|
new_index = dict_table_get_first_index(log->table);
|
2016-08-12 11:17:45 +03:00
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
ut_ad(dict_index_is_clust(index));
|
|
|
|
ut_ad(dict_index_is_clust(new_index));
|
|
|
|
ut_ad(!dict_index_is_online_ddl(new_index));
|
|
|
|
ut_ad(rec_offs_validate(rec, index, offsets));
|
|
|
|
ut_ad(rec_offs_n_fields(offsets) == dict_index_get_n_fields(index));
|
2018-07-26 22:52:53 +03:00
|
|
|
ut_ad(rec_offs_size(offsets) <= sizeof log->tail.buf);
|
2016-08-12 11:17:45 +03:00
|
|
|
ut_ad(rw_lock_own_flagged(
|
|
|
|
&index->lock,
|
|
|
|
RW_LOCK_FLAG_S | RW_LOCK_FLAG_X | RW_LOCK_FLAG_SX));
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
#ifdef UNIV_DEBUG
|
|
|
|
switch (fil_page_get_type(page_align(rec))) {
|
|
|
|
case FIL_PAGE_INDEX:
|
|
|
|
break;
|
|
|
|
case FIL_PAGE_TYPE_INSTANT:
|
|
|
|
ut_ad(index->is_instant());
|
2019-03-25 11:02:03 +02:00
|
|
|
ut_ad(!page_has_siblings(page_align(rec)));
|
|
|
|
ut_ad(page_get_page_no(page_align(rec)) == index->page);
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ut_ad(!"wrong page type");
|
|
|
|
}
|
|
|
|
#endif /* UNIV_DEBUG */
|
MDEV-15662 Instant DROP COLUMN or changing the order of columns
Allow ADD COLUMN anywhere in a table, not only adding as the
last column.
Allow instant DROP COLUMN and instant changing the order of columns.
The added columns will always be added last in clustered index records.
In new records, instantly dropped columns will be stored as NULL or
empty when possible.
Information about dropped and reordered columns will be written in
a metadata BLOB (mblob), which is stored before the first 'user' field
in the hidden metadata record at the start of the clustered index.
The presence of mblob is indicated by setting the delete-mark flag in
the metadata record.
The metadata BLOB stores the number of clustered index fields,
followed by an array of column information for each field.
For dropped columns, we store the NOT NULL flag, the fixed length,
and for variable-length columns, whether the maximum length exceeded
255 bytes. For non-dropped columns, we store the column position.
Unlike with MDEV-11369, when a table becomes empty, it cannot
be converted back to the canonical format. The reason for this is
that other threads may hold cached objects such as
row_prebuilt_t::ins_node that could refer to dropped or reordered
index fields.
For instant DROP COLUMN and ROW_FORMAT=COMPACT or ROW_FORMAT=DYNAMIC,
we must store the n_core_null_bytes in the root page, so that the
chain of node pointer records can be followed in order to reach the
leftmost leaf page where the metadata record is located.
If the mblob is present, we will zero-initialize the strings
"infimum" and "supremum" in the root page, and use the last byte of
"supremum" for storing the number of null bytes (which are allocated
but useless on node pointer pages). This is necessary for
btr_cur_instant_init_metadata() to be able to navigate to the mblob.
If the PRIMARY KEY contains any variable-length column and some
nullable columns were instantly dropped, the dict_index_t::n_nullable
in the data dictionary could be smaller than it actually is in the
non-leaf pages. Because of this, the non-leaf pages could use more
bytes for the null flags than the data dictionary expects, and we
could be reading the lengths of the variable-length columns from the
wrong offset, and thus reading the child page number from wrong place.
This is the result of two design mistakes that involve unnecessary
storage of data: First, it is nonsense to store any data fields for
the leftmost node pointer records, because the comparisons would be
resolved by the MIN_REC_FLAG alone. Second, there cannot be any null
fields in the clustered index node pointer fields, but we nevertheless
reserve space for all the null flags.
Limitations (future work):
MDEV-17459 Allow instant ALTER TABLE even if FULLTEXT INDEX exists
MDEV-17468 Avoid table rebuild on operations on generated columns
MDEV-17494 Refuse ALGORITHM=INSTANT when the row size is too large
btr_page_reorganize_low(): Preserve any metadata in the root page.
Call lock_move_reorganize_page() only after restoring the "infimum"
and "supremum" records, to avoid a memcmp() assertion failure.
dict_col_t::DROPPED: Magic value for dict_col_t::ind.
dict_col_t::clear_instant(): Renamed from dict_col_t::remove_instant().
Do not assert that the column was instantly added, because we
sometimes call this unconditionally for all columns.
Convert an instantly added column to a "core column". The old name
remove_instant() could be mistaken to refer to "instant DROP COLUMN".
dict_col_t::is_added(): Rename from dict_col_t::is_instant().
dtype_t::metadata_blob_init(): Initialize the mblob data type.
dtuple_t::is_metadata(), dtuple_t::is_alter_metadata(),
upd_t::is_metadata(), upd_t::is_alter_metadata(): Check if info_bits
refer to a metadata record.
dict_table_t::instant: Metadata about dropped or reordered columns.
dict_table_t::prepare_instant(): Prepare
ha_innobase_inplace_ctx::instant_table for instant ALTER TABLE.
innobase_instant_try() will pass this to dict_table_t::instant_column().
On rollback, dict_table_t::rollback_instant() will be called.
dict_table_t::instant_column(): Renamed from instant_add_column().
Add the parameter col_map so that columns can be reordered.
Copy and adjust v_cols[] as well.
dict_table_t::find(): Find an old column based on a new column number.
dict_table_t::serialise_columns(), dict_table_t::deserialise_columns():
Convert the mblob.
dict_index_t::instant_metadata(): Create the metadata record
for instant ALTER TABLE. Invoke dict_table_t::serialise_columns().
dict_index_t::reconstruct_fields(): Invoked by
dict_table_t::deserialise_columns().
dict_index_t::clear_instant_alter(): Move the fields for the
dropped columns to the end, and sort the surviving index fields
in ascending order of column position.
ha_innobase::check_if_supported_inplace_alter(): Do not allow
adding a FTS_DOC_ID column if a hidden FTS_DOC_ID column exists
due to FULLTEXT INDEX. (This always required ALGORITHM=COPY.)
instant_alter_column_possible(): Add a parameter for InnoDB table,
to check for additional conditions, such as the maximum number of
index fields.
ha_innobase_inplace_ctx::first_alter_pos: The first column whose position
is affected by instant ADD, DROP, or changing the order of columns.
innobase_build_col_map(): Skip added virtual columns.
prepare_inplace_add_virtual(): Correctly compute num_to_add_vcol.
Remove some unnecessary code. Note that the call to
innodb_base_col_setup() should be executed later.
commit_try_norebuild(): If ctx->is_instant(), let the virtual
columns be added or dropped by innobase_instant_try().
innobase_instant_try(): Fill in a zero default value for the
hidden column FTS_DOC_ID (to reduce the work needed in MDEV-17459).
If any columns were dropped or reordered (or added not last),
delete any SYS_COLUMNS records for the following columns, and
insert SYS_COLUMNS records for all subsequent stored columns as well
as for all virtual columns. If any virtual column is dropped, rewrite
all virtual column metadata. Use a shortcut only for adding
virtual columns. This is because innobase_drop_virtual_try()
assumes that the dropped virtual columns still exist in ctx->old_table.
innodb_update_cols(): Renamed from innodb_update_n_cols().
innobase_add_one_virtual(), innobase_insert_sys_virtual(): Change
the return type to bool, and invoke my_error() when detecting an error.
innodb_insert_sys_columns(): Insert a record into SYS_COLUMNS.
Refactored from innobase_add_one_virtual() and innobase_instant_add_col().
innobase_instant_add_col(): Replace the parameter dfield with type.
innobase_instant_drop_cols(): Drop matching columns from SYS_COLUMNS
and all columns from SYS_VIRTUAL.
innobase_add_virtual_try(), innobase_drop_virtual_try(): Let
the caller invoke innodb_update_cols().
innobase_rename_column_try(): Skip dropped columns.
commit_cache_norebuild(): Update table->fts->doc_col.
dict_mem_table_col_rename_low(): Skip dropped columns.
trx_undo_rec_get_partial_row(): Skip dropped columns.
trx_undo_update_rec_get_update(): Handle the metadata BLOB correctly.
trx_undo_page_report_modify(): Avoid out-of-bounds access to record fields.
Log metadata records consistently.
Apparently, the first fields of a clustered index may be updated
in an update_undo vector when the index is ID_IND of SYS_FOREIGN,
as part of renaming the table during ALTER TABLE. Normally, updates of
the PRIMARY KEY should be logged as delete-mark and an insert.
row_undo_mod_parse_undo_rec(), row_purge_parse_undo_rec():
Use trx_undo_metadata.
row_undo_mod_clust_low(): On metadata rollback, roll back the root page too.
row_undo_mod_clust(): Relax an assertion. The delete-mark flag was
repurposed for ALTER TABLE metadata records.
row_rec_to_index_entry_impl(): Add the template parameter mblob
and the optional parameter info_bits for specifying the desired new
info bits. For the metadata tuple, allow conversion between the original
format (ADD COLUMN only) and the generic format (with hidden BLOB).
Add the optional parameter "pad" to determine whether the tuple should
be padded to the index fields (on ALTER TABLE it should), or whether
it should remain at its original size (on rollback).
row_build_index_entry_low(): Clean up the code, removing
redundant variables and conditions. For instantly dropped columns,
generate a dummy value that is NULL, the empty string, or a
fixed length of NUL bytes, depending on the type of the dropped column.
row_upd_clust_rec_by_insert_inherit_func(): On the update of PRIMARY KEY
of a record that contained a dropped column whose value was stored
externally, we will be inserting a dummy NULL or empty string value
to the field of the dropped column. The externally stored column would
eventually be dropped when purge removes the delete-marked record for
the old PRIMARY KEY value.
btr_index_rec_validate(): Recognize the metadata record.
btr_discard_only_page_on_level(): Preserve the generic instant
ALTER TABLE metadata.
btr_set_instant(): Replaces page_set_instant(). This sets a clustered
index root page to the appropriate format, or upgrades from
the MDEV-11369 instant ADD COLUMN to generic ALTER TABLE format.
btr_cur_instant_init_low(): Read and validate the metadata BLOB page
before reconstructing the dictionary information based on it.
btr_cur_instant_init_metadata(): Do not read any lengths from the
metadata record header before reading the BLOB. At this point, we
would not actually know how many nullable fields the metadata record
contains.
btr_cur_instant_root_init(): Initialize n_core_null_bytes in one
of two possible ways.
btr_cur_trim(): Handle the mblob record.
row_metadata_to_tuple(): Convert a metadata record to a data tuple,
based on the new info_bits of the metadata record.
btr_cur_pessimistic_update(): Invoke row_metadata_to_tuple() if needed.
Invoke dtuple_convert_big_rec() for metadata records if the record is
too large, or if the mblob is not yet marked as externally stored.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
When the last user record is deleted, do not delete the
generic instant ALTER TABLE metadata record. Only delete
MDEV-11369 instant ADD COLUMN metadata records.
btr_cur_optimistic_insert(): Avoid unnecessary computation of rec_size.
btr_pcur_store_position(): Allow a logically empty page to contain
a metadata record for generic ALTER TABLE.
REC_INFO_DEFAULT_ROW_ADD: Renamed from REC_INFO_DEFAULT_ROW.
This is for the old instant ADD COLUMN (MDEV-11369) only.
REC_INFO_DEFAULT_ROW_ALTER: The more generic metadata record,
with additional information for dropped or reordered columns.
rec_info_bits_valid(): Remove. The only case when this would fail
is when the record is the generic ALTER TABLE metadata record.
rec_is_alter_metadata(): Check if a record is the metadata record
for instant ALTER TABLE (other than ADD COLUMN). NOTE: This function
must not be invoked on node pointer records, because the delete-mark
flag in those records may be set (it is garbage), and then a debug
assertion could fail because index->is_instant() does not necessarily
hold.
rec_is_add_metadata(): Check if a record is MDEV-11369 ADD COLUMN metadata
record (not more generic instant ALTER TABLE).
rec_get_converted_size_comp_prefix_low(): Assume that the metadata
field will be stored externally. In dtuple_convert_big_rec() during
the rec_get_converted_size() call, it would not be there yet.
rec_get_converted_size_comp(): Replace status,fields,n_fields with tuple.
rec_init_offsets_comp_ordinary(), rec_get_converted_size_comp_prefix_low(),
rec_convert_dtuple_to_rec_comp(): Add template<bool mblob = false>.
With mblob=true, process a record with a metadata BLOB.
rec_copy_prefix_to_buf(): Assert that no fields beyond the key and
system columns are being copied. Exclude the metadata BLOB field.
rec_convert_dtuple_to_metadata_comp(): Convert an alter metadata tuple
into a record.
row_upd_index_replace_metadata(): Apply an update vector to an
alter_metadata tuple.
row_log_allocate(): Replace dict_index_t::is_instant()
with a more appropriate condition that ignores dict_table_t::instant.
Only a table on which the MDEV-11369 ADD COLUMN was performed
can "lose its instantness" when it becomes empty. After
instant DROP COLUMN or reordering columns, we cannot simply
convert the table to the canonical format, because the data
dictionary cache and all possibly existing references to it
from other client connection threads would have to be adjusted.
row_quiesce_write_index_fields(): Do not crash when the table contains
an instantly dropped column.
Thanks to Thirunarayanan Balathandayuthapani for discussing the design
and implementing an initial prototype of this.
Thanks to Matthias Leich for testing.
2018-10-19 16:49:54 +03:00
|
|
|
ut_ad(!rec_is_metadata(rec, *index));
|
2017-09-19 12:40:29 +03:00
|
|
|
ut_ad(page_rec_is_leaf(rec));
|
2013-03-26 00:03:13 +02:00
|
|
|
ut_ad(!page_is_comp(page_align(rec)) == !rec_offs_comp(offsets));
|
2016-08-30 08:34:49 +03:00
|
|
|
/* old_pk=row_log_table_get_pk() [not needed in INSERT] is a prefix
|
|
|
|
of the clustered index record (PRIMARY KEY,DB_TRX_ID,DB_ROLL_PTR),
|
|
|
|
with no information on virtual columns */
|
|
|
|
ut_ad(!old_pk || !insert);
|
|
|
|
ut_ad(!old_pk || old_pk->n_v_fields == 0);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2018-05-07 15:30:57 +03:00
|
|
|
if (index->online_status != ONLINE_INDEX_CREATION
|
|
|
|
|| (index->type & DICT_CORRUPT) || index->table->corrupted
|
2018-07-26 22:52:53 +03:00
|
|
|
|| log->error != DB_SUCCESS) {
|
2013-03-26 00:03:13 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!rec_offs_comp(offsets)) {
|
|
|
|
row_log_table_low_redundant(
|
MDEV-13795/MDEV-14332 Corruption during online table-rebuilding ALTER when VIRTUAL columns exist
When MySQL 5.7 introduced indexed virtual columns, it introduced
several bugs into the online table-rebuilding ALTER, that is,
the row_log_table_apply() family of functions.
The online_log format that was introduced for online table-rebuilding
ALTER in MySQL 5.6 should be sufficient. Ideally, any indexed virtual
column values would be evaluated based on the log records in the temporary
file. There is no need to log virtual column values.
(For ADD INDEX, that is row_log_apply(), we always must log the values of
the keys, no matter if the columns are virtual.)
Because omitting the virtual column values removes any chance of
row_log_table_apply() working with indexed virtual columns, we
will for now refuse LOCK=NONE in table-rebuilding ALTER operations
when indexes on virtual columns exist. This restriction would be
lifted in MDEV-14341.
innobase_indexed_virtual_exist(): New predicate, to determine if
indexed virtual columns exist in a table definition.
ha_innobase::check_if_supported_inplace_alter(): Refuse online rebuild
if indexed virtual columns exist.
rec_get_converted_size_temp_v(), rec_convert_dtuple_to_temp_v(): Remove.
row_log_table_delete(), row_log_table_update(, row_log_table_insert():
Remove parameters for virtual columns.
trx_undo_read_v_rows(): Remove the col_map parameter.
row_log_table_apply(): Do not deal with virtual columns.
2017-11-09 23:01:44 +02:00
|
|
|
rec, index, insert, old_pk, new_index);
|
2013-03-26 00:03:13 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ut_ad(page_is_comp(page_align(rec)));
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
ut_ad(rec_get_status(rec) == REC_STATUS_ORDINARY
|
MDEV-15662 Instant DROP COLUMN or changing the order of columns
Allow ADD COLUMN anywhere in a table, not only adding as the
last column.
Allow instant DROP COLUMN and instant changing the order of columns.
The added columns will always be added last in clustered index records.
In new records, instantly dropped columns will be stored as NULL or
empty when possible.
Information about dropped and reordered columns will be written in
a metadata BLOB (mblob), which is stored before the first 'user' field
in the hidden metadata record at the start of the clustered index.
The presence of mblob is indicated by setting the delete-mark flag in
the metadata record.
The metadata BLOB stores the number of clustered index fields,
followed by an array of column information for each field.
For dropped columns, we store the NOT NULL flag, the fixed length,
and for variable-length columns, whether the maximum length exceeded
255 bytes. For non-dropped columns, we store the column position.
Unlike with MDEV-11369, when a table becomes empty, it cannot
be converted back to the canonical format. The reason for this is
that other threads may hold cached objects such as
row_prebuilt_t::ins_node that could refer to dropped or reordered
index fields.
For instant DROP COLUMN and ROW_FORMAT=COMPACT or ROW_FORMAT=DYNAMIC,
we must store the n_core_null_bytes in the root page, so that the
chain of node pointer records can be followed in order to reach the
leftmost leaf page where the metadata record is located.
If the mblob is present, we will zero-initialize the strings
"infimum" and "supremum" in the root page, and use the last byte of
"supremum" for storing the number of null bytes (which are allocated
but useless on node pointer pages). This is necessary for
btr_cur_instant_init_metadata() to be able to navigate to the mblob.
If the PRIMARY KEY contains any variable-length column and some
nullable columns were instantly dropped, the dict_index_t::n_nullable
in the data dictionary could be smaller than it actually is in the
non-leaf pages. Because of this, the non-leaf pages could use more
bytes for the null flags than the data dictionary expects, and we
could be reading the lengths of the variable-length columns from the
wrong offset, and thus reading the child page number from wrong place.
This is the result of two design mistakes that involve unnecessary
storage of data: First, it is nonsense to store any data fields for
the leftmost node pointer records, because the comparisons would be
resolved by the MIN_REC_FLAG alone. Second, there cannot be any null
fields in the clustered index node pointer fields, but we nevertheless
reserve space for all the null flags.
Limitations (future work):
MDEV-17459 Allow instant ALTER TABLE even if FULLTEXT INDEX exists
MDEV-17468 Avoid table rebuild on operations on generated columns
MDEV-17494 Refuse ALGORITHM=INSTANT when the row size is too large
btr_page_reorganize_low(): Preserve any metadata in the root page.
Call lock_move_reorganize_page() only after restoring the "infimum"
and "supremum" records, to avoid a memcmp() assertion failure.
dict_col_t::DROPPED: Magic value for dict_col_t::ind.
dict_col_t::clear_instant(): Renamed from dict_col_t::remove_instant().
Do not assert that the column was instantly added, because we
sometimes call this unconditionally for all columns.
Convert an instantly added column to a "core column". The old name
remove_instant() could be mistaken to refer to "instant DROP COLUMN".
dict_col_t::is_added(): Rename from dict_col_t::is_instant().
dtype_t::metadata_blob_init(): Initialize the mblob data type.
dtuple_t::is_metadata(), dtuple_t::is_alter_metadata(),
upd_t::is_metadata(), upd_t::is_alter_metadata(): Check if info_bits
refer to a metadata record.
dict_table_t::instant: Metadata about dropped or reordered columns.
dict_table_t::prepare_instant(): Prepare
ha_innobase_inplace_ctx::instant_table for instant ALTER TABLE.
innobase_instant_try() will pass this to dict_table_t::instant_column().
On rollback, dict_table_t::rollback_instant() will be called.
dict_table_t::instant_column(): Renamed from instant_add_column().
Add the parameter col_map so that columns can be reordered.
Copy and adjust v_cols[] as well.
dict_table_t::find(): Find an old column based on a new column number.
dict_table_t::serialise_columns(), dict_table_t::deserialise_columns():
Convert the mblob.
dict_index_t::instant_metadata(): Create the metadata record
for instant ALTER TABLE. Invoke dict_table_t::serialise_columns().
dict_index_t::reconstruct_fields(): Invoked by
dict_table_t::deserialise_columns().
dict_index_t::clear_instant_alter(): Move the fields for the
dropped columns to the end, and sort the surviving index fields
in ascending order of column position.
ha_innobase::check_if_supported_inplace_alter(): Do not allow
adding a FTS_DOC_ID column if a hidden FTS_DOC_ID column exists
due to FULLTEXT INDEX. (This always required ALGORITHM=COPY.)
instant_alter_column_possible(): Add a parameter for InnoDB table,
to check for additional conditions, such as the maximum number of
index fields.
ha_innobase_inplace_ctx::first_alter_pos: The first column whose position
is affected by instant ADD, DROP, or changing the order of columns.
innobase_build_col_map(): Skip added virtual columns.
prepare_inplace_add_virtual(): Correctly compute num_to_add_vcol.
Remove some unnecessary code. Note that the call to
innodb_base_col_setup() should be executed later.
commit_try_norebuild(): If ctx->is_instant(), let the virtual
columns be added or dropped by innobase_instant_try().
innobase_instant_try(): Fill in a zero default value for the
hidden column FTS_DOC_ID (to reduce the work needed in MDEV-17459).
If any columns were dropped or reordered (or added not last),
delete any SYS_COLUMNS records for the following columns, and
insert SYS_COLUMNS records for all subsequent stored columns as well
as for all virtual columns. If any virtual column is dropped, rewrite
all virtual column metadata. Use a shortcut only for adding
virtual columns. This is because innobase_drop_virtual_try()
assumes that the dropped virtual columns still exist in ctx->old_table.
innodb_update_cols(): Renamed from innodb_update_n_cols().
innobase_add_one_virtual(), innobase_insert_sys_virtual(): Change
the return type to bool, and invoke my_error() when detecting an error.
innodb_insert_sys_columns(): Insert a record into SYS_COLUMNS.
Refactored from innobase_add_one_virtual() and innobase_instant_add_col().
innobase_instant_add_col(): Replace the parameter dfield with type.
innobase_instant_drop_cols(): Drop matching columns from SYS_COLUMNS
and all columns from SYS_VIRTUAL.
innobase_add_virtual_try(), innobase_drop_virtual_try(): Let
the caller invoke innodb_update_cols().
innobase_rename_column_try(): Skip dropped columns.
commit_cache_norebuild(): Update table->fts->doc_col.
dict_mem_table_col_rename_low(): Skip dropped columns.
trx_undo_rec_get_partial_row(): Skip dropped columns.
trx_undo_update_rec_get_update(): Handle the metadata BLOB correctly.
trx_undo_page_report_modify(): Avoid out-of-bounds access to record fields.
Log metadata records consistently.
Apparently, the first fields of a clustered index may be updated
in an update_undo vector when the index is ID_IND of SYS_FOREIGN,
as part of renaming the table during ALTER TABLE. Normally, updates of
the PRIMARY KEY should be logged as delete-mark and an insert.
row_undo_mod_parse_undo_rec(), row_purge_parse_undo_rec():
Use trx_undo_metadata.
row_undo_mod_clust_low(): On metadata rollback, roll back the root page too.
row_undo_mod_clust(): Relax an assertion. The delete-mark flag was
repurposed for ALTER TABLE metadata records.
row_rec_to_index_entry_impl(): Add the template parameter mblob
and the optional parameter info_bits for specifying the desired new
info bits. For the metadata tuple, allow conversion between the original
format (ADD COLUMN only) and the generic format (with hidden BLOB).
Add the optional parameter "pad" to determine whether the tuple should
be padded to the index fields (on ALTER TABLE it should), or whether
it should remain at its original size (on rollback).
row_build_index_entry_low(): Clean up the code, removing
redundant variables and conditions. For instantly dropped columns,
generate a dummy value that is NULL, the empty string, or a
fixed length of NUL bytes, depending on the type of the dropped column.
row_upd_clust_rec_by_insert_inherit_func(): On the update of PRIMARY KEY
of a record that contained a dropped column whose value was stored
externally, we will be inserting a dummy NULL or empty string value
to the field of the dropped column. The externally stored column would
eventually be dropped when purge removes the delete-marked record for
the old PRIMARY KEY value.
btr_index_rec_validate(): Recognize the metadata record.
btr_discard_only_page_on_level(): Preserve the generic instant
ALTER TABLE metadata.
btr_set_instant(): Replaces page_set_instant(). This sets a clustered
index root page to the appropriate format, or upgrades from
the MDEV-11369 instant ADD COLUMN to generic ALTER TABLE format.
btr_cur_instant_init_low(): Read and validate the metadata BLOB page
before reconstructing the dictionary information based on it.
btr_cur_instant_init_metadata(): Do not read any lengths from the
metadata record header before reading the BLOB. At this point, we
would not actually know how many nullable fields the metadata record
contains.
btr_cur_instant_root_init(): Initialize n_core_null_bytes in one
of two possible ways.
btr_cur_trim(): Handle the mblob record.
row_metadata_to_tuple(): Convert a metadata record to a data tuple,
based on the new info_bits of the metadata record.
btr_cur_pessimistic_update(): Invoke row_metadata_to_tuple() if needed.
Invoke dtuple_convert_big_rec() for metadata records if the record is
too large, or if the mblob is not yet marked as externally stored.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
When the last user record is deleted, do not delete the
generic instant ALTER TABLE metadata record. Only delete
MDEV-11369 instant ADD COLUMN metadata records.
btr_cur_optimistic_insert(): Avoid unnecessary computation of rec_size.
btr_pcur_store_position(): Allow a logically empty page to contain
a metadata record for generic ALTER TABLE.
REC_INFO_DEFAULT_ROW_ADD: Renamed from REC_INFO_DEFAULT_ROW.
This is for the old instant ADD COLUMN (MDEV-11369) only.
REC_INFO_DEFAULT_ROW_ALTER: The more generic metadata record,
with additional information for dropped or reordered columns.
rec_info_bits_valid(): Remove. The only case when this would fail
is when the record is the generic ALTER TABLE metadata record.
rec_is_alter_metadata(): Check if a record is the metadata record
for instant ALTER TABLE (other than ADD COLUMN). NOTE: This function
must not be invoked on node pointer records, because the delete-mark
flag in those records may be set (it is garbage), and then a debug
assertion could fail because index->is_instant() does not necessarily
hold.
rec_is_add_metadata(): Check if a record is MDEV-11369 ADD COLUMN metadata
record (not more generic instant ALTER TABLE).
rec_get_converted_size_comp_prefix_low(): Assume that the metadata
field will be stored externally. In dtuple_convert_big_rec() during
the rec_get_converted_size() call, it would not be there yet.
rec_get_converted_size_comp(): Replace status,fields,n_fields with tuple.
rec_init_offsets_comp_ordinary(), rec_get_converted_size_comp_prefix_low(),
rec_convert_dtuple_to_rec_comp(): Add template<bool mblob = false>.
With mblob=true, process a record with a metadata BLOB.
rec_copy_prefix_to_buf(): Assert that no fields beyond the key and
system columns are being copied. Exclude the metadata BLOB field.
rec_convert_dtuple_to_metadata_comp(): Convert an alter metadata tuple
into a record.
row_upd_index_replace_metadata(): Apply an update vector to an
alter_metadata tuple.
row_log_allocate(): Replace dict_index_t::is_instant()
with a more appropriate condition that ignores dict_table_t::instant.
Only a table on which the MDEV-11369 ADD COLUMN was performed
can "lose its instantness" when it becomes empty. After
instant DROP COLUMN or reordering columns, we cannot simply
convert the table to the canonical format, because the data
dictionary cache and all possibly existing references to it
from other client connection threads would have to be adjusted.
row_quiesce_write_index_fields(): Do not crash when the table contains
an instantly dropped column.
Thanks to Thirunarayanan Balathandayuthapani for discussing the design
and implementing an initial prototype of this.
Thanks to Matthias Leich for testing.
2018-10-19 16:49:54 +03:00
|
|
|
|| rec_get_status(rec) == REC_STATUS_INSTANT);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
const ulint omit_size = REC_N_NEW_EXTRA_BYTES;
|
2013-03-26 00:03:13 +02:00
|
|
|
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
const ulint rec_extra_size = rec_offs_extra_size(offsets) - omit_size;
|
2018-07-26 22:52:53 +03:00
|
|
|
const bool is_instant = log->is_instant(index);
|
2018-05-03 15:17:16 +03:00
|
|
|
extra_size = rec_extra_size + is_instant;
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2018-07-26 22:52:53 +03:00
|
|
|
unsigned fake_extra_size = 0;
|
2019-08-16 08:29:41 +03:00
|
|
|
byte fake_extra_buf[3];
|
2018-07-26 22:52:53 +03:00
|
|
|
if (is_instant && UNIV_UNLIKELY(!index->is_instant())) {
|
|
|
|
/* The source table was emptied after ALTER TABLE
|
|
|
|
started, and it was converted to non-instant format.
|
|
|
|
Because row_log_table_apply_op() expects to find
|
|
|
|
all records to be logged in the same way, we will
|
|
|
|
be unable to copy the rec_extra_size bytes from the
|
|
|
|
record header, but must convert them here. */
|
|
|
|
unsigned n_add = index->n_fields - 1 - log->n_core_fields;
|
|
|
|
fake_extra_size = rec_get_n_add_field_len(n_add);
|
|
|
|
ut_ad(fake_extra_size == 1 || fake_extra_size == 2);
|
|
|
|
extra_size += fake_extra_size;
|
2019-08-16 08:29:41 +03:00
|
|
|
byte* fake_extra = fake_extra_buf + fake_extra_size;
|
2018-07-26 22:52:53 +03:00
|
|
|
rec_set_n_add_field(fake_extra, n_add);
|
2019-08-16 08:29:41 +03:00
|
|
|
ut_ad(fake_extra == fake_extra_buf);
|
2018-07-26 22:52:53 +03:00
|
|
|
}
|
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
mrec_size = ROW_LOG_HEADER_SIZE
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
+ (extra_size >= 0x80) + rec_offs_size(offsets) - omit_size
|
2018-07-26 22:52:53 +03:00
|
|
|
+ is_instant + fake_extra_size;
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2018-07-26 22:52:53 +03:00
|
|
|
if (insert || log->same_pk) {
|
2013-03-26 00:03:13 +02:00
|
|
|
ut_ad(!old_pk);
|
|
|
|
old_pk_extra_size = old_pk_size = 0;
|
|
|
|
} else {
|
|
|
|
ut_ad(old_pk);
|
|
|
|
ut_ad(old_pk->n_fields == 2 + old_pk->n_fields_cmp);
|
|
|
|
ut_ad(DATA_TRX_ID_LEN == dtuple_get_nth_field(
|
|
|
|
old_pk, old_pk->n_fields - 2)->len);
|
|
|
|
ut_ad(DATA_ROLL_PTR_LEN == dtuple_get_nth_field(
|
|
|
|
old_pk, old_pk->n_fields - 1)->len);
|
|
|
|
|
2021-03-06 05:55:14 +05:30
|
|
|
old_pk_size = rec_get_converted_size_temp<false>(
|
2013-03-26 00:03:13 +02:00
|
|
|
new_index, old_pk->fields, old_pk->n_fields,
|
Clean up the logging of virtual column values in table-rebuilding online ALTER
In online table-rebuilding ALTER TABLE (LOCK=NONE), virtual column values
are being written to the online_log. WL#8149 in MySQL 5.7 changed some
low-level functions that are also being used outside row0log.cc, causing
performance penalty to other code.
We revert those changes, and introduce separate functions for writing
the virtual column values.
The only functional change should be the one that is mentioned in
MDEV-13795: row_log_table_low_redundant() will no longer write
virtual column values along with old_pk, just like row_log_table_low().
As noted in MDEV-13795, some forms of table-rebuilding ALTER with
virtual columns is broken. At least DROP PRIMARY KEY, ADD PRIMARY KEY
is broken.
rec_get_converted_size_temp(), rec_convert_dtuple_to_temp():
Remove the parameter for passing virtual column values.
rec_get_converted_size_temp_v(), rec_convert_dtuple_to_temp_v():
New functions for appending virtual column values to the online_log.
rec_get_converted_size_comp_prefix_low(),
rec_convert_dtuple_to_rec_comp(): Remove the v_entry parameter,
and do not allow n_fields=0.
2017-09-13 15:41:04 +03:00
|
|
|
&old_pk_extra_size);
|
2013-03-26 00:03:13 +02:00
|
|
|
ut_ad(old_pk_extra_size < 0x100);
|
|
|
|
mrec_size += 1/*old_pk_extra_size*/ + old_pk_size;
|
|
|
|
}
|
|
|
|
|
2018-07-26 22:52:53 +03:00
|
|
|
if (byte* b = row_log_table_open(log, mrec_size, &avail_size)) {
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
if (insert) {
|
|
|
|
*b++ = ROW_T_INSERT;
|
|
|
|
} else {
|
|
|
|
*b++ = ROW_T_UPDATE;
|
2013-03-26 00:03:13 +02:00
|
|
|
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
if (old_pk_size) {
|
|
|
|
*b++ = static_cast<byte>(old_pk_extra_size);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2021-03-06 05:55:14 +05:30
|
|
|
rec_convert_dtuple_to_temp<false>(
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
b + old_pk_extra_size, new_index,
|
|
|
|
old_pk->fields, old_pk->n_fields);
|
|
|
|
b += old_pk_size;
|
|
|
|
}
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (extra_size < 0x80) {
|
|
|
|
*b++ = static_cast<byte>(extra_size);
|
|
|
|
} else {
|
|
|
|
ut_ad(extra_size < 0x8000);
|
|
|
|
*b++ = static_cast<byte>(0x80 | (extra_size >> 8));
|
|
|
|
*b++ = static_cast<byte>(extra_size);
|
|
|
|
}
|
|
|
|
|
2018-05-03 15:17:16 +03:00
|
|
|
if (is_instant) {
|
2018-07-26 22:52:53 +03:00
|
|
|
*b++ = fake_extra_size
|
MDEV-15662 Instant DROP COLUMN or changing the order of columns
Allow ADD COLUMN anywhere in a table, not only adding as the
last column.
Allow instant DROP COLUMN and instant changing the order of columns.
The added columns will always be added last in clustered index records.
In new records, instantly dropped columns will be stored as NULL or
empty when possible.
Information about dropped and reordered columns will be written in
a metadata BLOB (mblob), which is stored before the first 'user' field
in the hidden metadata record at the start of the clustered index.
The presence of mblob is indicated by setting the delete-mark flag in
the metadata record.
The metadata BLOB stores the number of clustered index fields,
followed by an array of column information for each field.
For dropped columns, we store the NOT NULL flag, the fixed length,
and for variable-length columns, whether the maximum length exceeded
255 bytes. For non-dropped columns, we store the column position.
Unlike with MDEV-11369, when a table becomes empty, it cannot
be converted back to the canonical format. The reason for this is
that other threads may hold cached objects such as
row_prebuilt_t::ins_node that could refer to dropped or reordered
index fields.
For instant DROP COLUMN and ROW_FORMAT=COMPACT or ROW_FORMAT=DYNAMIC,
we must store the n_core_null_bytes in the root page, so that the
chain of node pointer records can be followed in order to reach the
leftmost leaf page where the metadata record is located.
If the mblob is present, we will zero-initialize the strings
"infimum" and "supremum" in the root page, and use the last byte of
"supremum" for storing the number of null bytes (which are allocated
but useless on node pointer pages). This is necessary for
btr_cur_instant_init_metadata() to be able to navigate to the mblob.
If the PRIMARY KEY contains any variable-length column and some
nullable columns were instantly dropped, the dict_index_t::n_nullable
in the data dictionary could be smaller than it actually is in the
non-leaf pages. Because of this, the non-leaf pages could use more
bytes for the null flags than the data dictionary expects, and we
could be reading the lengths of the variable-length columns from the
wrong offset, and thus reading the child page number from wrong place.
This is the result of two design mistakes that involve unnecessary
storage of data: First, it is nonsense to store any data fields for
the leftmost node pointer records, because the comparisons would be
resolved by the MIN_REC_FLAG alone. Second, there cannot be any null
fields in the clustered index node pointer fields, but we nevertheless
reserve space for all the null flags.
Limitations (future work):
MDEV-17459 Allow instant ALTER TABLE even if FULLTEXT INDEX exists
MDEV-17468 Avoid table rebuild on operations on generated columns
MDEV-17494 Refuse ALGORITHM=INSTANT when the row size is too large
btr_page_reorganize_low(): Preserve any metadata in the root page.
Call lock_move_reorganize_page() only after restoring the "infimum"
and "supremum" records, to avoid a memcmp() assertion failure.
dict_col_t::DROPPED: Magic value for dict_col_t::ind.
dict_col_t::clear_instant(): Renamed from dict_col_t::remove_instant().
Do not assert that the column was instantly added, because we
sometimes call this unconditionally for all columns.
Convert an instantly added column to a "core column". The old name
remove_instant() could be mistaken to refer to "instant DROP COLUMN".
dict_col_t::is_added(): Rename from dict_col_t::is_instant().
dtype_t::metadata_blob_init(): Initialize the mblob data type.
dtuple_t::is_metadata(), dtuple_t::is_alter_metadata(),
upd_t::is_metadata(), upd_t::is_alter_metadata(): Check if info_bits
refer to a metadata record.
dict_table_t::instant: Metadata about dropped or reordered columns.
dict_table_t::prepare_instant(): Prepare
ha_innobase_inplace_ctx::instant_table for instant ALTER TABLE.
innobase_instant_try() will pass this to dict_table_t::instant_column().
On rollback, dict_table_t::rollback_instant() will be called.
dict_table_t::instant_column(): Renamed from instant_add_column().
Add the parameter col_map so that columns can be reordered.
Copy and adjust v_cols[] as well.
dict_table_t::find(): Find an old column based on a new column number.
dict_table_t::serialise_columns(), dict_table_t::deserialise_columns():
Convert the mblob.
dict_index_t::instant_metadata(): Create the metadata record
for instant ALTER TABLE. Invoke dict_table_t::serialise_columns().
dict_index_t::reconstruct_fields(): Invoked by
dict_table_t::deserialise_columns().
dict_index_t::clear_instant_alter(): Move the fields for the
dropped columns to the end, and sort the surviving index fields
in ascending order of column position.
ha_innobase::check_if_supported_inplace_alter(): Do not allow
adding a FTS_DOC_ID column if a hidden FTS_DOC_ID column exists
due to FULLTEXT INDEX. (This always required ALGORITHM=COPY.)
instant_alter_column_possible(): Add a parameter for InnoDB table,
to check for additional conditions, such as the maximum number of
index fields.
ha_innobase_inplace_ctx::first_alter_pos: The first column whose position
is affected by instant ADD, DROP, or changing the order of columns.
innobase_build_col_map(): Skip added virtual columns.
prepare_inplace_add_virtual(): Correctly compute num_to_add_vcol.
Remove some unnecessary code. Note that the call to
innodb_base_col_setup() should be executed later.
commit_try_norebuild(): If ctx->is_instant(), let the virtual
columns be added or dropped by innobase_instant_try().
innobase_instant_try(): Fill in a zero default value for the
hidden column FTS_DOC_ID (to reduce the work needed in MDEV-17459).
If any columns were dropped or reordered (or added not last),
delete any SYS_COLUMNS records for the following columns, and
insert SYS_COLUMNS records for all subsequent stored columns as well
as for all virtual columns. If any virtual column is dropped, rewrite
all virtual column metadata. Use a shortcut only for adding
virtual columns. This is because innobase_drop_virtual_try()
assumes that the dropped virtual columns still exist in ctx->old_table.
innodb_update_cols(): Renamed from innodb_update_n_cols().
innobase_add_one_virtual(), innobase_insert_sys_virtual(): Change
the return type to bool, and invoke my_error() when detecting an error.
innodb_insert_sys_columns(): Insert a record into SYS_COLUMNS.
Refactored from innobase_add_one_virtual() and innobase_instant_add_col().
innobase_instant_add_col(): Replace the parameter dfield with type.
innobase_instant_drop_cols(): Drop matching columns from SYS_COLUMNS
and all columns from SYS_VIRTUAL.
innobase_add_virtual_try(), innobase_drop_virtual_try(): Let
the caller invoke innodb_update_cols().
innobase_rename_column_try(): Skip dropped columns.
commit_cache_norebuild(): Update table->fts->doc_col.
dict_mem_table_col_rename_low(): Skip dropped columns.
trx_undo_rec_get_partial_row(): Skip dropped columns.
trx_undo_update_rec_get_update(): Handle the metadata BLOB correctly.
trx_undo_page_report_modify(): Avoid out-of-bounds access to record fields.
Log metadata records consistently.
Apparently, the first fields of a clustered index may be updated
in an update_undo vector when the index is ID_IND of SYS_FOREIGN,
as part of renaming the table during ALTER TABLE. Normally, updates of
the PRIMARY KEY should be logged as delete-mark and an insert.
row_undo_mod_parse_undo_rec(), row_purge_parse_undo_rec():
Use trx_undo_metadata.
row_undo_mod_clust_low(): On metadata rollback, roll back the root page too.
row_undo_mod_clust(): Relax an assertion. The delete-mark flag was
repurposed for ALTER TABLE metadata records.
row_rec_to_index_entry_impl(): Add the template parameter mblob
and the optional parameter info_bits for specifying the desired new
info bits. For the metadata tuple, allow conversion between the original
format (ADD COLUMN only) and the generic format (with hidden BLOB).
Add the optional parameter "pad" to determine whether the tuple should
be padded to the index fields (on ALTER TABLE it should), or whether
it should remain at its original size (on rollback).
row_build_index_entry_low(): Clean up the code, removing
redundant variables and conditions. For instantly dropped columns,
generate a dummy value that is NULL, the empty string, or a
fixed length of NUL bytes, depending on the type of the dropped column.
row_upd_clust_rec_by_insert_inherit_func(): On the update of PRIMARY KEY
of a record that contained a dropped column whose value was stored
externally, we will be inserting a dummy NULL or empty string value
to the field of the dropped column. The externally stored column would
eventually be dropped when purge removes the delete-marked record for
the old PRIMARY KEY value.
btr_index_rec_validate(): Recognize the metadata record.
btr_discard_only_page_on_level(): Preserve the generic instant
ALTER TABLE metadata.
btr_set_instant(): Replaces page_set_instant(). This sets a clustered
index root page to the appropriate format, or upgrades from
the MDEV-11369 instant ADD COLUMN to generic ALTER TABLE format.
btr_cur_instant_init_low(): Read and validate the metadata BLOB page
before reconstructing the dictionary information based on it.
btr_cur_instant_init_metadata(): Do not read any lengths from the
metadata record header before reading the BLOB. At this point, we
would not actually know how many nullable fields the metadata record
contains.
btr_cur_instant_root_init(): Initialize n_core_null_bytes in one
of two possible ways.
btr_cur_trim(): Handle the mblob record.
row_metadata_to_tuple(): Convert a metadata record to a data tuple,
based on the new info_bits of the metadata record.
btr_cur_pessimistic_update(): Invoke row_metadata_to_tuple() if needed.
Invoke dtuple_convert_big_rec() for metadata records if the record is
too large, or if the mblob is not yet marked as externally stored.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
When the last user record is deleted, do not delete the
generic instant ALTER TABLE metadata record. Only delete
MDEV-11369 instant ADD COLUMN metadata records.
btr_cur_optimistic_insert(): Avoid unnecessary computation of rec_size.
btr_pcur_store_position(): Allow a logically empty page to contain
a metadata record for generic ALTER TABLE.
REC_INFO_DEFAULT_ROW_ADD: Renamed from REC_INFO_DEFAULT_ROW.
This is for the old instant ADD COLUMN (MDEV-11369) only.
REC_INFO_DEFAULT_ROW_ALTER: The more generic metadata record,
with additional information for dropped or reordered columns.
rec_info_bits_valid(): Remove. The only case when this would fail
is when the record is the generic ALTER TABLE metadata record.
rec_is_alter_metadata(): Check if a record is the metadata record
for instant ALTER TABLE (other than ADD COLUMN). NOTE: This function
must not be invoked on node pointer records, because the delete-mark
flag in those records may be set (it is garbage), and then a debug
assertion could fail because index->is_instant() does not necessarily
hold.
rec_is_add_metadata(): Check if a record is MDEV-11369 ADD COLUMN metadata
record (not more generic instant ALTER TABLE).
rec_get_converted_size_comp_prefix_low(): Assume that the metadata
field will be stored externally. In dtuple_convert_big_rec() during
the rec_get_converted_size() call, it would not be there yet.
rec_get_converted_size_comp(): Replace status,fields,n_fields with tuple.
rec_init_offsets_comp_ordinary(), rec_get_converted_size_comp_prefix_low(),
rec_convert_dtuple_to_rec_comp(): Add template<bool mblob = false>.
With mblob=true, process a record with a metadata BLOB.
rec_copy_prefix_to_buf(): Assert that no fields beyond the key and
system columns are being copied. Exclude the metadata BLOB field.
rec_convert_dtuple_to_metadata_comp(): Convert an alter metadata tuple
into a record.
row_upd_index_replace_metadata(): Apply an update vector to an
alter_metadata tuple.
row_log_allocate(): Replace dict_index_t::is_instant()
with a more appropriate condition that ignores dict_table_t::instant.
Only a table on which the MDEV-11369 ADD COLUMN was performed
can "lose its instantness" when it becomes empty. After
instant DROP COLUMN or reordering columns, we cannot simply
convert the table to the canonical format, because the data
dictionary cache and all possibly existing references to it
from other client connection threads would have to be adjusted.
row_quiesce_write_index_fields(): Do not crash when the table contains
an instantly dropped column.
Thanks to Thirunarayanan Balathandayuthapani for discussing the design
and implementing an initial prototype of this.
Thanks to Matthias Leich for testing.
2018-10-19 16:49:54 +03:00
|
|
|
? REC_STATUS_INSTANT
|
2018-07-26 22:52:53 +03:00
|
|
|
: rec_get_status(rec);
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
} else {
|
|
|
|
ut_ad(rec_get_status(rec) == REC_STATUS_ORDINARY);
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(b, rec - rec_extra_size - omit_size, rec_extra_size);
|
|
|
|
b += rec_extra_size;
|
2019-08-16 08:29:41 +03:00
|
|
|
memcpy(b, fake_extra_buf + 1, fake_extra_size);
|
2018-07-26 22:52:53 +03:00
|
|
|
b += fake_extra_size;
|
2017-12-07 14:35:32 +02:00
|
|
|
ulint len;
|
|
|
|
ulint trx_id_offs = rec_get_nth_field_offs(
|
|
|
|
offsets, index->n_uniq, &len);
|
|
|
|
ut_ad(len == DATA_TRX_ID_LEN);
|
2013-03-26 00:03:13 +02:00
|
|
|
memcpy(b, rec, rec_offs_data_size(offsets));
|
2018-07-26 22:52:53 +03:00
|
|
|
if (trx_read_trx_id(b + trx_id_offs) < log->min_trx) {
|
2017-12-07 14:35:32 +02:00
|
|
|
memcpy(b + trx_id_offs,
|
|
|
|
reset_trx_id, sizeof reset_trx_id);
|
|
|
|
}
|
2013-03-26 00:03:13 +02:00
|
|
|
b += rec_offs_data_size(offsets);
|
|
|
|
|
2017-09-16 22:31:53 +03:00
|
|
|
row_log_table_close(index, b, mrec_size, avail_size);
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************//**
|
|
|
|
Logs an update to a table that is being rebuilt.
|
|
|
|
This will be merged in row_log_table_apply_update(). */
|
|
|
|
void
|
|
|
|
row_log_table_update(
|
|
|
|
/*=================*/
|
|
|
|
const rec_t* rec, /*!< in: clustered index leaf page record,
|
|
|
|
page X-latched */
|
|
|
|
dict_index_t* index, /*!< in/out: clustered index, S-latched
|
|
|
|
or X-latched */
|
2020-04-28 10:46:51 +10:00
|
|
|
const rec_offs* offsets,/*!< in: rec_get_offsets(rec,index) */
|
MDEV-13795/MDEV-14332 Corruption during online table-rebuilding ALTER when VIRTUAL columns exist
When MySQL 5.7 introduced indexed virtual columns, it introduced
several bugs into the online table-rebuilding ALTER, that is,
the row_log_table_apply() family of functions.
The online_log format that was introduced for online table-rebuilding
ALTER in MySQL 5.6 should be sufficient. Ideally, any indexed virtual
column values would be evaluated based on the log records in the temporary
file. There is no need to log virtual column values.
(For ADD INDEX, that is row_log_apply(), we always must log the values of
the keys, no matter if the columns are virtual.)
Because omitting the virtual column values removes any chance of
row_log_table_apply() working with indexed virtual columns, we
will for now refuse LOCK=NONE in table-rebuilding ALTER operations
when indexes on virtual columns exist. This restriction would be
lifted in MDEV-14341.
innobase_indexed_virtual_exist(): New predicate, to determine if
indexed virtual columns exist in a table definition.
ha_innobase::check_if_supported_inplace_alter(): Refuse online rebuild
if indexed virtual columns exist.
rec_get_converted_size_temp_v(), rec_convert_dtuple_to_temp_v(): Remove.
row_log_table_delete(), row_log_table_update(, row_log_table_insert():
Remove parameters for virtual columns.
trx_undo_read_v_rows(): Remove the col_map parameter.
row_log_table_apply(): Do not deal with virtual columns.
2017-11-09 23:01:44 +02:00
|
|
|
const dtuple_t* old_pk) /*!< in: row_log_table_get_pk()
|
2013-03-26 00:03:13 +02:00
|
|
|
before the update */
|
|
|
|
{
|
MDEV-13795/MDEV-14332 Corruption during online table-rebuilding ALTER when VIRTUAL columns exist
When MySQL 5.7 introduced indexed virtual columns, it introduced
several bugs into the online table-rebuilding ALTER, that is,
the row_log_table_apply() family of functions.
The online_log format that was introduced for online table-rebuilding
ALTER in MySQL 5.6 should be sufficient. Ideally, any indexed virtual
column values would be evaluated based on the log records in the temporary
file. There is no need to log virtual column values.
(For ADD INDEX, that is row_log_apply(), we always must log the values of
the keys, no matter if the columns are virtual.)
Because omitting the virtual column values removes any chance of
row_log_table_apply() working with indexed virtual columns, we
will for now refuse LOCK=NONE in table-rebuilding ALTER operations
when indexes on virtual columns exist. This restriction would be
lifted in MDEV-14341.
innobase_indexed_virtual_exist(): New predicate, to determine if
indexed virtual columns exist in a table definition.
ha_innobase::check_if_supported_inplace_alter(): Refuse online rebuild
if indexed virtual columns exist.
rec_get_converted_size_temp_v(), rec_convert_dtuple_to_temp_v(): Remove.
row_log_table_delete(), row_log_table_update(, row_log_table_insert():
Remove parameters for virtual columns.
trx_undo_read_v_rows(): Remove the col_map parameter.
row_log_table_apply(): Do not deal with virtual columns.
2017-11-09 23:01:44 +02:00
|
|
|
row_log_table_low(rec, index, offsets, false, old_pk);
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
/** Gets the old table column of a PRIMARY KEY column.
|
2016-08-12 11:17:45 +03:00
|
|
|
@param table old table (before ALTER TABLE)
|
|
|
|
@param col_map mapping of old column numbers to new ones
|
|
|
|
@param col_no column position in the new table
|
2014-02-01 09:33:26 +01:00
|
|
|
@return old table column, or NULL if this is an added column */
|
|
|
|
static
|
|
|
|
const dict_col_t*
|
|
|
|
row_log_table_get_pk_old_col(
|
|
|
|
/*=========================*/
|
|
|
|
const dict_table_t* table,
|
|
|
|
const ulint* col_map,
|
|
|
|
ulint col_no)
|
|
|
|
{
|
|
|
|
for (ulint i = 0; i < table->n_cols; i++) {
|
|
|
|
if (col_no == col_map[i]) {
|
|
|
|
return(dict_table_get_nth_col(table, i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Maps an old table column of a PRIMARY KEY column.
|
2016-08-12 11:17:45 +03:00
|
|
|
@param[in] ifield clustered index field in the new table (after
|
|
|
|
ALTER TABLE)
|
2020-02-05 11:12:10 +02:00
|
|
|
@param[in] index the clustered index of ifield
|
2016-08-12 11:17:45 +03:00
|
|
|
@param[in,out] dfield clustered index tuple field in the new table
|
|
|
|
@param[in,out] heap memory heap for allocating dfield contents
|
|
|
|
@param[in] rec clustered index leaf page record in the old
|
|
|
|
table
|
|
|
|
@param[in] offsets rec_get_offsets(rec)
|
|
|
|
@param[in] i rec field corresponding to col
|
2019-02-06 19:50:11 +02:00
|
|
|
@param[in] zip_size ROW_FORMAT=COMPRESSED size of the old table
|
2016-08-12 11:17:45 +03:00
|
|
|
@param[in] max_len maximum length of dfield
|
2018-06-25 14:52:38 +05:30
|
|
|
@param[in] log row log for the table
|
2016-08-12 11:17:45 +03:00
|
|
|
@retval DB_INVALID_NULL if a NULL value is encountered
|
|
|
|
@retval DB_TOO_BIG_INDEX_COL if the maximum prefix length is exceeded */
|
2014-02-01 09:33:26 +01:00
|
|
|
static
|
|
|
|
dberr_t
|
|
|
|
row_log_table_get_pk_col(
|
|
|
|
const dict_field_t* ifield,
|
2020-02-05 11:12:10 +02:00
|
|
|
const dict_index_t* index,
|
2014-02-01 09:33:26 +01:00
|
|
|
dfield_t* dfield,
|
|
|
|
mem_heap_t* heap,
|
|
|
|
const rec_t* rec,
|
2020-04-28 10:46:51 +10:00
|
|
|
const rec_offs* offsets,
|
2014-02-01 09:33:26 +01:00
|
|
|
ulint i,
|
2019-02-06 19:50:11 +02:00
|
|
|
ulint zip_size,
|
2018-04-24 13:15:35 +05:30
|
|
|
ulint max_len,
|
2018-06-25 14:52:38 +05:30
|
|
|
const row_log_t* log)
|
2014-02-01 09:33:26 +01:00
|
|
|
{
|
|
|
|
const byte* field;
|
|
|
|
ulint len;
|
|
|
|
|
|
|
|
field = rec_get_nth_field(rec, offsets, i, &len);
|
|
|
|
|
2019-11-15 21:49:04 +07:00
|
|
|
if (len == UNIV_SQL_DEFAULT) {
|
|
|
|
field = log->instant_field_value(i, &len);
|
|
|
|
}
|
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
if (len == UNIV_SQL_NULL) {
|
2018-06-25 14:52:38 +05:30
|
|
|
if (!log->allow_not_null) {
|
2018-04-24 13:15:35 +05:30
|
|
|
return(DB_INVALID_NULL);
|
|
|
|
}
|
|
|
|
|
2020-05-25 21:42:26 +05:30
|
|
|
unsigned col_no= ifield->col->ind;
|
|
|
|
ut_ad(col_no < log->defaults->n_fields);
|
2018-06-25 14:52:38 +05:30
|
|
|
|
|
|
|
field = static_cast<const byte*>(
|
2020-05-25 21:42:26 +05:30
|
|
|
log->defaults->fields[col_no].data);
|
2018-08-14 19:34:33 +03:00
|
|
|
if (!field) {
|
|
|
|
return(DB_INVALID_NULL);
|
|
|
|
}
|
2020-05-25 21:42:26 +05:30
|
|
|
len = log->defaults->fields[col_no].len;
|
2014-02-01 09:33:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (rec_offs_nth_extern(offsets, i)) {
|
|
|
|
ulint field_len = ifield->prefix_len;
|
|
|
|
byte* blob_field;
|
|
|
|
|
|
|
|
if (!field_len) {
|
|
|
|
field_len = ifield->fixed_len;
|
|
|
|
if (!field_len) {
|
|
|
|
field_len = max_len + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
blob_field = static_cast<byte*>(
|
|
|
|
mem_heap_alloc(heap, field_len));
|
|
|
|
|
|
|
|
len = btr_copy_externally_stored_field_prefix(
|
2019-02-06 19:50:11 +02:00
|
|
|
blob_field, field_len, zip_size, field, len);
|
2014-02-01 09:33:26 +01:00
|
|
|
if (len >= max_len + 1) {
|
|
|
|
return(DB_TOO_BIG_INDEX_COL);
|
|
|
|
}
|
|
|
|
|
|
|
|
dfield_set_data(dfield, blob_field, len);
|
|
|
|
} else {
|
|
|
|
dfield_set_data(dfield, mem_heap_dup(heap, field, len), len);
|
|
|
|
}
|
|
|
|
|
|
|
|
return(DB_SUCCESS);
|
|
|
|
}
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
/******************************************************//**
|
|
|
|
Constructs the old PRIMARY KEY and DB_TRX_ID,DB_ROLL_PTR
|
|
|
|
of a table that is being rebuilt.
|
|
|
|
@return tuple of PRIMARY KEY,DB_TRX_ID,DB_ROLL_PTR in the rebuilt table,
|
|
|
|
or NULL if the PRIMARY KEY definition does not change */
|
|
|
|
const dtuple_t*
|
|
|
|
row_log_table_get_pk(
|
|
|
|
/*=================*/
|
|
|
|
const rec_t* rec, /*!< in: clustered index leaf page record,
|
|
|
|
page X-latched */
|
|
|
|
dict_index_t* index, /*!< in/out: clustered index, S-latched
|
|
|
|
or X-latched */
|
2020-04-28 10:46:51 +10:00
|
|
|
const rec_offs* offsets,/*!< in: rec_get_offsets(rec,index) */
|
2014-05-06 21:13:16 +02:00
|
|
|
byte* sys, /*!< out: DB_TRX_ID,DB_ROLL_PTR for
|
|
|
|
row_log_table_delete(), or NULL */
|
2013-03-26 00:03:13 +02:00
|
|
|
mem_heap_t** heap) /*!< in/out: memory heap where allocated */
|
|
|
|
{
|
|
|
|
dtuple_t* tuple = NULL;
|
|
|
|
row_log_t* log = index->online_log;
|
|
|
|
|
|
|
|
ut_ad(dict_index_is_clust(index));
|
|
|
|
ut_ad(dict_index_is_online_ddl(index));
|
|
|
|
ut_ad(!offsets || rec_offs_validate(rec, index, offsets));
|
2016-08-12 11:17:45 +03:00
|
|
|
ut_ad(rw_lock_own_flagged(
|
|
|
|
&index->lock,
|
|
|
|
RW_LOCK_FLAG_S | RW_LOCK_FLAG_X | RW_LOCK_FLAG_SX));
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
ut_ad(log);
|
|
|
|
ut_ad(log->table);
|
MDEV-13654 Various crashes due to DB_TRX_ID mismatch in table-rebuilding ALTER TABLE…LOCK=NONE
After MDEV-12288 and MDEV-13536, the DB_TRX_ID of old clustered index
records for which no history is available should be reset to 0.
This caused crashes in online table-rebuilding ALTER, because the
row_log_table_apply() is built on the assumption that the PRIMARY KEY
together with DB_TRX_ID,DB_ROLL_PTR identifies the record.
Both when copying the old table and when writing log about changes to
the old table, we must map "old" DB_TRX_ID to 0. "old" here is simply
"older than the trx_id of the ALTER TABLE transaction", because
the MDL_EXCLUSIVE (and exclusive InnoDB table lock) in
ha_innobase::prepare_inplace_alter_table() forces any transactions
accessing the table to commit or rollback. So, we know that we can
safely reset any DB_TRX_ID in the table that is older than the
transaction ID of the ALTER TABLE, because the undo log history would be
lost in a table-rebuilding ALTER.
Note: After a table-rebuilding online ALTER TABLE, the rebuilt table
may end up containing some nonzero DB_TRX_ID columns. The apply logic
identifies the rows by the combination of PRIMARY KEY and DB_TRX_ID.
These nonzero DB_TRX_ID would necessarily refer to concurrent DML
operations that were started during ha_innobase::inplace_alter_table().
row_log_allocate(): Add a parameter for the ALTER TABLE transaction.
row_log_t::min_trx: The ALTER TABLE transaction ID.
trx_id_check(): A debug function to check that DB_TRX_ID makes sense
(is either 0 or bigger than the ALTER TABLE transaction ID).
reset_trx_id[]: The reset DB_TRX_ID,DB_ROLL_PTR columns.
row_log_table_delete(), row_log_table_get_pk(): Reset the
DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE transaction.
row_log_table_apply_delete(), row_log_table_apply_update():
Assert trx_id_check().
row_merge_insert_index_tuples(): Remove the unused parameter trx_id.
row_merge_read_clustered_index(): In a table-rebuilding ALTER,
reset the DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE
transaction. Assert trx_id_check() on clustered index records that
are being buffered.
2017-08-31 18:03:50 +03:00
|
|
|
ut_ad(log->min_trx);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
if (log->same_pk) {
|
|
|
|
/* The PRIMARY KEY columns are unchanged. */
|
2014-05-06 21:13:16 +02:00
|
|
|
if (sys) {
|
|
|
|
/* Store the DB_TRX_ID,DB_ROLL_PTR. */
|
|
|
|
ulint trx_id_offs = index->trx_id_offset;
|
|
|
|
|
|
|
|
if (!trx_id_offs) {
|
|
|
|
ulint len;
|
|
|
|
|
|
|
|
if (!offsets) {
|
|
|
|
offsets = rec_get_offsets(
|
2021-04-14 10:33:59 +03:00
|
|
|
rec, index, nullptr,
|
2021-04-13 10:28:13 +03:00
|
|
|
index->n_core_fields,
|
2018-11-16 19:36:04 +02:00
|
|
|
index->db_trx_id() + 1, heap);
|
2014-05-06 21:13:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
trx_id_offs = rec_get_nth_field_offs(
|
2018-11-16 19:36:04 +02:00
|
|
|
offsets, index->db_trx_id(), &len);
|
2014-05-06 21:13:16 +02:00
|
|
|
ut_ad(len == DATA_TRX_ID_LEN);
|
|
|
|
}
|
|
|
|
|
MDEV-13654 Various crashes due to DB_TRX_ID mismatch in table-rebuilding ALTER TABLE…LOCK=NONE
After MDEV-12288 and MDEV-13536, the DB_TRX_ID of old clustered index
records for which no history is available should be reset to 0.
This caused crashes in online table-rebuilding ALTER, because the
row_log_table_apply() is built on the assumption that the PRIMARY KEY
together with DB_TRX_ID,DB_ROLL_PTR identifies the record.
Both when copying the old table and when writing log about changes to
the old table, we must map "old" DB_TRX_ID to 0. "old" here is simply
"older than the trx_id of the ALTER TABLE transaction", because
the MDL_EXCLUSIVE (and exclusive InnoDB table lock) in
ha_innobase::prepare_inplace_alter_table() forces any transactions
accessing the table to commit or rollback. So, we know that we can
safely reset any DB_TRX_ID in the table that is older than the
transaction ID of the ALTER TABLE, because the undo log history would be
lost in a table-rebuilding ALTER.
Note: After a table-rebuilding online ALTER TABLE, the rebuilt table
may end up containing some nonzero DB_TRX_ID columns. The apply logic
identifies the rows by the combination of PRIMARY KEY and DB_TRX_ID.
These nonzero DB_TRX_ID would necessarily refer to concurrent DML
operations that were started during ha_innobase::inplace_alter_table().
row_log_allocate(): Add a parameter for the ALTER TABLE transaction.
row_log_t::min_trx: The ALTER TABLE transaction ID.
trx_id_check(): A debug function to check that DB_TRX_ID makes sense
(is either 0 or bigger than the ALTER TABLE transaction ID).
reset_trx_id[]: The reset DB_TRX_ID,DB_ROLL_PTR columns.
row_log_table_delete(), row_log_table_get_pk(): Reset the
DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE transaction.
row_log_table_apply_delete(), row_log_table_apply_update():
Assert trx_id_check().
row_merge_insert_index_tuples(): Remove the unused parameter trx_id.
row_merge_read_clustered_index(): In a table-rebuilding ALTER,
reset the DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE
transaction. Assert trx_id_check() on clustered index records that
are being buffered.
2017-08-31 18:03:50 +03:00
|
|
|
const byte* ptr = trx_read_trx_id(rec + trx_id_offs)
|
|
|
|
< log->min_trx
|
|
|
|
? reset_trx_id
|
|
|
|
: rec + trx_id_offs;
|
|
|
|
|
|
|
|
memcpy(sys, ptr, DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN);
|
|
|
|
ut_d(trx_id_check(sys, log->min_trx));
|
2014-05-06 21:13:16 +02:00
|
|
|
}
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_enter(&log->mutex);
|
|
|
|
|
|
|
|
/* log->error is protected by log->mutex. */
|
|
|
|
if (log->error == DB_SUCCESS) {
|
|
|
|
dict_table_t* new_table = log->table;
|
|
|
|
dict_index_t* new_index
|
|
|
|
= dict_table_get_first_index(new_table);
|
|
|
|
const ulint new_n_uniq
|
|
|
|
= dict_index_get_n_unique(new_index);
|
|
|
|
|
|
|
|
if (!*heap) {
|
|
|
|
ulint size = 0;
|
|
|
|
|
|
|
|
if (!offsets) {
|
|
|
|
size += (1 + REC_OFFS_HEADER_SIZE
|
2018-04-28 15:49:09 +03:00
|
|
|
+ unsigned(index->n_fields))
|
2013-03-26 00:03:13 +02:00
|
|
|
* sizeof *offsets;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (ulint i = 0; i < new_n_uniq; i++) {
|
|
|
|
size += dict_col_get_min_size(
|
|
|
|
dict_index_get_nth_col(new_index, i));
|
|
|
|
}
|
|
|
|
|
|
|
|
*heap = mem_heap_create(
|
|
|
|
DTUPLE_EST_ALLOC(new_n_uniq + 2) + size);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!offsets) {
|
2021-04-14 10:33:59 +03:00
|
|
|
offsets = rec_get_offsets(rec, index, nullptr,
|
2021-04-13 10:28:13 +03:00
|
|
|
index->n_core_fields,
|
2013-03-26 00:03:13 +02:00
|
|
|
ULINT_UNDEFINED, heap);
|
|
|
|
}
|
|
|
|
|
|
|
|
tuple = dtuple_create(*heap, new_n_uniq + 2);
|
|
|
|
dict_index_copy_types(tuple, new_index, tuple->n_fields);
|
|
|
|
dtuple_set_n_fields_cmp(tuple, new_n_uniq);
|
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
const ulint max_len = DICT_MAX_FIELD_LEN_BY_FORMAT(new_table);
|
2016-08-12 11:17:45 +03:00
|
|
|
|
2019-02-06 19:50:11 +02:00
|
|
|
const ulint zip_size = index->table->space->zip_size();
|
2014-02-01 09:33:26 +01:00
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
for (ulint new_i = 0; new_i < new_n_uniq; new_i++) {
|
2014-02-01 09:33:26 +01:00
|
|
|
dict_field_t* ifield;
|
|
|
|
dfield_t* dfield;
|
|
|
|
ulint prtype;
|
2018-01-24 12:29:31 +02:00
|
|
|
ulint mbminlen, mbmaxlen;
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
ifield = dict_index_get_nth_field(new_index, new_i);
|
|
|
|
dfield = dtuple_get_nth_field(tuple, new_i);
|
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
const ulint col_no
|
|
|
|
= dict_field_get_col(ifield)->ind;
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
if (const dict_col_t* col
|
|
|
|
= row_log_table_get_pk_old_col(
|
|
|
|
index->table, log->col_map, col_no)) {
|
|
|
|
ulint i = dict_col_get_clust_pos(col, index);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
if (i == ULINT_UNDEFINED) {
|
|
|
|
ut_ad(0);
|
|
|
|
log->error = DB_CORRUPTION;
|
|
|
|
goto err_exit;
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
log->error = row_log_table_get_pk_col(
|
2020-02-05 11:12:10 +02:00
|
|
|
ifield, new_index, dfield, *heap,
|
2019-02-06 19:50:11 +02:00
|
|
|
rec, offsets, i, zip_size, max_len,
|
|
|
|
log);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
if (log->error != DB_SUCCESS) {
|
|
|
|
err_exit:
|
2013-03-26 00:03:13 +02:00
|
|
|
tuple = NULL;
|
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
|
2018-01-24 12:29:31 +02:00
|
|
|
mbminlen = col->mbminlen;
|
|
|
|
mbmaxlen = col->mbmaxlen;
|
2014-02-01 09:33:26 +01:00
|
|
|
prtype = col->prtype;
|
2013-03-26 00:03:13 +02:00
|
|
|
} else {
|
2014-02-01 09:33:26 +01:00
|
|
|
/* No matching column was found in the old
|
|
|
|
table, so this must be an added column.
|
|
|
|
Copy the default value. */
|
2018-04-24 13:15:35 +05:30
|
|
|
ut_ad(log->defaults);
|
2014-02-01 09:33:26 +01:00
|
|
|
|
|
|
|
dfield_copy(dfield, dtuple_get_nth_field(
|
2018-04-24 13:15:35 +05:30
|
|
|
log->defaults, col_no));
|
2018-01-24 12:29:31 +02:00
|
|
|
mbminlen = dfield->type.mbminlen;
|
|
|
|
mbmaxlen = dfield->type.mbmaxlen;
|
2014-02-01 09:33:26 +01:00
|
|
|
prtype = dfield->type.prtype;
|
|
|
|
}
|
|
|
|
|
|
|
|
ut_ad(!dfield_is_ext(dfield));
|
|
|
|
ut_ad(!dfield_is_null(dfield));
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
if (ifield->prefix_len) {
|
|
|
|
ulint len = dtype_get_at_most_n_mbchars(
|
2018-01-24 12:29:31 +02:00
|
|
|
prtype, mbminlen, mbmaxlen,
|
2014-02-01 09:33:26 +01:00
|
|
|
ifield->prefix_len,
|
|
|
|
dfield_get_len(dfield),
|
|
|
|
static_cast<const char*>(
|
|
|
|
dfield_get_data(dfield)));
|
|
|
|
|
|
|
|
ut_ad(len <= dfield_get_len(dfield));
|
|
|
|
dfield_set_len(dfield, len);
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const byte* trx_roll = rec
|
|
|
|
+ row_get_trx_id_offset(index, offsets);
|
|
|
|
|
2014-05-06 21:13:16 +02:00
|
|
|
/* Copy the fields, because the fields will be updated
|
|
|
|
or the record may be moved somewhere else in the B-tree
|
|
|
|
as part of the upcoming operation. */
|
MDEV-13654 Various crashes due to DB_TRX_ID mismatch in table-rebuilding ALTER TABLE…LOCK=NONE
After MDEV-12288 and MDEV-13536, the DB_TRX_ID of old clustered index
records for which no history is available should be reset to 0.
This caused crashes in online table-rebuilding ALTER, because the
row_log_table_apply() is built on the assumption that the PRIMARY KEY
together with DB_TRX_ID,DB_ROLL_PTR identifies the record.
Both when copying the old table and when writing log about changes to
the old table, we must map "old" DB_TRX_ID to 0. "old" here is simply
"older than the trx_id of the ALTER TABLE transaction", because
the MDL_EXCLUSIVE (and exclusive InnoDB table lock) in
ha_innobase::prepare_inplace_alter_table() forces any transactions
accessing the table to commit or rollback. So, we know that we can
safely reset any DB_TRX_ID in the table that is older than the
transaction ID of the ALTER TABLE, because the undo log history would be
lost in a table-rebuilding ALTER.
Note: After a table-rebuilding online ALTER TABLE, the rebuilt table
may end up containing some nonzero DB_TRX_ID columns. The apply logic
identifies the rows by the combination of PRIMARY KEY and DB_TRX_ID.
These nonzero DB_TRX_ID would necessarily refer to concurrent DML
operations that were started during ha_innobase::inplace_alter_table().
row_log_allocate(): Add a parameter for the ALTER TABLE transaction.
row_log_t::min_trx: The ALTER TABLE transaction ID.
trx_id_check(): A debug function to check that DB_TRX_ID makes sense
(is either 0 or bigger than the ALTER TABLE transaction ID).
reset_trx_id[]: The reset DB_TRX_ID,DB_ROLL_PTR columns.
row_log_table_delete(), row_log_table_get_pk(): Reset the
DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE transaction.
row_log_table_apply_delete(), row_log_table_apply_update():
Assert trx_id_check().
row_merge_insert_index_tuples(): Remove the unused parameter trx_id.
row_merge_read_clustered_index(): In a table-rebuilding ALTER,
reset the DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE
transaction. Assert trx_id_check() on clustered index records that
are being buffered.
2017-08-31 18:03:50 +03:00
|
|
|
if (trx_read_trx_id(trx_roll) < log->min_trx) {
|
|
|
|
trx_roll = reset_trx_id;
|
|
|
|
if (sys) {
|
|
|
|
memcpy(sys, trx_roll,
|
|
|
|
DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN);
|
|
|
|
}
|
|
|
|
} else if (sys) {
|
2014-05-06 21:13:16 +02:00
|
|
|
memcpy(sys, trx_roll,
|
|
|
|
DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN);
|
|
|
|
trx_roll = sys;
|
|
|
|
} else {
|
|
|
|
trx_roll = static_cast<const byte*>(
|
|
|
|
mem_heap_dup(
|
|
|
|
*heap, trx_roll,
|
|
|
|
DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN));
|
|
|
|
}
|
|
|
|
|
MDEV-13654 Various crashes due to DB_TRX_ID mismatch in table-rebuilding ALTER TABLE…LOCK=NONE
After MDEV-12288 and MDEV-13536, the DB_TRX_ID of old clustered index
records for which no history is available should be reset to 0.
This caused crashes in online table-rebuilding ALTER, because the
row_log_table_apply() is built on the assumption that the PRIMARY KEY
together with DB_TRX_ID,DB_ROLL_PTR identifies the record.
Both when copying the old table and when writing log about changes to
the old table, we must map "old" DB_TRX_ID to 0. "old" here is simply
"older than the trx_id of the ALTER TABLE transaction", because
the MDL_EXCLUSIVE (and exclusive InnoDB table lock) in
ha_innobase::prepare_inplace_alter_table() forces any transactions
accessing the table to commit or rollback. So, we know that we can
safely reset any DB_TRX_ID in the table that is older than the
transaction ID of the ALTER TABLE, because the undo log history would be
lost in a table-rebuilding ALTER.
Note: After a table-rebuilding online ALTER TABLE, the rebuilt table
may end up containing some nonzero DB_TRX_ID columns. The apply logic
identifies the rows by the combination of PRIMARY KEY and DB_TRX_ID.
These nonzero DB_TRX_ID would necessarily refer to concurrent DML
operations that were started during ha_innobase::inplace_alter_table().
row_log_allocate(): Add a parameter for the ALTER TABLE transaction.
row_log_t::min_trx: The ALTER TABLE transaction ID.
trx_id_check(): A debug function to check that DB_TRX_ID makes sense
(is either 0 or bigger than the ALTER TABLE transaction ID).
reset_trx_id[]: The reset DB_TRX_ID,DB_ROLL_PTR columns.
row_log_table_delete(), row_log_table_get_pk(): Reset the
DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE transaction.
row_log_table_apply_delete(), row_log_table_apply_update():
Assert trx_id_check().
row_merge_insert_index_tuples(): Remove the unused parameter trx_id.
row_merge_read_clustered_index(): In a table-rebuilding ALTER,
reset the DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE
transaction. Assert trx_id_check() on clustered index records that
are being buffered.
2017-08-31 18:03:50 +03:00
|
|
|
ut_d(trx_id_check(trx_roll, log->min_trx));
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
dfield_set_data(dtuple_get_nth_field(tuple, new_n_uniq),
|
|
|
|
trx_roll, DATA_TRX_ID_LEN);
|
|
|
|
dfield_set_data(dtuple_get_nth_field(tuple, new_n_uniq + 1),
|
|
|
|
trx_roll + DATA_TRX_ID_LEN, DATA_ROLL_PTR_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
func_exit:
|
|
|
|
mutex_exit(&log->mutex);
|
|
|
|
return(tuple);
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************//**
|
|
|
|
Logs an insert to a table that is being rebuilt.
|
|
|
|
This will be merged in row_log_table_apply_insert(). */
|
|
|
|
void
|
|
|
|
row_log_table_insert(
|
|
|
|
/*=================*/
|
|
|
|
const rec_t* rec, /*!< in: clustered index leaf page record,
|
|
|
|
page X-latched */
|
|
|
|
dict_index_t* index, /*!< in/out: clustered index, S-latched
|
|
|
|
or X-latched */
|
2020-04-28 10:46:51 +10:00
|
|
|
const rec_offs* offsets)/*!< in: rec_get_offsets(rec,index) */
|
2013-03-26 00:03:13 +02:00
|
|
|
{
|
MDEV-13795/MDEV-14332 Corruption during online table-rebuilding ALTER when VIRTUAL columns exist
When MySQL 5.7 introduced indexed virtual columns, it introduced
several bugs into the online table-rebuilding ALTER, that is,
the row_log_table_apply() family of functions.
The online_log format that was introduced for online table-rebuilding
ALTER in MySQL 5.6 should be sufficient. Ideally, any indexed virtual
column values would be evaluated based on the log records in the temporary
file. There is no need to log virtual column values.
(For ADD INDEX, that is row_log_apply(), we always must log the values of
the keys, no matter if the columns are virtual.)
Because omitting the virtual column values removes any chance of
row_log_table_apply() working with indexed virtual columns, we
will for now refuse LOCK=NONE in table-rebuilding ALTER operations
when indexes on virtual columns exist. This restriction would be
lifted in MDEV-14341.
innobase_indexed_virtual_exist(): New predicate, to determine if
indexed virtual columns exist in a table definition.
ha_innobase::check_if_supported_inplace_alter(): Refuse online rebuild
if indexed virtual columns exist.
rec_get_converted_size_temp_v(), rec_convert_dtuple_to_temp_v(): Remove.
row_log_table_delete(), row_log_table_update(, row_log_table_insert():
Remove parameters for virtual columns.
trx_undo_read_v_rows(): Remove the col_map parameter.
row_log_table_apply(): Do not deal with virtual columns.
2017-11-09 23:01:44 +02:00
|
|
|
row_log_table_low(rec, index, offsets, true, NULL);
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************//**
|
2014-02-01 09:33:26 +01:00
|
|
|
Notes that a BLOB is being freed during online ALTER TABLE. */
|
2013-03-26 00:03:13 +02:00
|
|
|
void
|
2014-02-01 09:33:26 +01:00
|
|
|
row_log_table_blob_free(
|
|
|
|
/*====================*/
|
|
|
|
dict_index_t* index, /*!< in/out: clustered index, X-latched */
|
|
|
|
ulint page_no)/*!< in: starting page number of the BLOB */
|
2013-03-26 00:03:13 +02:00
|
|
|
{
|
|
|
|
ut_ad(dict_index_is_clust(index));
|
2014-02-01 09:33:26 +01:00
|
|
|
ut_ad(dict_index_is_online_ddl(index));
|
2016-08-12 11:17:45 +03:00
|
|
|
ut_ad(rw_lock_own_flagged(
|
|
|
|
&index->lock,
|
|
|
|
RW_LOCK_FLAG_X | RW_LOCK_FLAG_SX));
|
2014-02-01 09:33:26 +01:00
|
|
|
ut_ad(page_no != FIL_NULL);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
if (index->online_log->error != DB_SUCCESS) {
|
|
|
|
return;
|
|
|
|
}
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
page_no_map* blobs = index->online_log->blobs;
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
if (blobs == NULL) {
|
|
|
|
index->online_log->blobs = blobs = UT_NEW_NOKEY(page_no_map());
|
2014-02-01 09:33:26 +01:00
|
|
|
}
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
#ifdef UNIV_DEBUG
|
|
|
|
const ulonglong log_pos = index->online_log->tail.total;
|
|
|
|
#else
|
|
|
|
# define log_pos /* empty */
|
|
|
|
#endif /* UNIV_DEBUG */
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
const page_no_map::value_type v(page_no,
|
|
|
|
row_log_table_blob_t(log_pos));
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
std::pair<page_no_map::iterator,bool> p = blobs->insert(v);
|
|
|
|
|
|
|
|
if (!p.second) {
|
|
|
|
/* Update the existing mapping. */
|
|
|
|
ut_ad(p.first->first == page_no);
|
|
|
|
p.first->second.blob_free(log_pos);
|
|
|
|
}
|
|
|
|
#undef log_pos
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************//**
|
2014-02-01 09:33:26 +01:00
|
|
|
Notes that a BLOB is being allocated during online ALTER TABLE. */
|
|
|
|
void
|
|
|
|
row_log_table_blob_alloc(
|
|
|
|
/*=====================*/
|
|
|
|
dict_index_t* index, /*!< in/out: clustered index, X-latched */
|
|
|
|
ulint page_no)/*!< in: starting page number of the BLOB */
|
2013-03-26 00:03:13 +02:00
|
|
|
{
|
|
|
|
ut_ad(dict_index_is_clust(index));
|
|
|
|
ut_ad(dict_index_is_online_ddl(index));
|
2016-08-12 11:17:45 +03:00
|
|
|
|
|
|
|
ut_ad(rw_lock_own_flagged(
|
|
|
|
&index->lock,
|
|
|
|
RW_LOCK_FLAG_X | RW_LOCK_FLAG_SX));
|
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
ut_ad(page_no != FIL_NULL);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
if (index->online_log->error != DB_SUCCESS) {
|
|
|
|
return;
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
/* Only track allocations if the same page has been freed
|
|
|
|
earlier. Double allocation without a free is not allowed. */
|
|
|
|
if (page_no_map* blobs = index->online_log->blobs) {
|
|
|
|
page_no_map::iterator p = blobs->find(page_no);
|
|
|
|
|
|
|
|
if (p != blobs->end()) {
|
|
|
|
ut_ad(p->first == page_no);
|
|
|
|
p->second.blob_alloc(index->online_log->tail.total);
|
|
|
|
}
|
|
|
|
}
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************//**
|
|
|
|
Converts a log record to a table row.
|
2014-02-26 19:23:04 +01:00
|
|
|
@return converted row, or NULL if the conversion fails */
|
2016-06-21 14:21:03 +02:00
|
|
|
static MY_ATTRIBUTE((nonnull, warn_unused_result))
|
2013-03-26 00:03:13 +02:00
|
|
|
const dtuple_t*
|
|
|
|
row_log_table_apply_convert_mrec(
|
|
|
|
/*=============================*/
|
|
|
|
const mrec_t* mrec, /*!< in: merge record */
|
|
|
|
dict_index_t* index, /*!< in: index of mrec */
|
2020-04-28 10:46:51 +10:00
|
|
|
const rec_offs* offsets, /*!< in: offsets of mrec */
|
2018-06-25 18:16:29 +05:30
|
|
|
row_log_t* log, /*!< in: rebuild context */
|
2013-03-26 00:03:13 +02:00
|
|
|
mem_heap_t* heap, /*!< in/out: memory heap */
|
|
|
|
dberr_t* error) /*!< out: DB_SUCCESS or
|
2014-05-06 21:13:16 +02:00
|
|
|
DB_MISSING_HISTORY or
|
2013-03-26 00:03:13 +02:00
|
|
|
reason of failure */
|
|
|
|
{
|
|
|
|
dtuple_t* row;
|
|
|
|
|
2018-06-25 18:16:29 +05:30
|
|
|
log->n_rows++;
|
2014-05-06 21:13:16 +02:00
|
|
|
*error = DB_SUCCESS;
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
/* This is based on row_build(). */
|
2018-04-24 13:15:35 +05:30
|
|
|
if (log->defaults) {
|
|
|
|
row = dtuple_copy(log->defaults, heap);
|
2013-03-26 00:03:13 +02:00
|
|
|
/* dict_table_copy_types() would set the fields to NULL */
|
|
|
|
for (ulint i = 0; i < dict_table_get_n_cols(log->table); i++) {
|
|
|
|
dict_col_copy_type(
|
|
|
|
dict_table_get_nth_col(log->table, i),
|
|
|
|
dfield_get_type(dtuple_get_nth_field(row, i)));
|
|
|
|
}
|
|
|
|
} else {
|
MDEV-13795/MDEV-14332 Corruption during online table-rebuilding ALTER when VIRTUAL columns exist
When MySQL 5.7 introduced indexed virtual columns, it introduced
several bugs into the online table-rebuilding ALTER, that is,
the row_log_table_apply() family of functions.
The online_log format that was introduced for online table-rebuilding
ALTER in MySQL 5.6 should be sufficient. Ideally, any indexed virtual
column values would be evaluated based on the log records in the temporary
file. There is no need to log virtual column values.
(For ADD INDEX, that is row_log_apply(), we always must log the values of
the keys, no matter if the columns are virtual.)
Because omitting the virtual column values removes any chance of
row_log_table_apply() working with indexed virtual columns, we
will for now refuse LOCK=NONE in table-rebuilding ALTER operations
when indexes on virtual columns exist. This restriction would be
lifted in MDEV-14341.
innobase_indexed_virtual_exist(): New predicate, to determine if
indexed virtual columns exist in a table definition.
ha_innobase::check_if_supported_inplace_alter(): Refuse online rebuild
if indexed virtual columns exist.
rec_get_converted_size_temp_v(), rec_convert_dtuple_to_temp_v(): Remove.
row_log_table_delete(), row_log_table_update(, row_log_table_insert():
Remove parameters for virtual columns.
trx_undo_read_v_rows(): Remove the col_map parameter.
row_log_table_apply(): Do not deal with virtual columns.
2017-11-09 23:01:44 +02:00
|
|
|
row = dtuple_create(heap, dict_table_get_n_cols(log->table));
|
2013-03-26 00:03:13 +02:00
|
|
|
dict_table_copy_types(row, log->table);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (ulint i = 0; i < rec_offs_n_fields(offsets); i++) {
|
|
|
|
const dict_field_t* ind_field
|
|
|
|
= dict_index_get_nth_field(index, i);
|
|
|
|
|
|
|
|
if (ind_field->prefix_len) {
|
|
|
|
/* Column prefixes can only occur in key
|
|
|
|
fields, which cannot be stored externally. For
|
|
|
|
a column prefix, there should also be the full
|
|
|
|
field in the clustered index tuple. The row
|
|
|
|
tuple comprises full fields, not prefixes. */
|
|
|
|
ut_ad(!rec_offs_nth_extern(offsets, i));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const dict_col_t* col
|
|
|
|
= dict_field_get_col(ind_field);
|
2016-08-12 11:17:45 +03:00
|
|
|
|
MDEV-15662 Instant DROP COLUMN or changing the order of columns
Allow ADD COLUMN anywhere in a table, not only adding as the
last column.
Allow instant DROP COLUMN and instant changing the order of columns.
The added columns will always be added last in clustered index records.
In new records, instantly dropped columns will be stored as NULL or
empty when possible.
Information about dropped and reordered columns will be written in
a metadata BLOB (mblob), which is stored before the first 'user' field
in the hidden metadata record at the start of the clustered index.
The presence of mblob is indicated by setting the delete-mark flag in
the metadata record.
The metadata BLOB stores the number of clustered index fields,
followed by an array of column information for each field.
For dropped columns, we store the NOT NULL flag, the fixed length,
and for variable-length columns, whether the maximum length exceeded
255 bytes. For non-dropped columns, we store the column position.
Unlike with MDEV-11369, when a table becomes empty, it cannot
be converted back to the canonical format. The reason for this is
that other threads may hold cached objects such as
row_prebuilt_t::ins_node that could refer to dropped or reordered
index fields.
For instant DROP COLUMN and ROW_FORMAT=COMPACT or ROW_FORMAT=DYNAMIC,
we must store the n_core_null_bytes in the root page, so that the
chain of node pointer records can be followed in order to reach the
leftmost leaf page where the metadata record is located.
If the mblob is present, we will zero-initialize the strings
"infimum" and "supremum" in the root page, and use the last byte of
"supremum" for storing the number of null bytes (which are allocated
but useless on node pointer pages). This is necessary for
btr_cur_instant_init_metadata() to be able to navigate to the mblob.
If the PRIMARY KEY contains any variable-length column and some
nullable columns were instantly dropped, the dict_index_t::n_nullable
in the data dictionary could be smaller than it actually is in the
non-leaf pages. Because of this, the non-leaf pages could use more
bytes for the null flags than the data dictionary expects, and we
could be reading the lengths of the variable-length columns from the
wrong offset, and thus reading the child page number from wrong place.
This is the result of two design mistakes that involve unnecessary
storage of data: First, it is nonsense to store any data fields for
the leftmost node pointer records, because the comparisons would be
resolved by the MIN_REC_FLAG alone. Second, there cannot be any null
fields in the clustered index node pointer fields, but we nevertheless
reserve space for all the null flags.
Limitations (future work):
MDEV-17459 Allow instant ALTER TABLE even if FULLTEXT INDEX exists
MDEV-17468 Avoid table rebuild on operations on generated columns
MDEV-17494 Refuse ALGORITHM=INSTANT when the row size is too large
btr_page_reorganize_low(): Preserve any metadata in the root page.
Call lock_move_reorganize_page() only after restoring the "infimum"
and "supremum" records, to avoid a memcmp() assertion failure.
dict_col_t::DROPPED: Magic value for dict_col_t::ind.
dict_col_t::clear_instant(): Renamed from dict_col_t::remove_instant().
Do not assert that the column was instantly added, because we
sometimes call this unconditionally for all columns.
Convert an instantly added column to a "core column". The old name
remove_instant() could be mistaken to refer to "instant DROP COLUMN".
dict_col_t::is_added(): Rename from dict_col_t::is_instant().
dtype_t::metadata_blob_init(): Initialize the mblob data type.
dtuple_t::is_metadata(), dtuple_t::is_alter_metadata(),
upd_t::is_metadata(), upd_t::is_alter_metadata(): Check if info_bits
refer to a metadata record.
dict_table_t::instant: Metadata about dropped or reordered columns.
dict_table_t::prepare_instant(): Prepare
ha_innobase_inplace_ctx::instant_table for instant ALTER TABLE.
innobase_instant_try() will pass this to dict_table_t::instant_column().
On rollback, dict_table_t::rollback_instant() will be called.
dict_table_t::instant_column(): Renamed from instant_add_column().
Add the parameter col_map so that columns can be reordered.
Copy and adjust v_cols[] as well.
dict_table_t::find(): Find an old column based on a new column number.
dict_table_t::serialise_columns(), dict_table_t::deserialise_columns():
Convert the mblob.
dict_index_t::instant_metadata(): Create the metadata record
for instant ALTER TABLE. Invoke dict_table_t::serialise_columns().
dict_index_t::reconstruct_fields(): Invoked by
dict_table_t::deserialise_columns().
dict_index_t::clear_instant_alter(): Move the fields for the
dropped columns to the end, and sort the surviving index fields
in ascending order of column position.
ha_innobase::check_if_supported_inplace_alter(): Do not allow
adding a FTS_DOC_ID column if a hidden FTS_DOC_ID column exists
due to FULLTEXT INDEX. (This always required ALGORITHM=COPY.)
instant_alter_column_possible(): Add a parameter for InnoDB table,
to check for additional conditions, such as the maximum number of
index fields.
ha_innobase_inplace_ctx::first_alter_pos: The first column whose position
is affected by instant ADD, DROP, or changing the order of columns.
innobase_build_col_map(): Skip added virtual columns.
prepare_inplace_add_virtual(): Correctly compute num_to_add_vcol.
Remove some unnecessary code. Note that the call to
innodb_base_col_setup() should be executed later.
commit_try_norebuild(): If ctx->is_instant(), let the virtual
columns be added or dropped by innobase_instant_try().
innobase_instant_try(): Fill in a zero default value for the
hidden column FTS_DOC_ID (to reduce the work needed in MDEV-17459).
If any columns were dropped or reordered (or added not last),
delete any SYS_COLUMNS records for the following columns, and
insert SYS_COLUMNS records for all subsequent stored columns as well
as for all virtual columns. If any virtual column is dropped, rewrite
all virtual column metadata. Use a shortcut only for adding
virtual columns. This is because innobase_drop_virtual_try()
assumes that the dropped virtual columns still exist in ctx->old_table.
innodb_update_cols(): Renamed from innodb_update_n_cols().
innobase_add_one_virtual(), innobase_insert_sys_virtual(): Change
the return type to bool, and invoke my_error() when detecting an error.
innodb_insert_sys_columns(): Insert a record into SYS_COLUMNS.
Refactored from innobase_add_one_virtual() and innobase_instant_add_col().
innobase_instant_add_col(): Replace the parameter dfield with type.
innobase_instant_drop_cols(): Drop matching columns from SYS_COLUMNS
and all columns from SYS_VIRTUAL.
innobase_add_virtual_try(), innobase_drop_virtual_try(): Let
the caller invoke innodb_update_cols().
innobase_rename_column_try(): Skip dropped columns.
commit_cache_norebuild(): Update table->fts->doc_col.
dict_mem_table_col_rename_low(): Skip dropped columns.
trx_undo_rec_get_partial_row(): Skip dropped columns.
trx_undo_update_rec_get_update(): Handle the metadata BLOB correctly.
trx_undo_page_report_modify(): Avoid out-of-bounds access to record fields.
Log metadata records consistently.
Apparently, the first fields of a clustered index may be updated
in an update_undo vector when the index is ID_IND of SYS_FOREIGN,
as part of renaming the table during ALTER TABLE. Normally, updates of
the PRIMARY KEY should be logged as delete-mark and an insert.
row_undo_mod_parse_undo_rec(), row_purge_parse_undo_rec():
Use trx_undo_metadata.
row_undo_mod_clust_low(): On metadata rollback, roll back the root page too.
row_undo_mod_clust(): Relax an assertion. The delete-mark flag was
repurposed for ALTER TABLE metadata records.
row_rec_to_index_entry_impl(): Add the template parameter mblob
and the optional parameter info_bits for specifying the desired new
info bits. For the metadata tuple, allow conversion between the original
format (ADD COLUMN only) and the generic format (with hidden BLOB).
Add the optional parameter "pad" to determine whether the tuple should
be padded to the index fields (on ALTER TABLE it should), or whether
it should remain at its original size (on rollback).
row_build_index_entry_low(): Clean up the code, removing
redundant variables and conditions. For instantly dropped columns,
generate a dummy value that is NULL, the empty string, or a
fixed length of NUL bytes, depending on the type of the dropped column.
row_upd_clust_rec_by_insert_inherit_func(): On the update of PRIMARY KEY
of a record that contained a dropped column whose value was stored
externally, we will be inserting a dummy NULL or empty string value
to the field of the dropped column. The externally stored column would
eventually be dropped when purge removes the delete-marked record for
the old PRIMARY KEY value.
btr_index_rec_validate(): Recognize the metadata record.
btr_discard_only_page_on_level(): Preserve the generic instant
ALTER TABLE metadata.
btr_set_instant(): Replaces page_set_instant(). This sets a clustered
index root page to the appropriate format, or upgrades from
the MDEV-11369 instant ADD COLUMN to generic ALTER TABLE format.
btr_cur_instant_init_low(): Read and validate the metadata BLOB page
before reconstructing the dictionary information based on it.
btr_cur_instant_init_metadata(): Do not read any lengths from the
metadata record header before reading the BLOB. At this point, we
would not actually know how many nullable fields the metadata record
contains.
btr_cur_instant_root_init(): Initialize n_core_null_bytes in one
of two possible ways.
btr_cur_trim(): Handle the mblob record.
row_metadata_to_tuple(): Convert a metadata record to a data tuple,
based on the new info_bits of the metadata record.
btr_cur_pessimistic_update(): Invoke row_metadata_to_tuple() if needed.
Invoke dtuple_convert_big_rec() for metadata records if the record is
too large, or if the mblob is not yet marked as externally stored.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
When the last user record is deleted, do not delete the
generic instant ALTER TABLE metadata record. Only delete
MDEV-11369 instant ADD COLUMN metadata records.
btr_cur_optimistic_insert(): Avoid unnecessary computation of rec_size.
btr_pcur_store_position(): Allow a logically empty page to contain
a metadata record for generic ALTER TABLE.
REC_INFO_DEFAULT_ROW_ADD: Renamed from REC_INFO_DEFAULT_ROW.
This is for the old instant ADD COLUMN (MDEV-11369) only.
REC_INFO_DEFAULT_ROW_ALTER: The more generic metadata record,
with additional information for dropped or reordered columns.
rec_info_bits_valid(): Remove. The only case when this would fail
is when the record is the generic ALTER TABLE metadata record.
rec_is_alter_metadata(): Check if a record is the metadata record
for instant ALTER TABLE (other than ADD COLUMN). NOTE: This function
must not be invoked on node pointer records, because the delete-mark
flag in those records may be set (it is garbage), and then a debug
assertion could fail because index->is_instant() does not necessarily
hold.
rec_is_add_metadata(): Check if a record is MDEV-11369 ADD COLUMN metadata
record (not more generic instant ALTER TABLE).
rec_get_converted_size_comp_prefix_low(): Assume that the metadata
field will be stored externally. In dtuple_convert_big_rec() during
the rec_get_converted_size() call, it would not be there yet.
rec_get_converted_size_comp(): Replace status,fields,n_fields with tuple.
rec_init_offsets_comp_ordinary(), rec_get_converted_size_comp_prefix_low(),
rec_convert_dtuple_to_rec_comp(): Add template<bool mblob = false>.
With mblob=true, process a record with a metadata BLOB.
rec_copy_prefix_to_buf(): Assert that no fields beyond the key and
system columns are being copied. Exclude the metadata BLOB field.
rec_convert_dtuple_to_metadata_comp(): Convert an alter metadata tuple
into a record.
row_upd_index_replace_metadata(): Apply an update vector to an
alter_metadata tuple.
row_log_allocate(): Replace dict_index_t::is_instant()
with a more appropriate condition that ignores dict_table_t::instant.
Only a table on which the MDEV-11369 ADD COLUMN was performed
can "lose its instantness" when it becomes empty. After
instant DROP COLUMN or reordering columns, we cannot simply
convert the table to the canonical format, because the data
dictionary cache and all possibly existing references to it
from other client connection threads would have to be adjusted.
row_quiesce_write_index_fields(): Do not crash when the table contains
an instantly dropped column.
Thanks to Thirunarayanan Balathandayuthapani for discussing the design
and implementing an initial prototype of this.
Thanks to Matthias Leich for testing.
2018-10-19 16:49:54 +03:00
|
|
|
if (col->is_dropped()) {
|
|
|
|
/* the column was instantly dropped earlier */
|
|
|
|
ut_ad(index->table->instant);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
ulint col_no
|
|
|
|
= log->col_map[dict_col_get_no(col)];
|
|
|
|
|
|
|
|
if (col_no == ULINT_UNDEFINED) {
|
MDEV-15662 Instant DROP COLUMN or changing the order of columns
Allow ADD COLUMN anywhere in a table, not only adding as the
last column.
Allow instant DROP COLUMN and instant changing the order of columns.
The added columns will always be added last in clustered index records.
In new records, instantly dropped columns will be stored as NULL or
empty when possible.
Information about dropped and reordered columns will be written in
a metadata BLOB (mblob), which is stored before the first 'user' field
in the hidden metadata record at the start of the clustered index.
The presence of mblob is indicated by setting the delete-mark flag in
the metadata record.
The metadata BLOB stores the number of clustered index fields,
followed by an array of column information for each field.
For dropped columns, we store the NOT NULL flag, the fixed length,
and for variable-length columns, whether the maximum length exceeded
255 bytes. For non-dropped columns, we store the column position.
Unlike with MDEV-11369, when a table becomes empty, it cannot
be converted back to the canonical format. The reason for this is
that other threads may hold cached objects such as
row_prebuilt_t::ins_node that could refer to dropped or reordered
index fields.
For instant DROP COLUMN and ROW_FORMAT=COMPACT or ROW_FORMAT=DYNAMIC,
we must store the n_core_null_bytes in the root page, so that the
chain of node pointer records can be followed in order to reach the
leftmost leaf page where the metadata record is located.
If the mblob is present, we will zero-initialize the strings
"infimum" and "supremum" in the root page, and use the last byte of
"supremum" for storing the number of null bytes (which are allocated
but useless on node pointer pages). This is necessary for
btr_cur_instant_init_metadata() to be able to navigate to the mblob.
If the PRIMARY KEY contains any variable-length column and some
nullable columns were instantly dropped, the dict_index_t::n_nullable
in the data dictionary could be smaller than it actually is in the
non-leaf pages. Because of this, the non-leaf pages could use more
bytes for the null flags than the data dictionary expects, and we
could be reading the lengths of the variable-length columns from the
wrong offset, and thus reading the child page number from wrong place.
This is the result of two design mistakes that involve unnecessary
storage of data: First, it is nonsense to store any data fields for
the leftmost node pointer records, because the comparisons would be
resolved by the MIN_REC_FLAG alone. Second, there cannot be any null
fields in the clustered index node pointer fields, but we nevertheless
reserve space for all the null flags.
Limitations (future work):
MDEV-17459 Allow instant ALTER TABLE even if FULLTEXT INDEX exists
MDEV-17468 Avoid table rebuild on operations on generated columns
MDEV-17494 Refuse ALGORITHM=INSTANT when the row size is too large
btr_page_reorganize_low(): Preserve any metadata in the root page.
Call lock_move_reorganize_page() only after restoring the "infimum"
and "supremum" records, to avoid a memcmp() assertion failure.
dict_col_t::DROPPED: Magic value for dict_col_t::ind.
dict_col_t::clear_instant(): Renamed from dict_col_t::remove_instant().
Do not assert that the column was instantly added, because we
sometimes call this unconditionally for all columns.
Convert an instantly added column to a "core column". The old name
remove_instant() could be mistaken to refer to "instant DROP COLUMN".
dict_col_t::is_added(): Rename from dict_col_t::is_instant().
dtype_t::metadata_blob_init(): Initialize the mblob data type.
dtuple_t::is_metadata(), dtuple_t::is_alter_metadata(),
upd_t::is_metadata(), upd_t::is_alter_metadata(): Check if info_bits
refer to a metadata record.
dict_table_t::instant: Metadata about dropped or reordered columns.
dict_table_t::prepare_instant(): Prepare
ha_innobase_inplace_ctx::instant_table for instant ALTER TABLE.
innobase_instant_try() will pass this to dict_table_t::instant_column().
On rollback, dict_table_t::rollback_instant() will be called.
dict_table_t::instant_column(): Renamed from instant_add_column().
Add the parameter col_map so that columns can be reordered.
Copy and adjust v_cols[] as well.
dict_table_t::find(): Find an old column based on a new column number.
dict_table_t::serialise_columns(), dict_table_t::deserialise_columns():
Convert the mblob.
dict_index_t::instant_metadata(): Create the metadata record
for instant ALTER TABLE. Invoke dict_table_t::serialise_columns().
dict_index_t::reconstruct_fields(): Invoked by
dict_table_t::deserialise_columns().
dict_index_t::clear_instant_alter(): Move the fields for the
dropped columns to the end, and sort the surviving index fields
in ascending order of column position.
ha_innobase::check_if_supported_inplace_alter(): Do not allow
adding a FTS_DOC_ID column if a hidden FTS_DOC_ID column exists
due to FULLTEXT INDEX. (This always required ALGORITHM=COPY.)
instant_alter_column_possible(): Add a parameter for InnoDB table,
to check for additional conditions, such as the maximum number of
index fields.
ha_innobase_inplace_ctx::first_alter_pos: The first column whose position
is affected by instant ADD, DROP, or changing the order of columns.
innobase_build_col_map(): Skip added virtual columns.
prepare_inplace_add_virtual(): Correctly compute num_to_add_vcol.
Remove some unnecessary code. Note that the call to
innodb_base_col_setup() should be executed later.
commit_try_norebuild(): If ctx->is_instant(), let the virtual
columns be added or dropped by innobase_instant_try().
innobase_instant_try(): Fill in a zero default value for the
hidden column FTS_DOC_ID (to reduce the work needed in MDEV-17459).
If any columns were dropped or reordered (or added not last),
delete any SYS_COLUMNS records for the following columns, and
insert SYS_COLUMNS records for all subsequent stored columns as well
as for all virtual columns. If any virtual column is dropped, rewrite
all virtual column metadata. Use a shortcut only for adding
virtual columns. This is because innobase_drop_virtual_try()
assumes that the dropped virtual columns still exist in ctx->old_table.
innodb_update_cols(): Renamed from innodb_update_n_cols().
innobase_add_one_virtual(), innobase_insert_sys_virtual(): Change
the return type to bool, and invoke my_error() when detecting an error.
innodb_insert_sys_columns(): Insert a record into SYS_COLUMNS.
Refactored from innobase_add_one_virtual() and innobase_instant_add_col().
innobase_instant_add_col(): Replace the parameter dfield with type.
innobase_instant_drop_cols(): Drop matching columns from SYS_COLUMNS
and all columns from SYS_VIRTUAL.
innobase_add_virtual_try(), innobase_drop_virtual_try(): Let
the caller invoke innodb_update_cols().
innobase_rename_column_try(): Skip dropped columns.
commit_cache_norebuild(): Update table->fts->doc_col.
dict_mem_table_col_rename_low(): Skip dropped columns.
trx_undo_rec_get_partial_row(): Skip dropped columns.
trx_undo_update_rec_get_update(): Handle the metadata BLOB correctly.
trx_undo_page_report_modify(): Avoid out-of-bounds access to record fields.
Log metadata records consistently.
Apparently, the first fields of a clustered index may be updated
in an update_undo vector when the index is ID_IND of SYS_FOREIGN,
as part of renaming the table during ALTER TABLE. Normally, updates of
the PRIMARY KEY should be logged as delete-mark and an insert.
row_undo_mod_parse_undo_rec(), row_purge_parse_undo_rec():
Use trx_undo_metadata.
row_undo_mod_clust_low(): On metadata rollback, roll back the root page too.
row_undo_mod_clust(): Relax an assertion. The delete-mark flag was
repurposed for ALTER TABLE metadata records.
row_rec_to_index_entry_impl(): Add the template parameter mblob
and the optional parameter info_bits for specifying the desired new
info bits. For the metadata tuple, allow conversion between the original
format (ADD COLUMN only) and the generic format (with hidden BLOB).
Add the optional parameter "pad" to determine whether the tuple should
be padded to the index fields (on ALTER TABLE it should), or whether
it should remain at its original size (on rollback).
row_build_index_entry_low(): Clean up the code, removing
redundant variables and conditions. For instantly dropped columns,
generate a dummy value that is NULL, the empty string, or a
fixed length of NUL bytes, depending on the type of the dropped column.
row_upd_clust_rec_by_insert_inherit_func(): On the update of PRIMARY KEY
of a record that contained a dropped column whose value was stored
externally, we will be inserting a dummy NULL or empty string value
to the field of the dropped column. The externally stored column would
eventually be dropped when purge removes the delete-marked record for
the old PRIMARY KEY value.
btr_index_rec_validate(): Recognize the metadata record.
btr_discard_only_page_on_level(): Preserve the generic instant
ALTER TABLE metadata.
btr_set_instant(): Replaces page_set_instant(). This sets a clustered
index root page to the appropriate format, or upgrades from
the MDEV-11369 instant ADD COLUMN to generic ALTER TABLE format.
btr_cur_instant_init_low(): Read and validate the metadata BLOB page
before reconstructing the dictionary information based on it.
btr_cur_instant_init_metadata(): Do not read any lengths from the
metadata record header before reading the BLOB. At this point, we
would not actually know how many nullable fields the metadata record
contains.
btr_cur_instant_root_init(): Initialize n_core_null_bytes in one
of two possible ways.
btr_cur_trim(): Handle the mblob record.
row_metadata_to_tuple(): Convert a metadata record to a data tuple,
based on the new info_bits of the metadata record.
btr_cur_pessimistic_update(): Invoke row_metadata_to_tuple() if needed.
Invoke dtuple_convert_big_rec() for metadata records if the record is
too large, or if the mblob is not yet marked as externally stored.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
When the last user record is deleted, do not delete the
generic instant ALTER TABLE metadata record. Only delete
MDEV-11369 instant ADD COLUMN metadata records.
btr_cur_optimistic_insert(): Avoid unnecessary computation of rec_size.
btr_pcur_store_position(): Allow a logically empty page to contain
a metadata record for generic ALTER TABLE.
REC_INFO_DEFAULT_ROW_ADD: Renamed from REC_INFO_DEFAULT_ROW.
This is for the old instant ADD COLUMN (MDEV-11369) only.
REC_INFO_DEFAULT_ROW_ALTER: The more generic metadata record,
with additional information for dropped or reordered columns.
rec_info_bits_valid(): Remove. The only case when this would fail
is when the record is the generic ALTER TABLE metadata record.
rec_is_alter_metadata(): Check if a record is the metadata record
for instant ALTER TABLE (other than ADD COLUMN). NOTE: This function
must not be invoked on node pointer records, because the delete-mark
flag in those records may be set (it is garbage), and then a debug
assertion could fail because index->is_instant() does not necessarily
hold.
rec_is_add_metadata(): Check if a record is MDEV-11369 ADD COLUMN metadata
record (not more generic instant ALTER TABLE).
rec_get_converted_size_comp_prefix_low(): Assume that the metadata
field will be stored externally. In dtuple_convert_big_rec() during
the rec_get_converted_size() call, it would not be there yet.
rec_get_converted_size_comp(): Replace status,fields,n_fields with tuple.
rec_init_offsets_comp_ordinary(), rec_get_converted_size_comp_prefix_low(),
rec_convert_dtuple_to_rec_comp(): Add template<bool mblob = false>.
With mblob=true, process a record with a metadata BLOB.
rec_copy_prefix_to_buf(): Assert that no fields beyond the key and
system columns are being copied. Exclude the metadata BLOB field.
rec_convert_dtuple_to_metadata_comp(): Convert an alter metadata tuple
into a record.
row_upd_index_replace_metadata(): Apply an update vector to an
alter_metadata tuple.
row_log_allocate(): Replace dict_index_t::is_instant()
with a more appropriate condition that ignores dict_table_t::instant.
Only a table on which the MDEV-11369 ADD COLUMN was performed
can "lose its instantness" when it becomes empty. After
instant DROP COLUMN or reordering columns, we cannot simply
convert the table to the canonical format, because the data
dictionary cache and all possibly existing references to it
from other client connection threads would have to be adjusted.
row_quiesce_write_index_fields(): Do not crash when the table contains
an instantly dropped column.
Thanks to Thirunarayanan Balathandayuthapani for discussing the design
and implementing an initial prototype of this.
Thanks to Matthias Leich for testing.
2018-10-19 16:49:54 +03:00
|
|
|
/* the column is being dropped now */
|
2013-03-26 00:03:13 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
dfield_t* dfield
|
2013-03-26 00:03:13 +02:00
|
|
|
= dtuple_get_nth_field(row, col_no);
|
2016-08-12 11:17:45 +03:00
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
ulint len;
|
2014-05-06 21:13:16 +02:00
|
|
|
const byte* data;
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
if (rec_offs_nth_extern(offsets, i)) {
|
|
|
|
ut_ad(rec_offs_any_extern(offsets));
|
2014-02-01 09:33:26 +01:00
|
|
|
rw_lock_x_lock(dict_index_get_lock(index));
|
|
|
|
|
|
|
|
if (const page_no_map* blobs = log->blobs) {
|
|
|
|
data = rec_get_nth_field(
|
|
|
|
mrec, offsets, i, &len);
|
|
|
|
ut_ad(len >= BTR_EXTERN_FIELD_REF_SIZE);
|
|
|
|
|
|
|
|
ulint page_no = mach_read_from_4(
|
|
|
|
data + len - (BTR_EXTERN_FIELD_REF_SIZE
|
|
|
|
- BTR_EXTERN_PAGE_NO));
|
|
|
|
page_no_map::const_iterator p = blobs->find(
|
|
|
|
page_no);
|
|
|
|
if (p != blobs->end()
|
|
|
|
&& p->second.is_freed(log->head.total)) {
|
|
|
|
/* This BLOB has been freed.
|
|
|
|
We must not access the row. */
|
2014-05-06 21:13:16 +02:00
|
|
|
*error = DB_MISSING_HISTORY;
|
|
|
|
dfield_set_data(dfield, data, len);
|
|
|
|
dfield_set_ext(dfield);
|
|
|
|
goto blob_done;
|
2014-02-01 09:33:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-06 21:13:16 +02:00
|
|
|
data = btr_rec_copy_externally_stored_field(
|
|
|
|
mrec, offsets,
|
2019-02-06 19:50:11 +02:00
|
|
|
index->table->space->zip_size(),
|
2016-08-12 11:17:45 +03:00
|
|
|
i, &len, heap);
|
2014-05-06 21:13:16 +02:00
|
|
|
ut_a(data);
|
|
|
|
dfield_set_data(dfield, data, len);
|
|
|
|
blob_done:
|
2014-02-01 09:33:26 +01:00
|
|
|
rw_lock_x_unlock(dict_index_get_lock(index));
|
2013-03-26 00:03:13 +02:00
|
|
|
} else {
|
|
|
|
data = rec_get_nth_field(mrec, offsets, i, &len);
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
if (len == UNIV_SQL_DEFAULT) {
|
2018-07-26 22:52:53 +03:00
|
|
|
data = log->instant_field_value(i, &len);
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
}
|
2014-05-06 21:13:16 +02:00
|
|
|
dfield_set_data(dfield, data, len);
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
2015-05-04 22:13:46 +02:00
|
|
|
if (len != UNIV_SQL_NULL && col->mtype == DATA_MYSQL
|
|
|
|
&& col->len != len && !dict_table_is_comp(log->table)) {
|
|
|
|
|
|
|
|
ut_ad(col->len >= len);
|
|
|
|
if (dict_table_is_comp(index->table)) {
|
|
|
|
byte* buf = (byte*) mem_heap_alloc(heap,
|
|
|
|
col->len);
|
|
|
|
memcpy(buf, dfield->data, len);
|
|
|
|
memset(buf + len, 0x20, col->len - len);
|
|
|
|
|
|
|
|
dfield_set_data(dfield, buf, col->len);
|
|
|
|
} else {
|
|
|
|
/* field length mismatch should not happen
|
|
|
|
when rebuilding the redundant row format
|
|
|
|
table. */
|
|
|
|
ut_ad(0);
|
|
|
|
*error = DB_CORRUPTION;
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
/* See if any columns were changed to NULL or NOT NULL. */
|
|
|
|
const dict_col_t* new_col
|
|
|
|
= dict_table_get_nth_col(log->table, col_no);
|
2020-01-21 18:47:56 +02:00
|
|
|
ut_ad(new_col->same_format(*col));
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
/* Assert that prtype matches except for nullability. */
|
|
|
|
ut_ad(!((new_col->prtype ^ dfield_get_type(dfield)->prtype)
|
2020-01-21 18:47:56 +02:00
|
|
|
& ~(DATA_NOT_NULL | DATA_VERSIONED
|
|
|
|
| CHAR_COLL_MASK << 16 | DATA_LONG_TRUE_VARCHAR)));
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
if (new_col->prtype == col->prtype) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((new_col->prtype & DATA_NOT_NULL)
|
|
|
|
&& dfield_is_null(dfield)) {
|
2018-04-24 13:15:35 +05:30
|
|
|
|
|
|
|
const dfield_t& default_field
|
|
|
|
= log->defaults->fields[col_no];
|
2020-05-25 21:42:26 +05:30
|
|
|
|
|
|
|
Field* field = log->old_table->field[col->ind];
|
2018-06-25 14:52:38 +05:30
|
|
|
|
|
|
|
field->set_warning(Sql_condition::WARN_LEVEL_WARN,
|
2020-05-25 21:42:26 +05:30
|
|
|
WARN_DATA_TRUNCATED, 1,
|
|
|
|
ulong(log->n_rows));
|
2018-04-24 13:15:35 +05:30
|
|
|
|
2018-06-25 14:52:38 +05:30
|
|
|
if (!log->allow_not_null) {
|
2018-04-24 13:15:35 +05:30
|
|
|
/* We got a NULL value for a NOT NULL column. */
|
|
|
|
*error = DB_INVALID_NULL;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
*dfield = default_field;
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Adjust the DATA_NOT_NULL flag in the parsed row. */
|
|
|
|
dfield_get_type(dfield)->prtype = new_col->prtype;
|
|
|
|
|
|
|
|
ut_ad(dict_col_type_assert_equal(new_col,
|
|
|
|
dfield_get_type(dfield)));
|
|
|
|
}
|
|
|
|
|
|
|
|
return(row);
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************//**
|
|
|
|
Replays an insert operation on a table that was rebuilt.
|
|
|
|
@return DB_SUCCESS or error code */
|
2016-06-21 14:21:03 +02:00
|
|
|
static MY_ATTRIBUTE((nonnull, warn_unused_result))
|
2013-03-26 00:03:13 +02:00
|
|
|
dberr_t
|
|
|
|
row_log_table_apply_insert_low(
|
|
|
|
/*===========================*/
|
|
|
|
que_thr_t* thr, /*!< in: query graph */
|
|
|
|
const dtuple_t* row, /*!< in: table row
|
|
|
|
in the old table definition */
|
|
|
|
mem_heap_t* offsets_heap, /*!< in/out: memory heap
|
|
|
|
that can be emptied */
|
|
|
|
mem_heap_t* heap, /*!< in/out: memory heap */
|
|
|
|
row_merge_dup_t* dup) /*!< in/out: for reporting
|
|
|
|
duplicate key errors */
|
|
|
|
{
|
|
|
|
dberr_t error;
|
|
|
|
dtuple_t* entry;
|
|
|
|
const row_log_t*log = dup->index->online_log;
|
|
|
|
dict_index_t* index = dict_table_get_first_index(log->table);
|
2015-12-13 10:11:49 +01:00
|
|
|
ulint n_index = 0;
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
ut_ad(dtuple_validate(row));
|
|
|
|
|
2017-02-16 09:16:11 +02:00
|
|
|
DBUG_LOG("ib_alter_table",
|
|
|
|
"insert table " << index->table->id << " (index "
|
|
|
|
<< index->id << "): " << rec_printer(row).str());
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
static const ulint flags
|
|
|
|
= (BTR_CREATE_FLAG
|
|
|
|
| BTR_NO_LOCKING_FLAG
|
|
|
|
| BTR_NO_UNDO_LOG_FLAG
|
|
|
|
| BTR_KEEP_SYS_FLAG);
|
|
|
|
|
|
|
|
entry = row_build_index_entry(row, NULL, index, heap);
|
|
|
|
|
|
|
|
error = row_ins_clust_index_entry_low(
|
2016-08-12 11:17:45 +03:00
|
|
|
flags, BTR_MODIFY_TREE, index, index->n_uniq,
|
2019-09-23 08:29:39 +03:00
|
|
|
entry, 0, thr);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
switch (error) {
|
|
|
|
case DB_SUCCESS:
|
|
|
|
break;
|
|
|
|
case DB_SUCCESS_LOCKED_REC:
|
|
|
|
/* The row had already been copied to the table. */
|
|
|
|
return(DB_SUCCESS);
|
|
|
|
default:
|
|
|
|
return(error);
|
|
|
|
}
|
|
|
|
|
2017-12-21 17:19:13 +02:00
|
|
|
ut_ad(dict_index_is_clust(index));
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2017-12-21 17:19:13 +02:00
|
|
|
for (n_index += index->type != DICT_CLUSTERED;
|
|
|
|
(index = dict_table_get_next_index(index)); n_index++) {
|
2013-03-26 00:03:13 +02:00
|
|
|
if (index->type & DICT_FTS) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
entry = row_build_index_entry(row, NULL, index, heap);
|
|
|
|
error = row_ins_sec_index_entry_low(
|
|
|
|
flags, BTR_MODIFY_TREE,
|
2017-08-07 23:44:45 +03:00
|
|
|
index, offsets_heap, heap, entry,
|
2019-09-23 10:25:34 +03:00
|
|
|
thr_get_trx(thr)->id, thr);
|
2016-09-06 09:43:16 +03:00
|
|
|
|
2017-12-21 17:19:13 +02:00
|
|
|
if (error != DB_SUCCESS) {
|
|
|
|
if (error == DB_DUPLICATE_KEY) {
|
|
|
|
thr_get_trx(thr)->error_key_num = n_index;
|
|
|
|
}
|
|
|
|
break;
|
2015-12-13 10:11:49 +01:00
|
|
|
}
|
2017-12-21 17:19:13 +02:00
|
|
|
}
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
return(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************//**
|
|
|
|
Replays an insert operation on a table that was rebuilt.
|
|
|
|
@return DB_SUCCESS or error code */
|
2016-06-21 14:21:03 +02:00
|
|
|
static MY_ATTRIBUTE((nonnull, warn_unused_result))
|
2013-03-26 00:03:13 +02:00
|
|
|
dberr_t
|
|
|
|
row_log_table_apply_insert(
|
|
|
|
/*=======================*/
|
|
|
|
que_thr_t* thr, /*!< in: query graph */
|
|
|
|
const mrec_t* mrec, /*!< in: record to insert */
|
2020-04-28 10:46:51 +10:00
|
|
|
const rec_offs* offsets, /*!< in: offsets of mrec */
|
2013-03-26 00:03:13 +02:00
|
|
|
mem_heap_t* offsets_heap, /*!< in/out: memory heap
|
|
|
|
that can be emptied */
|
|
|
|
mem_heap_t* heap, /*!< in/out: memory heap */
|
2017-08-07 23:44:45 +03:00
|
|
|
row_merge_dup_t* dup) /*!< in/out: for reporting
|
2013-03-26 00:03:13 +02:00
|
|
|
duplicate key errors */
|
|
|
|
{
|
2018-06-25 18:16:29 +05:30
|
|
|
row_log_t*log = dup->index->online_log;
|
2013-03-26 00:03:13 +02:00
|
|
|
dberr_t error;
|
|
|
|
const dtuple_t* row = row_log_table_apply_convert_mrec(
|
2017-08-07 23:44:45 +03:00
|
|
|
mrec, dup->index, offsets, log, heap, &error);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2014-05-06 21:13:16 +02:00
|
|
|
switch (error) {
|
|
|
|
case DB_MISSING_HISTORY:
|
|
|
|
ut_ad(log->blobs);
|
|
|
|
/* Because some BLOBs are missing, we know that the
|
|
|
|
transaction was rolled back later (a rollback of
|
|
|
|
an insert can free BLOBs).
|
|
|
|
We can simply skip the insert: the subsequent
|
|
|
|
ROW_T_DELETE will be ignored, or a ROW_T_UPDATE will
|
|
|
|
be interpreted as ROW_T_INSERT. */
|
|
|
|
return(DB_SUCCESS);
|
|
|
|
case DB_SUCCESS:
|
|
|
|
ut_ad(row != NULL);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ut_ad(0);
|
2019-08-30 12:51:37 +03:00
|
|
|
/* fall through */
|
2014-05-06 21:13:16 +02:00
|
|
|
case DB_INVALID_NULL:
|
|
|
|
ut_ad(row == NULL);
|
2013-03-26 00:03:13 +02:00
|
|
|
return(error);
|
2014-05-06 21:13:16 +02:00
|
|
|
}
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2014-05-06 21:13:16 +02:00
|
|
|
error = row_log_table_apply_insert_low(
|
2017-08-07 23:44:45 +03:00
|
|
|
thr, row, offsets_heap, heap, dup);
|
2014-05-06 21:13:16 +02:00
|
|
|
if (error != DB_SUCCESS) {
|
|
|
|
/* Report the erroneous row using the new
|
|
|
|
version of the table. */
|
|
|
|
innobase_row_to_mysql(dup->table, log->table, row);
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
return(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************//**
|
|
|
|
Deletes a record from a table that is being rebuilt.
|
|
|
|
@return DB_SUCCESS or error code */
|
2018-01-10 20:54:09 +05:30
|
|
|
static MY_ATTRIBUTE((nonnull, warn_unused_result))
|
2013-03-26 00:03:13 +02:00
|
|
|
dberr_t
|
|
|
|
row_log_table_apply_delete_low(
|
|
|
|
/*===========================*/
|
|
|
|
btr_pcur_t* pcur, /*!< in/out: B-tree cursor,
|
|
|
|
will be trashed */
|
2020-04-28 10:46:51 +10:00
|
|
|
const rec_offs* offsets, /*!< in: offsets on pcur */
|
2013-03-26 00:03:13 +02:00
|
|
|
mem_heap_t* heap, /*!< in/out: memory heap */
|
|
|
|
mtr_t* mtr) /*!< in/out: mini-transaction,
|
|
|
|
will be committed */
|
|
|
|
{
|
|
|
|
dberr_t error;
|
|
|
|
row_ext_t* ext;
|
|
|
|
dtuple_t* row;
|
|
|
|
dict_index_t* index = btr_pcur_get_btr_cur(pcur)->index;
|
|
|
|
|
|
|
|
ut_ad(dict_index_is_clust(index));
|
|
|
|
|
2017-02-16 09:16:11 +02:00
|
|
|
DBUG_LOG("ib_alter_table",
|
|
|
|
"delete table " << index->table->id << " (index "
|
|
|
|
<< index->id << "): "
|
|
|
|
<< rec_printer(btr_pcur_get_rec(pcur), offsets).str());
|
2016-08-12 11:17:45 +03:00
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
if (dict_table_get_next_index(index)) {
|
|
|
|
/* Build a row template for purging secondary index entries. */
|
|
|
|
row = row_build(
|
|
|
|
ROW_COPY_DATA, index, btr_pcur_get_rec(pcur),
|
2018-01-10 20:54:09 +05:30
|
|
|
offsets, NULL, NULL, NULL, &ext, heap);
|
2013-03-26 00:03:13 +02:00
|
|
|
} else {
|
|
|
|
row = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
btr_cur_pessimistic_delete(&error, FALSE, btr_pcur_get_btr_cur(pcur),
|
2016-08-12 11:17:45 +03:00
|
|
|
BTR_CREATE_FLAG, false, mtr);
|
2013-03-26 00:03:13 +02:00
|
|
|
mtr_commit(mtr);
|
|
|
|
|
|
|
|
if (error != DB_SUCCESS) {
|
|
|
|
return(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
while ((index = dict_table_get_next_index(index)) != NULL) {
|
|
|
|
if (index->type & DICT_FTS) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const dtuple_t* entry = row_build_index_entry(
|
2018-01-10 20:54:09 +05:30
|
|
|
row, ext, index, heap);
|
2018-03-22 15:30:54 +02:00
|
|
|
mtr->start();
|
|
|
|
index->set_modified(*mtr);
|
2013-03-26 00:03:13 +02:00
|
|
|
btr_pcur_open(index, entry, PAGE_CUR_LE,
|
2016-08-12 11:17:45 +03:00
|
|
|
BTR_MODIFY_TREE | BTR_LATCH_FOR_DELETE,
|
|
|
|
pcur, mtr);
|
2013-03-26 00:03:13 +02:00
|
|
|
#ifdef UNIV_DEBUG
|
|
|
|
switch (btr_pcur_get_btr_cur(pcur)->flag) {
|
|
|
|
case BTR_CUR_DELETE_REF:
|
|
|
|
case BTR_CUR_DEL_MARK_IBUF:
|
|
|
|
case BTR_CUR_DELETE_IBUF:
|
|
|
|
case BTR_CUR_INSERT_TO_IBUF:
|
|
|
|
/* We did not request buffering. */
|
|
|
|
break;
|
|
|
|
case BTR_CUR_HASH:
|
|
|
|
case BTR_CUR_HASH_FAIL:
|
|
|
|
case BTR_CUR_BINARY:
|
|
|
|
goto flag_ok;
|
|
|
|
}
|
|
|
|
ut_ad(0);
|
|
|
|
flag_ok:
|
|
|
|
#endif /* UNIV_DEBUG */
|
|
|
|
|
|
|
|
if (page_rec_is_infimum(btr_pcur_get_rec(pcur))
|
|
|
|
|| btr_pcur_get_low_match(pcur) < index->n_uniq) {
|
|
|
|
/* All secondary index entries should be
|
|
|
|
found, because new_table is being modified by
|
|
|
|
this thread only, and all indexes should be
|
|
|
|
updated in sync. */
|
2018-03-22 15:30:54 +02:00
|
|
|
mtr->commit();
|
2013-03-26 00:03:13 +02:00
|
|
|
return(DB_INDEX_CORRUPT);
|
|
|
|
}
|
|
|
|
|
|
|
|
btr_cur_pessimistic_delete(&error, FALSE,
|
|
|
|
btr_pcur_get_btr_cur(pcur),
|
2016-08-12 11:17:45 +03:00
|
|
|
BTR_CREATE_FLAG, false, mtr);
|
2018-03-22 15:30:54 +02:00
|
|
|
mtr->commit();
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************//**
|
|
|
|
Replays a delete operation on a table that was rebuilt.
|
|
|
|
@return DB_SUCCESS or error code */
|
2018-01-10 20:54:09 +05:30
|
|
|
static MY_ATTRIBUTE((nonnull, warn_unused_result))
|
2013-03-26 00:03:13 +02:00
|
|
|
dberr_t
|
|
|
|
row_log_table_apply_delete(
|
|
|
|
/*=======================*/
|
|
|
|
ulint trx_id_col, /*!< in: position of
|
|
|
|
DB_TRX_ID in the new
|
|
|
|
clustered index */
|
|
|
|
const mrec_t* mrec, /*!< in: merge record */
|
2020-04-28 10:46:51 +10:00
|
|
|
const rec_offs* moffsets, /*!< in: offsets of mrec */
|
2013-03-26 00:03:13 +02:00
|
|
|
mem_heap_t* offsets_heap, /*!< in/out: memory heap
|
|
|
|
that can be emptied */
|
|
|
|
mem_heap_t* heap, /*!< in/out: memory heap */
|
2018-01-10 20:54:09 +05:30
|
|
|
const row_log_t* log) /*!< in: online log */
|
2013-03-26 00:03:13 +02:00
|
|
|
{
|
2014-05-06 21:13:16 +02:00
|
|
|
dict_table_t* new_table = log->table;
|
2013-03-26 00:03:13 +02:00
|
|
|
dict_index_t* index = dict_table_get_first_index(new_table);
|
|
|
|
dtuple_t* old_pk;
|
|
|
|
mtr_t mtr;
|
|
|
|
btr_pcur_t pcur;
|
2020-04-28 10:46:51 +10:00
|
|
|
rec_offs* offsets;
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2018-10-10 11:53:23 +03:00
|
|
|
ut_ad(rec_offs_n_fields(moffsets) == index->first_user_field());
|
2013-03-26 00:03:13 +02:00
|
|
|
ut_ad(!rec_offs_any_extern(moffsets));
|
|
|
|
|
|
|
|
/* Convert the row to a search tuple. */
|
MDEV-13795/MDEV-14332 Corruption during online table-rebuilding ALTER when VIRTUAL columns exist
When MySQL 5.7 introduced indexed virtual columns, it introduced
several bugs into the online table-rebuilding ALTER, that is,
the row_log_table_apply() family of functions.
The online_log format that was introduced for online table-rebuilding
ALTER in MySQL 5.6 should be sufficient. Ideally, any indexed virtual
column values would be evaluated based on the log records in the temporary
file. There is no need to log virtual column values.
(For ADD INDEX, that is row_log_apply(), we always must log the values of
the keys, no matter if the columns are virtual.)
Because omitting the virtual column values removes any chance of
row_log_table_apply() working with indexed virtual columns, we
will for now refuse LOCK=NONE in table-rebuilding ALTER operations
when indexes on virtual columns exist. This restriction would be
lifted in MDEV-14341.
innobase_indexed_virtual_exist(): New predicate, to determine if
indexed virtual columns exist in a table definition.
ha_innobase::check_if_supported_inplace_alter(): Refuse online rebuild
if indexed virtual columns exist.
rec_get_converted_size_temp_v(), rec_convert_dtuple_to_temp_v(): Remove.
row_log_table_delete(), row_log_table_update(, row_log_table_insert():
Remove parameters for virtual columns.
trx_undo_read_v_rows(): Remove the col_map parameter.
row_log_table_apply(): Do not deal with virtual columns.
2017-11-09 23:01:44 +02:00
|
|
|
old_pk = dtuple_create(heap, index->n_uniq);
|
2014-05-06 21:13:16 +02:00
|
|
|
dict_index_copy_types(old_pk, index, index->n_uniq);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2014-05-06 21:13:16 +02:00
|
|
|
for (ulint i = 0; i < index->n_uniq; i++) {
|
2013-03-26 00:03:13 +02:00
|
|
|
ulint len;
|
|
|
|
const void* field;
|
|
|
|
field = rec_get_nth_field(mrec, moffsets, i, &len);
|
|
|
|
ut_ad(len != UNIV_SQL_NULL);
|
|
|
|
dfield_set_data(dtuple_get_nth_field(old_pk, i),
|
|
|
|
field, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
mtr_start(&mtr);
|
2018-03-22 15:30:54 +02:00
|
|
|
index->set_modified(mtr);
|
2013-03-26 00:03:13 +02:00
|
|
|
btr_pcur_open(index, old_pk, PAGE_CUR_LE,
|
2016-08-12 11:17:45 +03:00
|
|
|
BTR_MODIFY_TREE | BTR_LATCH_FOR_DELETE,
|
|
|
|
&pcur, &mtr);
|
2013-03-26 00:03:13 +02:00
|
|
|
#ifdef UNIV_DEBUG
|
|
|
|
switch (btr_pcur_get_btr_cur(&pcur)->flag) {
|
|
|
|
case BTR_CUR_DELETE_REF:
|
|
|
|
case BTR_CUR_DEL_MARK_IBUF:
|
|
|
|
case BTR_CUR_DELETE_IBUF:
|
|
|
|
case BTR_CUR_INSERT_TO_IBUF:
|
|
|
|
/* We did not request buffering. */
|
|
|
|
break;
|
|
|
|
case BTR_CUR_HASH:
|
|
|
|
case BTR_CUR_HASH_FAIL:
|
|
|
|
case BTR_CUR_BINARY:
|
|
|
|
goto flag_ok;
|
|
|
|
}
|
|
|
|
ut_ad(0);
|
|
|
|
flag_ok:
|
|
|
|
#endif /* UNIV_DEBUG */
|
|
|
|
|
|
|
|
if (page_rec_is_infimum(btr_pcur_get_rec(&pcur))
|
|
|
|
|| btr_pcur_get_low_match(&pcur) < index->n_uniq) {
|
|
|
|
all_done:
|
|
|
|
mtr_commit(&mtr);
|
|
|
|
/* The record was not found. All done. */
|
2014-05-06 21:13:16 +02:00
|
|
|
/* This should only happen when an earlier
|
|
|
|
ROW_T_INSERT was skipped or
|
|
|
|
ROW_T_UPDATE was interpreted as ROW_T_DELETE
|
|
|
|
due to BLOBs having been freed by rollback. */
|
2013-03-26 00:03:13 +02:00
|
|
|
return(DB_SUCCESS);
|
|
|
|
}
|
|
|
|
|
2021-04-14 10:33:59 +03:00
|
|
|
offsets = rec_get_offsets(btr_pcur_get_rec(&pcur), index, nullptr,
|
2021-04-13 10:28:13 +03:00
|
|
|
index->n_core_fields,
|
2013-03-26 00:03:13 +02:00
|
|
|
ULINT_UNDEFINED, &offsets_heap);
|
|
|
|
#if defined UNIV_DEBUG || defined UNIV_BLOB_LIGHT_DEBUG
|
|
|
|
ut_a(!rec_offs_any_null_extern(btr_pcur_get_rec(&pcur), offsets));
|
|
|
|
#endif /* UNIV_DEBUG || UNIV_BLOB_LIGHT_DEBUG */
|
|
|
|
|
2014-05-06 21:13:16 +02:00
|
|
|
/* Only remove the record if DB_TRX_ID,DB_ROLL_PTR match. */
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
{
|
|
|
|
ulint len;
|
2014-05-06 21:13:16 +02:00
|
|
|
const byte* mrec_trx_id
|
2013-03-26 00:03:13 +02:00
|
|
|
= rec_get_nth_field(mrec, moffsets, trx_id_col, &len);
|
|
|
|
ut_ad(len == DATA_TRX_ID_LEN);
|
2014-05-06 21:13:16 +02:00
|
|
|
const byte* rec_trx_id
|
2013-03-26 00:03:13 +02:00
|
|
|
= rec_get_nth_field(btr_pcur_get_rec(&pcur), offsets,
|
|
|
|
trx_id_col, &len);
|
|
|
|
ut_ad(len == DATA_TRX_ID_LEN);
|
MDEV-13654 Various crashes due to DB_TRX_ID mismatch in table-rebuilding ALTER TABLE…LOCK=NONE
After MDEV-12288 and MDEV-13536, the DB_TRX_ID of old clustered index
records for which no history is available should be reset to 0.
This caused crashes in online table-rebuilding ALTER, because the
row_log_table_apply() is built on the assumption that the PRIMARY KEY
together with DB_TRX_ID,DB_ROLL_PTR identifies the record.
Both when copying the old table and when writing log about changes to
the old table, we must map "old" DB_TRX_ID to 0. "old" here is simply
"older than the trx_id of the ALTER TABLE transaction", because
the MDL_EXCLUSIVE (and exclusive InnoDB table lock) in
ha_innobase::prepare_inplace_alter_table() forces any transactions
accessing the table to commit or rollback. So, we know that we can
safely reset any DB_TRX_ID in the table that is older than the
transaction ID of the ALTER TABLE, because the undo log history would be
lost in a table-rebuilding ALTER.
Note: After a table-rebuilding online ALTER TABLE, the rebuilt table
may end up containing some nonzero DB_TRX_ID columns. The apply logic
identifies the rows by the combination of PRIMARY KEY and DB_TRX_ID.
These nonzero DB_TRX_ID would necessarily refer to concurrent DML
operations that were started during ha_innobase::inplace_alter_table().
row_log_allocate(): Add a parameter for the ALTER TABLE transaction.
row_log_t::min_trx: The ALTER TABLE transaction ID.
trx_id_check(): A debug function to check that DB_TRX_ID makes sense
(is either 0 or bigger than the ALTER TABLE transaction ID).
reset_trx_id[]: The reset DB_TRX_ID,DB_ROLL_PTR columns.
row_log_table_delete(), row_log_table_get_pk(): Reset the
DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE transaction.
row_log_table_apply_delete(), row_log_table_apply_update():
Assert trx_id_check().
row_merge_insert_index_tuples(): Remove the unused parameter trx_id.
row_merge_read_clustered_index(): In a table-rebuilding ALTER,
reset the DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE
transaction. Assert trx_id_check() on clustered index records that
are being buffered.
2017-08-31 18:03:50 +03:00
|
|
|
ut_d(trx_id_check(rec_trx_id, log->min_trx));
|
|
|
|
ut_d(trx_id_check(mrec_trx_id, log->min_trx));
|
2014-05-06 21:13:16 +02:00
|
|
|
|
|
|
|
ut_ad(rec_get_nth_field(mrec, moffsets, trx_id_col + 1, &len)
|
|
|
|
== mrec_trx_id + DATA_TRX_ID_LEN);
|
|
|
|
ut_ad(len == DATA_ROLL_PTR_LEN);
|
|
|
|
ut_ad(rec_get_nth_field(btr_pcur_get_rec(&pcur), offsets,
|
|
|
|
trx_id_col + 1, &len)
|
|
|
|
== rec_trx_id + DATA_TRX_ID_LEN);
|
|
|
|
ut_ad(len == DATA_ROLL_PTR_LEN);
|
|
|
|
|
|
|
|
if (memcmp(mrec_trx_id, rec_trx_id,
|
|
|
|
DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN)) {
|
|
|
|
/* The ROW_T_DELETE was logged for a different
|
|
|
|
PRIMARY KEY,DB_TRX_ID,DB_ROLL_PTR.
|
|
|
|
This is possible if a ROW_T_INSERT was skipped
|
|
|
|
or a ROW_T_UPDATE was interpreted as ROW_T_DELETE
|
|
|
|
because some BLOBs were missing due to
|
|
|
|
(1) rolling back the initial insert, or
|
|
|
|
(2) purging the BLOB for a later ROW_T_DELETE
|
|
|
|
(3) purging 'old values' for a later ROW_T_UPDATE
|
|
|
|
or ROW_T_DELETE. */
|
|
|
|
ut_ad(!log->same_pk);
|
2013-03-26 00:03:13 +02:00
|
|
|
goto all_done;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-10 20:54:09 +05:30
|
|
|
return row_log_table_apply_delete_low(&pcur, offsets, heap, &mtr);
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************//**
|
|
|
|
Replays an update operation on a table that was rebuilt.
|
|
|
|
@return DB_SUCCESS or error code */
|
2016-06-21 14:21:03 +02:00
|
|
|
static MY_ATTRIBUTE((nonnull, warn_unused_result))
|
2013-03-26 00:03:13 +02:00
|
|
|
dberr_t
|
|
|
|
row_log_table_apply_update(
|
|
|
|
/*=======================*/
|
|
|
|
que_thr_t* thr, /*!< in: query graph */
|
|
|
|
ulint new_trx_id_col, /*!< in: position of
|
|
|
|
DB_TRX_ID in the new
|
|
|
|
clustered index */
|
|
|
|
const mrec_t* mrec, /*!< in: new value */
|
2020-04-28 10:46:51 +10:00
|
|
|
const rec_offs* offsets, /*!< in: offsets of mrec */
|
2013-03-26 00:03:13 +02:00
|
|
|
mem_heap_t* offsets_heap, /*!< in/out: memory heap
|
|
|
|
that can be emptied */
|
|
|
|
mem_heap_t* heap, /*!< in/out: memory heap */
|
|
|
|
row_merge_dup_t* dup, /*!< in/out: for reporting
|
|
|
|
duplicate key errors */
|
|
|
|
const dtuple_t* old_pk) /*!< in: PRIMARY KEY and
|
|
|
|
DB_TRX_ID,DB_ROLL_PTR
|
|
|
|
of the old value,
|
|
|
|
or PRIMARY KEY if same_pk */
|
|
|
|
{
|
2018-06-25 18:16:29 +05:30
|
|
|
row_log_t* log = dup->index->online_log;
|
2013-03-26 00:03:13 +02:00
|
|
|
const dtuple_t* row;
|
|
|
|
dict_index_t* index = dict_table_get_first_index(log->table);
|
|
|
|
mtr_t mtr;
|
|
|
|
btr_pcur_t pcur;
|
|
|
|
dberr_t error;
|
2015-12-13 10:11:49 +01:00
|
|
|
ulint n_index = 0;
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
ut_ad(dtuple_get_n_fields_cmp(old_pk)
|
|
|
|
== dict_index_get_n_unique(index));
|
|
|
|
ut_ad(dtuple_get_n_fields(old_pk)
|
|
|
|
== dict_index_get_n_unique(index)
|
2014-05-06 21:13:16 +02:00
|
|
|
+ (log->same_pk ? 0 : 2));
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
row = row_log_table_apply_convert_mrec(
|
2017-08-07 23:44:45 +03:00
|
|
|
mrec, dup->index, offsets, log, heap, &error);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2014-05-06 21:13:16 +02:00
|
|
|
switch (error) {
|
|
|
|
case DB_MISSING_HISTORY:
|
|
|
|
/* The record contained BLOBs that are now missing. */
|
|
|
|
ut_ad(log->blobs);
|
|
|
|
/* Whether or not we are updating the PRIMARY KEY, we
|
|
|
|
know that there should be a subsequent
|
|
|
|
ROW_T_DELETE for rolling back a preceding ROW_T_INSERT,
|
|
|
|
overriding this ROW_T_UPDATE record. (*1)
|
|
|
|
|
|
|
|
This allows us to interpret this ROW_T_UPDATE
|
|
|
|
as ROW_T_DELETE.
|
|
|
|
|
|
|
|
When applying the subsequent ROW_T_DELETE, no matching
|
|
|
|
record will be found. */
|
2017-05-16 20:08:47 +03:00
|
|
|
/* fall through */
|
2014-05-06 21:13:16 +02:00
|
|
|
case DB_SUCCESS:
|
|
|
|
ut_ad(row != NULL);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ut_ad(0);
|
2019-08-30 12:51:37 +03:00
|
|
|
/* fall through */
|
2014-05-06 21:13:16 +02:00
|
|
|
case DB_INVALID_NULL:
|
|
|
|
ut_ad(row == NULL);
|
2013-03-26 00:03:13 +02:00
|
|
|
return(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
mtr_start(&mtr);
|
2018-03-22 15:30:54 +02:00
|
|
|
index->set_modified(mtr);
|
2013-03-26 00:03:13 +02:00
|
|
|
btr_pcur_open(index, old_pk, PAGE_CUR_LE,
|
|
|
|
BTR_MODIFY_TREE, &pcur, &mtr);
|
|
|
|
#ifdef UNIV_DEBUG
|
|
|
|
switch (btr_pcur_get_btr_cur(&pcur)->flag) {
|
|
|
|
case BTR_CUR_DELETE_REF:
|
|
|
|
case BTR_CUR_DEL_MARK_IBUF:
|
|
|
|
case BTR_CUR_DELETE_IBUF:
|
|
|
|
case BTR_CUR_INSERT_TO_IBUF:
|
|
|
|
ut_ad(0);/* We did not request buffering. */
|
|
|
|
case BTR_CUR_HASH:
|
|
|
|
case BTR_CUR_HASH_FAIL:
|
|
|
|
case BTR_CUR_BINARY:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
#endif /* UNIV_DEBUG */
|
|
|
|
|
|
|
|
if (page_rec_is_infimum(btr_pcur_get_rec(&pcur))
|
|
|
|
|| btr_pcur_get_low_match(&pcur) < index->n_uniq) {
|
2014-05-06 21:13:16 +02:00
|
|
|
/* The record was not found. This should only happen
|
|
|
|
when an earlier ROW_T_INSERT or ROW_T_UPDATE was
|
|
|
|
diverted because BLOBs were freed when the insert was
|
|
|
|
later rolled back. */
|
|
|
|
|
|
|
|
ut_ad(log->blobs);
|
|
|
|
|
|
|
|
if (error == DB_SUCCESS) {
|
|
|
|
/* An earlier ROW_T_INSERT could have been
|
|
|
|
skipped because of a missing BLOB, like this:
|
|
|
|
|
|
|
|
BEGIN;
|
|
|
|
INSERT INTO t SET blob_col='blob value';
|
|
|
|
UPDATE t SET blob_col='';
|
|
|
|
ROLLBACK;
|
|
|
|
|
|
|
|
This would generate the following records:
|
|
|
|
ROW_T_INSERT (referring to 'blob value')
|
|
|
|
ROW_T_UPDATE
|
|
|
|
ROW_T_UPDATE (referring to 'blob value')
|
|
|
|
ROW_T_DELETE
|
|
|
|
[ROLLBACK removes the 'blob value']
|
|
|
|
|
|
|
|
The ROW_T_INSERT would have been skipped
|
|
|
|
because of a missing BLOB. Now we are
|
|
|
|
executing the first ROW_T_UPDATE.
|
|
|
|
The second ROW_T_UPDATE (for the ROLLBACK)
|
|
|
|
would be interpreted as ROW_T_DELETE, because
|
|
|
|
the BLOB would be missing.
|
|
|
|
|
|
|
|
We could probably assume that the transaction
|
|
|
|
has been rolled back and simply skip the
|
|
|
|
'insert' part of this ROW_T_UPDATE record.
|
|
|
|
However, there might be some complex scenario
|
|
|
|
that could interfere with such a shortcut.
|
|
|
|
So, we will insert the row (and risk
|
|
|
|
introducing a bogus duplicate key error
|
|
|
|
for the ALTER TABLE), and a subsequent
|
|
|
|
ROW_T_UPDATE or ROW_T_DELETE will delete it. */
|
|
|
|
mtr_commit(&mtr);
|
|
|
|
error = row_log_table_apply_insert_low(
|
2017-08-07 23:44:45 +03:00
|
|
|
thr, row, offsets_heap, heap, dup);
|
2014-05-06 21:13:16 +02:00
|
|
|
} else {
|
|
|
|
/* Some BLOBs are missing, so we are interpreting
|
|
|
|
this ROW_T_UPDATE as ROW_T_DELETE (see *1).
|
|
|
|
Because the record was not found, we do nothing. */
|
|
|
|
ut_ad(error == DB_MISSING_HISTORY);
|
|
|
|
error = DB_SUCCESS;
|
2014-02-26 19:23:04 +01:00
|
|
|
func_exit:
|
2014-05-06 21:13:16 +02:00
|
|
|
mtr_commit(&mtr);
|
|
|
|
}
|
2014-02-26 19:23:04 +01:00
|
|
|
func_exit_committed:
|
2016-08-12 11:17:45 +03:00
|
|
|
ut_ad(mtr.has_committed());
|
2022-09-22 11:18:00 +03:00
|
|
|
ut_free(pcur.old_rec_buf);
|
2014-02-26 19:23:04 +01:00
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
if (error != DB_SUCCESS) {
|
|
|
|
/* Report the erroneous row using the new
|
|
|
|
version of the table. */
|
|
|
|
innobase_row_to_mysql(dup->table, log->table, row);
|
|
|
|
}
|
|
|
|
|
|
|
|
return(error);
|
|
|
|
}
|
|
|
|
|
2014-05-06 21:13:16 +02:00
|
|
|
/* Prepare to update (or delete) the record. */
|
2020-04-28 10:46:51 +10:00
|
|
|
rec_offs* cur_offsets = rec_get_offsets(
|
2021-04-14 10:33:59 +03:00
|
|
|
btr_pcur_get_rec(&pcur), index, nullptr, index->n_core_fields,
|
2017-09-19 19:20:11 +03:00
|
|
|
ULINT_UNDEFINED, &offsets_heap);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2014-05-06 21:13:16 +02:00
|
|
|
if (!log->same_pk) {
|
|
|
|
/* Only update the record if DB_TRX_ID,DB_ROLL_PTR match what
|
|
|
|
was buffered. */
|
|
|
|
ulint len;
|
MDEV-13654 Various crashes due to DB_TRX_ID mismatch in table-rebuilding ALTER TABLE…LOCK=NONE
After MDEV-12288 and MDEV-13536, the DB_TRX_ID of old clustered index
records for which no history is available should be reset to 0.
This caused crashes in online table-rebuilding ALTER, because the
row_log_table_apply() is built on the assumption that the PRIMARY KEY
together with DB_TRX_ID,DB_ROLL_PTR identifies the record.
Both when copying the old table and when writing log about changes to
the old table, we must map "old" DB_TRX_ID to 0. "old" here is simply
"older than the trx_id of the ALTER TABLE transaction", because
the MDL_EXCLUSIVE (and exclusive InnoDB table lock) in
ha_innobase::prepare_inplace_alter_table() forces any transactions
accessing the table to commit or rollback. So, we know that we can
safely reset any DB_TRX_ID in the table that is older than the
transaction ID of the ALTER TABLE, because the undo log history would be
lost in a table-rebuilding ALTER.
Note: After a table-rebuilding online ALTER TABLE, the rebuilt table
may end up containing some nonzero DB_TRX_ID columns. The apply logic
identifies the rows by the combination of PRIMARY KEY and DB_TRX_ID.
These nonzero DB_TRX_ID would necessarily refer to concurrent DML
operations that were started during ha_innobase::inplace_alter_table().
row_log_allocate(): Add a parameter for the ALTER TABLE transaction.
row_log_t::min_trx: The ALTER TABLE transaction ID.
trx_id_check(): A debug function to check that DB_TRX_ID makes sense
(is either 0 or bigger than the ALTER TABLE transaction ID).
reset_trx_id[]: The reset DB_TRX_ID,DB_ROLL_PTR columns.
row_log_table_delete(), row_log_table_get_pk(): Reset the
DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE transaction.
row_log_table_apply_delete(), row_log_table_apply_update():
Assert trx_id_check().
row_merge_insert_index_tuples(): Remove the unused parameter trx_id.
row_merge_read_clustered_index(): In a table-rebuilding ALTER,
reset the DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE
transaction. Assert trx_id_check() on clustered index records that
are being buffered.
2017-08-31 18:03:50 +03:00
|
|
|
const byte* rec_trx_id
|
2014-05-06 21:13:16 +02:00
|
|
|
= rec_get_nth_field(btr_pcur_get_rec(&pcur),
|
|
|
|
cur_offsets, index->n_uniq, &len);
|
MDEV-13654 Various crashes due to DB_TRX_ID mismatch in table-rebuilding ALTER TABLE…LOCK=NONE
After MDEV-12288 and MDEV-13536, the DB_TRX_ID of old clustered index
records for which no history is available should be reset to 0.
This caused crashes in online table-rebuilding ALTER, because the
row_log_table_apply() is built on the assumption that the PRIMARY KEY
together with DB_TRX_ID,DB_ROLL_PTR identifies the record.
Both when copying the old table and when writing log about changes to
the old table, we must map "old" DB_TRX_ID to 0. "old" here is simply
"older than the trx_id of the ALTER TABLE transaction", because
the MDL_EXCLUSIVE (and exclusive InnoDB table lock) in
ha_innobase::prepare_inplace_alter_table() forces any transactions
accessing the table to commit or rollback. So, we know that we can
safely reset any DB_TRX_ID in the table that is older than the
transaction ID of the ALTER TABLE, because the undo log history would be
lost in a table-rebuilding ALTER.
Note: After a table-rebuilding online ALTER TABLE, the rebuilt table
may end up containing some nonzero DB_TRX_ID columns. The apply logic
identifies the rows by the combination of PRIMARY KEY and DB_TRX_ID.
These nonzero DB_TRX_ID would necessarily refer to concurrent DML
operations that were started during ha_innobase::inplace_alter_table().
row_log_allocate(): Add a parameter for the ALTER TABLE transaction.
row_log_t::min_trx: The ALTER TABLE transaction ID.
trx_id_check(): A debug function to check that DB_TRX_ID makes sense
(is either 0 or bigger than the ALTER TABLE transaction ID).
reset_trx_id[]: The reset DB_TRX_ID,DB_ROLL_PTR columns.
row_log_table_delete(), row_log_table_get_pk(): Reset the
DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE transaction.
row_log_table_apply_delete(), row_log_table_apply_update():
Assert trx_id_check().
row_merge_insert_index_tuples(): Remove the unused parameter trx_id.
row_merge_read_clustered_index(): In a table-rebuilding ALTER,
reset the DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE
transaction. Assert trx_id_check() on clustered index records that
are being buffered.
2017-08-31 18:03:50 +03:00
|
|
|
const dfield_t* old_pk_trx_id
|
|
|
|
= dtuple_get_nth_field(old_pk, index->n_uniq);
|
2014-05-06 21:13:16 +02:00
|
|
|
ut_ad(len == DATA_TRX_ID_LEN);
|
MDEV-13654 Various crashes due to DB_TRX_ID mismatch in table-rebuilding ALTER TABLE…LOCK=NONE
After MDEV-12288 and MDEV-13536, the DB_TRX_ID of old clustered index
records for which no history is available should be reset to 0.
This caused crashes in online table-rebuilding ALTER, because the
row_log_table_apply() is built on the assumption that the PRIMARY KEY
together with DB_TRX_ID,DB_ROLL_PTR identifies the record.
Both when copying the old table and when writing log about changes to
the old table, we must map "old" DB_TRX_ID to 0. "old" here is simply
"older than the trx_id of the ALTER TABLE transaction", because
the MDL_EXCLUSIVE (and exclusive InnoDB table lock) in
ha_innobase::prepare_inplace_alter_table() forces any transactions
accessing the table to commit or rollback. So, we know that we can
safely reset any DB_TRX_ID in the table that is older than the
transaction ID of the ALTER TABLE, because the undo log history would be
lost in a table-rebuilding ALTER.
Note: After a table-rebuilding online ALTER TABLE, the rebuilt table
may end up containing some nonzero DB_TRX_ID columns. The apply logic
identifies the rows by the combination of PRIMARY KEY and DB_TRX_ID.
These nonzero DB_TRX_ID would necessarily refer to concurrent DML
operations that were started during ha_innobase::inplace_alter_table().
row_log_allocate(): Add a parameter for the ALTER TABLE transaction.
row_log_t::min_trx: The ALTER TABLE transaction ID.
trx_id_check(): A debug function to check that DB_TRX_ID makes sense
(is either 0 or bigger than the ALTER TABLE transaction ID).
reset_trx_id[]: The reset DB_TRX_ID,DB_ROLL_PTR columns.
row_log_table_delete(), row_log_table_get_pk(): Reset the
DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE transaction.
row_log_table_apply_delete(), row_log_table_apply_update():
Assert trx_id_check().
row_merge_insert_index_tuples(): Remove the unused parameter trx_id.
row_merge_read_clustered_index(): In a table-rebuilding ALTER,
reset the DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE
transaction. Assert trx_id_check() on clustered index records that
are being buffered.
2017-08-31 18:03:50 +03:00
|
|
|
ut_d(trx_id_check(rec_trx_id, log->min_trx));
|
|
|
|
ut_ad(old_pk_trx_id->len == DATA_TRX_ID_LEN);
|
|
|
|
ut_ad(old_pk_trx_id[1].len == DATA_ROLL_PTR_LEN);
|
|
|
|
ut_ad(DATA_TRX_ID_LEN
|
|
|
|
+ static_cast<const char*>(old_pk_trx_id->data)
|
|
|
|
== old_pk_trx_id[1].data);
|
|
|
|
ut_d(trx_id_check(old_pk_trx_id->data, log->min_trx));
|
|
|
|
|
|
|
|
if (memcmp(rec_trx_id, old_pk_trx_id->data,
|
2014-05-06 21:13:16 +02:00
|
|
|
DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN)) {
|
|
|
|
/* The ROW_T_UPDATE was logged for a different
|
|
|
|
DB_TRX_ID,DB_ROLL_PTR. This is possible if an
|
|
|
|
earlier ROW_T_INSERT or ROW_T_UPDATE was diverted
|
|
|
|
because some BLOBs were missing due to rolling
|
|
|
|
back the initial insert or due to purging
|
|
|
|
the old BLOB values of an update. */
|
|
|
|
ut_ad(log->blobs);
|
|
|
|
if (error != DB_SUCCESS) {
|
|
|
|
ut_ad(error == DB_MISSING_HISTORY);
|
|
|
|
/* Some BLOBs are missing, so we are
|
|
|
|
interpreting this ROW_T_UPDATE as
|
|
|
|
ROW_T_DELETE (see *1).
|
|
|
|
Because this is a different row,
|
|
|
|
we will do nothing. */
|
|
|
|
error = DB_SUCCESS;
|
|
|
|
} else {
|
|
|
|
/* Because the user record is missing due to
|
|
|
|
BLOBs that were missing when processing
|
|
|
|
an earlier log record, we should
|
|
|
|
interpret the ROW_T_UPDATE as ROW_T_INSERT.
|
|
|
|
However, there is a different user record
|
|
|
|
with the same PRIMARY KEY value already. */
|
|
|
|
error = DB_DUPLICATE_KEY;
|
|
|
|
}
|
|
|
|
|
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (error != DB_SUCCESS) {
|
|
|
|
ut_ad(error == DB_MISSING_HISTORY);
|
|
|
|
ut_ad(log->blobs);
|
|
|
|
/* Some BLOBs are missing, so we are interpreting
|
|
|
|
this ROW_T_UPDATE as ROW_T_DELETE (see *1). */
|
|
|
|
error = row_log_table_apply_delete_low(
|
2018-01-10 20:54:09 +05:30
|
|
|
&pcur, cur_offsets, heap, &mtr);
|
2014-05-06 21:13:16 +02:00
|
|
|
goto func_exit_committed;
|
|
|
|
}
|
|
|
|
|
2016-12-28 17:44:16 +05:30
|
|
|
dtuple_t* entry = row_build_index_entry_low(
|
MDEV-13795/MDEV-14332 Corruption during online table-rebuilding ALTER when VIRTUAL columns exist
When MySQL 5.7 introduced indexed virtual columns, it introduced
several bugs into the online table-rebuilding ALTER, that is,
the row_log_table_apply() family of functions.
The online_log format that was introduced for online table-rebuilding
ALTER in MySQL 5.6 should be sufficient. Ideally, any indexed virtual
column values would be evaluated based on the log records in the temporary
file. There is no need to log virtual column values.
(For ADD INDEX, that is row_log_apply(), we always must log the values of
the keys, no matter if the columns are virtual.)
Because omitting the virtual column values removes any chance of
row_log_table_apply() working with indexed virtual columns, we
will for now refuse LOCK=NONE in table-rebuilding ALTER operations
when indexes on virtual columns exist. This restriction would be
lifted in MDEV-14341.
innobase_indexed_virtual_exist(): New predicate, to determine if
indexed virtual columns exist in a table definition.
ha_innobase::check_if_supported_inplace_alter(): Refuse online rebuild
if indexed virtual columns exist.
rec_get_converted_size_temp_v(), rec_convert_dtuple_to_temp_v(): Remove.
row_log_table_delete(), row_log_table_update(, row_log_table_insert():
Remove parameters for virtual columns.
trx_undo_read_v_rows(): Remove the col_map parameter.
row_log_table_apply(): Do not deal with virtual columns.
2017-11-09 23:01:44 +02:00
|
|
|
row, NULL, index, heap, ROW_BUILD_NORMAL);
|
2016-08-12 11:17:45 +03:00
|
|
|
upd_t* update = row_upd_build_difference_binary(
|
2013-03-26 00:03:13 +02:00
|
|
|
index, entry, btr_pcur_get_rec(&pcur), cur_offsets,
|
2022-10-10 19:41:09 +03:00
|
|
|
false, false, NULL, heap, dup->table, &error);
|
2018-10-10 18:05:02 +05:30
|
|
|
if (error != DB_SUCCESS) {
|
|
|
|
goto func_exit;
|
|
|
|
}
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
if (!update->n_fields) {
|
|
|
|
/* Nothing to do. */
|
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
|
2014-02-26 19:23:04 +01:00
|
|
|
const bool pk_updated
|
|
|
|
= upd_get_nth_field(update, 0)->field_no < new_trx_id_col;
|
|
|
|
|
|
|
|
if (pk_updated || rec_offs_any_extern(cur_offsets)) {
|
2013-03-26 00:03:13 +02:00
|
|
|
/* If the record contains any externally stored
|
|
|
|
columns, perform the update by delete and insert,
|
|
|
|
because we will not write any undo log that would
|
|
|
|
allow purge to free any orphaned externally stored
|
|
|
|
columns. */
|
|
|
|
|
2014-05-06 21:13:16 +02:00
|
|
|
if (pk_updated && log->same_pk) {
|
2013-03-26 00:03:13 +02:00
|
|
|
/* The ROW_T_UPDATE log record should only be
|
|
|
|
written when the PRIMARY KEY fields of the
|
|
|
|
record did not change in the old table. We
|
|
|
|
can only get a change of PRIMARY KEY columns
|
|
|
|
in the rebuilt table if the PRIMARY KEY was
|
|
|
|
redefined (!same_pk). */
|
|
|
|
ut_ad(0);
|
|
|
|
error = DB_CORRUPTION;
|
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
|
2014-02-26 19:23:04 +01:00
|
|
|
error = row_log_table_apply_delete_low(
|
2018-01-10 20:54:09 +05:30
|
|
|
&pcur, cur_offsets, heap, &mtr);
|
2016-08-12 11:17:45 +03:00
|
|
|
ut_ad(mtr.has_committed());
|
2014-02-26 19:23:04 +01:00
|
|
|
|
|
|
|
if (error == DB_SUCCESS) {
|
|
|
|
error = row_log_table_apply_insert_low(
|
2017-08-07 23:44:45 +03:00
|
|
|
thr, row, offsets_heap, heap, dup);
|
2014-02-26 19:23:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
goto func_exit_committed;
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
dtuple_t* old_row;
|
|
|
|
row_ext_t* old_ext;
|
|
|
|
|
|
|
|
if (dict_table_get_next_index(index)) {
|
|
|
|
/* Construct the row corresponding to the old value of
|
|
|
|
the record. */
|
|
|
|
old_row = row_build(
|
|
|
|
ROW_COPY_DATA, index, btr_pcur_get_rec(&pcur),
|
|
|
|
cur_offsets, NULL, NULL, NULL, &old_ext, heap);
|
|
|
|
ut_ad(old_row);
|
2016-08-12 11:17:45 +03:00
|
|
|
|
2017-02-16 09:16:11 +02:00
|
|
|
DBUG_LOG("ib_alter_table",
|
|
|
|
"update table " << index->table->id
|
|
|
|
<< " (index " << index->id
|
|
|
|
<< ": " << rec_printer(old_row).str()
|
|
|
|
<< " to " << rec_printer(row).str());
|
2013-03-26 00:03:13 +02:00
|
|
|
} else {
|
|
|
|
old_row = NULL;
|
|
|
|
old_ext = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
big_rec_t* big_rec;
|
|
|
|
|
|
|
|
error = btr_cur_pessimistic_update(
|
|
|
|
BTR_CREATE_FLAG | BTR_NO_LOCKING_FLAG
|
|
|
|
| BTR_NO_UNDO_LOG_FLAG | BTR_KEEP_SYS_FLAG
|
|
|
|
| BTR_KEEP_POS_FLAG,
|
|
|
|
btr_pcur_get_btr_cur(&pcur),
|
|
|
|
&cur_offsets, &offsets_heap, heap, &big_rec,
|
2014-02-01 09:33:26 +01:00
|
|
|
update, 0, thr, 0, &mtr);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
if (big_rec) {
|
|
|
|
if (error == DB_SUCCESS) {
|
|
|
|
error = btr_store_big_rec_extern_fields(
|
2017-10-11 11:41:31 +03:00
|
|
|
&pcur, cur_offsets, big_rec, &mtr,
|
2016-08-12 11:17:45 +03:00
|
|
|
BTR_STORE_UPDATE);
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
dtuple_big_rec_free(big_rec);
|
|
|
|
}
|
|
|
|
|
2017-12-21 17:19:13 +02:00
|
|
|
for (n_index += index->type != DICT_CLUSTERED;
|
|
|
|
(index = dict_table_get_next_index(index)); n_index++) {
|
2013-03-26 00:03:13 +02:00
|
|
|
if (index->type & DICT_FTS) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-12-21 17:19:13 +02:00
|
|
|
if (error != DB_SUCCESS) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
if (!row_upd_changes_ord_field_binary(
|
|
|
|
index, update, thr, old_row, NULL)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
if (dict_index_has_virtual(index)) {
|
|
|
|
dtuple_copy_v_fields(old_row, old_pk);
|
|
|
|
}
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
mtr_commit(&mtr);
|
|
|
|
|
|
|
|
entry = row_build_index_entry(old_row, old_ext, index, heap);
|
|
|
|
if (!entry) {
|
|
|
|
ut_ad(0);
|
2022-09-22 11:18:00 +03:00
|
|
|
error = DB_CORRUPTION;
|
|
|
|
goto func_exit_committed;
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
mtr_start(&mtr);
|
2018-03-22 15:30:54 +02:00
|
|
|
index->set_modified(mtr);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2022-11-10 12:09:16 +05:30
|
|
|
ut_free(pcur.old_rec_buf);
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
if (ROW_FOUND != row_search_index_entry(
|
|
|
|
index, entry, BTR_MODIFY_TREE, &pcur, &mtr)) {
|
|
|
|
ut_ad(0);
|
|
|
|
error = DB_CORRUPTION;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
btr_cur_pessimistic_delete(
|
|
|
|
&error, FALSE, btr_pcur_get_btr_cur(&pcur),
|
2016-08-12 11:17:45 +03:00
|
|
|
BTR_CREATE_FLAG, false, &mtr);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
if (error != DB_SUCCESS) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
mtr_commit(&mtr);
|
|
|
|
|
|
|
|
entry = row_build_index_entry(row, NULL, index, heap);
|
|
|
|
error = row_ins_sec_index_entry_low(
|
|
|
|
BTR_CREATE_FLAG | BTR_NO_LOCKING_FLAG
|
|
|
|
| BTR_NO_UNDO_LOG_FLAG | BTR_KEEP_SYS_FLAG,
|
|
|
|
BTR_MODIFY_TREE, index, offsets_heap, heap,
|
2019-09-23 10:25:34 +03:00
|
|
|
entry, thr_get_trx(thr)->id, thr);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2015-12-13 10:11:49 +01:00
|
|
|
/* Report correct index name for duplicate key error. */
|
|
|
|
if (error == DB_DUPLICATE_KEY) {
|
|
|
|
thr_get_trx(thr)->error_key_num = n_index;
|
|
|
|
}
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
mtr_start(&mtr);
|
2018-03-22 15:30:54 +02:00
|
|
|
index->set_modified(mtr);
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
2014-02-26 19:23:04 +01:00
|
|
|
goto func_exit;
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************//**
|
|
|
|
Applies an operation to a table that was rebuilt.
|
|
|
|
@return NULL on failure (mrec corruption) or when out of data;
|
|
|
|
pointer to next record on success */
|
2016-06-21 14:21:03 +02:00
|
|
|
static MY_ATTRIBUTE((nonnull, warn_unused_result))
|
2013-03-26 00:03:13 +02:00
|
|
|
const mrec_t*
|
|
|
|
row_log_table_apply_op(
|
|
|
|
/*===================*/
|
|
|
|
que_thr_t* thr, /*!< in: query graph */
|
|
|
|
ulint new_trx_id_col, /*!< in: position of
|
|
|
|
DB_TRX_ID in new index */
|
|
|
|
row_merge_dup_t* dup, /*!< in/out: for reporting
|
|
|
|
duplicate key errors */
|
|
|
|
dberr_t* error, /*!< out: DB_SUCCESS
|
|
|
|
or error code */
|
|
|
|
mem_heap_t* offsets_heap, /*!< in/out: memory heap
|
|
|
|
that can be emptied */
|
|
|
|
mem_heap_t* heap, /*!< in/out: memory heap */
|
|
|
|
const mrec_t* mrec, /*!< in: merge record */
|
|
|
|
const mrec_t* mrec_end, /*!< in: end of buffer */
|
2020-04-28 10:46:51 +10:00
|
|
|
rec_offs* offsets) /*!< in/out: work area
|
2013-03-26 00:03:13 +02:00
|
|
|
for parsing mrec */
|
|
|
|
{
|
2014-02-01 09:33:26 +01:00
|
|
|
row_log_t* log = dup->index->online_log;
|
2013-03-26 00:03:13 +02:00
|
|
|
dict_index_t* new_index = dict_table_get_first_index(log->table);
|
|
|
|
ulint extra_size;
|
|
|
|
const mrec_t* next_mrec;
|
|
|
|
dtuple_t* old_pk;
|
|
|
|
|
|
|
|
ut_ad(dict_index_is_clust(dup->index));
|
|
|
|
ut_ad(dup->index->table != log->table);
|
2014-02-01 09:33:26 +01:00
|
|
|
ut_ad(log->head.total <= log->tail.total);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
*error = DB_SUCCESS;
|
|
|
|
|
2018-01-10 20:54:09 +05:30
|
|
|
/* 3 = 1 (op type) + 1 (extra_size) + at least 1 byte payload */
|
2013-03-26 00:03:13 +02:00
|
|
|
if (mrec + 3 >= mrec_end) {
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
2018-05-03 15:17:16 +03:00
|
|
|
const bool is_instant = log->is_instant(dup->index);
|
2014-02-01 09:33:26 +01:00
|
|
|
const mrec_t* const mrec_start = mrec;
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
switch (*mrec++) {
|
|
|
|
default:
|
|
|
|
ut_ad(0);
|
|
|
|
*error = DB_CORRUPTION;
|
|
|
|
return(NULL);
|
|
|
|
case ROW_T_INSERT:
|
|
|
|
extra_size = *mrec++;
|
|
|
|
|
|
|
|
if (extra_size >= 0x80) {
|
|
|
|
/* Read another byte of extra_size. */
|
|
|
|
|
|
|
|
extra_size = (extra_size & 0x7f) << 8;
|
|
|
|
extra_size |= *mrec++;
|
|
|
|
}
|
|
|
|
|
|
|
|
mrec += extra_size;
|
|
|
|
|
2018-05-03 15:17:16 +03:00
|
|
|
ut_ad(extra_size || !is_instant);
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
if (mrec > mrec_end) {
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
rec_offs_set_n_fields(offsets, dup->index->n_fields);
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
rec_init_offsets_temp(mrec, dup->index, offsets,
|
2018-07-26 22:52:53 +03:00
|
|
|
log->n_core_fields, log->non_core_fields,
|
2018-05-03 15:17:16 +03:00
|
|
|
is_instant
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
? static_cast<rec_comp_status_t>(
|
|
|
|
*(mrec - extra_size))
|
|
|
|
: REC_STATUS_ORDINARY);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
next_mrec = mrec + rec_offs_data_size(offsets);
|
|
|
|
|
|
|
|
if (next_mrec > mrec_end) {
|
|
|
|
return(NULL);
|
|
|
|
} else {
|
2018-04-28 15:49:09 +03:00
|
|
|
log->head.total += ulint(next_mrec - mrec_start);
|
2013-03-26 00:03:13 +02:00
|
|
|
*error = row_log_table_apply_insert(
|
|
|
|
thr, mrec, offsets, offsets_heap,
|
2017-08-07 23:44:45 +03:00
|
|
|
heap, dup);
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ROW_T_DELETE:
|
2018-01-10 20:54:09 +05:30
|
|
|
/* 1 (extra_size) + at least 1 (payload) */
|
|
|
|
if (mrec + 2 >= mrec_end) {
|
2013-03-26 00:03:13 +02:00
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
extra_size = *mrec++;
|
|
|
|
ut_ad(mrec < mrec_end);
|
|
|
|
|
|
|
|
/* We assume extra_size < 0x100 for the PRIMARY KEY prefix.
|
|
|
|
For fixed-length PRIMARY key columns, it is 0. */
|
|
|
|
mrec += extra_size;
|
|
|
|
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
/* The ROW_T_DELETE record was converted by
|
|
|
|
rec_convert_dtuple_to_temp() using new_index. */
|
|
|
|
ut_ad(!new_index->is_instant());
|
2018-10-10 11:53:23 +03:00
|
|
|
rec_offs_set_n_fields(offsets, new_index->first_user_field());
|
2013-03-26 00:03:13 +02:00
|
|
|
rec_init_offsets_temp(mrec, new_index, offsets);
|
2018-01-10 20:54:09 +05:30
|
|
|
next_mrec = mrec + rec_offs_data_size(offsets);
|
2013-03-26 00:03:13 +02:00
|
|
|
if (next_mrec > mrec_end) {
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
2018-04-28 15:49:09 +03:00
|
|
|
log->head.total += ulint(next_mrec - mrec_start);
|
2014-02-01 09:33:26 +01:00
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
*error = row_log_table_apply_delete(
|
2018-05-01 01:10:37 +03:00
|
|
|
new_trx_id_col,
|
2018-01-10 20:54:09 +05:30
|
|
|
mrec, offsets, offsets_heap, heap, log);
|
2013-03-26 00:03:13 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case ROW_T_UPDATE:
|
|
|
|
/* Logically, the log entry consists of the
|
|
|
|
(PRIMARY KEY,DB_TRX_ID) of the old value (converted
|
|
|
|
to the new primary key definition) followed by
|
|
|
|
the new value in the old table definition. If the
|
|
|
|
definition of the columns belonging to PRIMARY KEY
|
|
|
|
is not changed, the log will only contain
|
|
|
|
DB_TRX_ID,new_row. */
|
|
|
|
|
2018-05-03 15:17:16 +03:00
|
|
|
if (log->same_pk) {
|
2013-03-26 00:03:13 +02:00
|
|
|
ut_ad(new_index->n_uniq == dup->index->n_uniq);
|
|
|
|
|
|
|
|
extra_size = *mrec++;
|
|
|
|
|
|
|
|
if (extra_size >= 0x80) {
|
|
|
|
/* Read another byte of extra_size. */
|
|
|
|
|
|
|
|
extra_size = (extra_size & 0x7f) << 8;
|
|
|
|
extra_size |= *mrec++;
|
|
|
|
}
|
|
|
|
|
|
|
|
mrec += extra_size;
|
|
|
|
|
2018-05-03 15:17:16 +03:00
|
|
|
ut_ad(extra_size || !is_instant);
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
if (mrec > mrec_end) {
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
rec_offs_set_n_fields(offsets, dup->index->n_fields);
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
rec_init_offsets_temp(mrec, dup->index, offsets,
|
2018-05-03 15:17:16 +03:00
|
|
|
log->n_core_fields,
|
2018-07-26 22:52:53 +03:00
|
|
|
log->non_core_fields,
|
2018-05-03 15:17:16 +03:00
|
|
|
is_instant
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
? static_cast<rec_comp_status_t>(
|
|
|
|
*(mrec - extra_size))
|
|
|
|
: REC_STATUS_ORDINARY);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
next_mrec = mrec + rec_offs_data_size(offsets);
|
|
|
|
|
|
|
|
if (next_mrec > mrec_end) {
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
MDEV-13795/MDEV-14332 Corruption during online table-rebuilding ALTER when VIRTUAL columns exist
When MySQL 5.7 introduced indexed virtual columns, it introduced
several bugs into the online table-rebuilding ALTER, that is,
the row_log_table_apply() family of functions.
The online_log format that was introduced for online table-rebuilding
ALTER in MySQL 5.6 should be sufficient. Ideally, any indexed virtual
column values would be evaluated based on the log records in the temporary
file. There is no need to log virtual column values.
(For ADD INDEX, that is row_log_apply(), we always must log the values of
the keys, no matter if the columns are virtual.)
Because omitting the virtual column values removes any chance of
row_log_table_apply() working with indexed virtual columns, we
will for now refuse LOCK=NONE in table-rebuilding ALTER operations
when indexes on virtual columns exist. This restriction would be
lifted in MDEV-14341.
innobase_indexed_virtual_exist(): New predicate, to determine if
indexed virtual columns exist in a table definition.
ha_innobase::check_if_supported_inplace_alter(): Refuse online rebuild
if indexed virtual columns exist.
rec_get_converted_size_temp_v(), rec_convert_dtuple_to_temp_v(): Remove.
row_log_table_delete(), row_log_table_update(, row_log_table_insert():
Remove parameters for virtual columns.
trx_undo_read_v_rows(): Remove the col_map parameter.
row_log_table_apply(): Do not deal with virtual columns.
2017-11-09 23:01:44 +02:00
|
|
|
old_pk = dtuple_create(heap, new_index->n_uniq);
|
2013-03-26 00:03:13 +02:00
|
|
|
dict_index_copy_types(
|
|
|
|
old_pk, new_index, old_pk->n_fields);
|
|
|
|
|
|
|
|
/* Copy the PRIMARY KEY fields from mrec to old_pk. */
|
|
|
|
for (ulint i = 0; i < new_index->n_uniq; i++) {
|
|
|
|
const void* field;
|
|
|
|
ulint len;
|
|
|
|
dfield_t* dfield;
|
|
|
|
|
|
|
|
ut_ad(!rec_offs_nth_extern(offsets, i));
|
|
|
|
|
|
|
|
field = rec_get_nth_field(
|
|
|
|
mrec, offsets, i, &len);
|
|
|
|
ut_ad(len != UNIV_SQL_NULL);
|
|
|
|
|
|
|
|
dfield = dtuple_get_nth_field(old_pk, i);
|
|
|
|
dfield_set_data(dfield, field, len);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* We assume extra_size < 0x100
|
|
|
|
for the PRIMARY KEY prefix. */
|
|
|
|
mrec += *mrec + 1;
|
|
|
|
|
|
|
|
if (mrec > mrec_end) {
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get offsets for PRIMARY KEY,
|
|
|
|
DB_TRX_ID, DB_ROLL_PTR. */
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
/* The old_pk prefix was converted by
|
|
|
|
rec_convert_dtuple_to_temp() using new_index. */
|
|
|
|
ut_ad(!new_index->is_instant());
|
2018-04-28 15:49:09 +03:00
|
|
|
rec_offs_set_n_fields(offsets,
|
2018-10-10 11:53:23 +03:00
|
|
|
new_index->first_user_field());
|
2013-03-26 00:03:13 +02:00
|
|
|
rec_init_offsets_temp(mrec, new_index, offsets);
|
|
|
|
|
|
|
|
next_mrec = mrec + rec_offs_data_size(offsets);
|
|
|
|
if (next_mrec + 2 > mrec_end) {
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Copy the PRIMARY KEY fields and
|
|
|
|
DB_TRX_ID, DB_ROLL_PTR from mrec to old_pk. */
|
2018-10-10 11:53:23 +03:00
|
|
|
old_pk = dtuple_create(heap,
|
|
|
|
new_index->first_user_field());
|
2013-03-26 00:03:13 +02:00
|
|
|
dict_index_copy_types(old_pk, new_index,
|
|
|
|
old_pk->n_fields);
|
|
|
|
|
2018-10-10 11:53:23 +03:00
|
|
|
for (ulint i = 0; i < new_index->first_user_field();
|
2013-03-26 00:03:13 +02:00
|
|
|
i++) {
|
|
|
|
const void* field;
|
|
|
|
ulint len;
|
|
|
|
dfield_t* dfield;
|
|
|
|
|
|
|
|
ut_ad(!rec_offs_nth_extern(offsets, i));
|
|
|
|
|
|
|
|
field = rec_get_nth_field(
|
|
|
|
mrec, offsets, i, &len);
|
|
|
|
ut_ad(len != UNIV_SQL_NULL);
|
|
|
|
|
|
|
|
dfield = dtuple_get_nth_field(old_pk, i);
|
|
|
|
dfield_set_data(dfield, field, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
mrec = next_mrec;
|
|
|
|
|
|
|
|
/* Fetch the new value of the row as it was
|
|
|
|
in the old table definition. */
|
|
|
|
extra_size = *mrec++;
|
|
|
|
|
|
|
|
if (extra_size >= 0x80) {
|
|
|
|
/* Read another byte of extra_size. */
|
|
|
|
|
|
|
|
extra_size = (extra_size & 0x7f) << 8;
|
|
|
|
extra_size |= *mrec++;
|
|
|
|
}
|
|
|
|
|
|
|
|
mrec += extra_size;
|
|
|
|
|
2018-05-03 15:17:16 +03:00
|
|
|
ut_ad(extra_size || !is_instant);
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
if (mrec > mrec_end) {
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
rec_offs_set_n_fields(offsets, dup->index->n_fields);
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
rec_init_offsets_temp(mrec, dup->index, offsets,
|
2018-05-03 15:17:16 +03:00
|
|
|
log->n_core_fields,
|
2018-07-26 22:52:53 +03:00
|
|
|
log->non_core_fields,
|
2018-05-03 15:17:16 +03:00
|
|
|
is_instant
|
MDEV-11369 Instant ADD COLUMN for InnoDB
For InnoDB tables, adding, dropping and reordering columns has
required a rebuild of the table and all its indexes. Since MySQL 5.6
(and MariaDB 10.0) this has been supported online (LOCK=NONE), allowing
concurrent modification of the tables.
This work revises the InnoDB ROW_FORMAT=REDUNDANT, ROW_FORMAT=COMPACT
and ROW_FORMAT=DYNAMIC so that columns can be appended instantaneously,
with only minor changes performed to the table structure. The counter
innodb_instant_alter_column in INFORMATION_SCHEMA.GLOBAL_STATUS
is incremented whenever a table rebuild operation is converted into
an instant ADD COLUMN operation.
ROW_FORMAT=COMPRESSED tables will not support instant ADD COLUMN.
Some usability limitations will be addressed in subsequent work:
MDEV-13134 Introduce ALTER TABLE attributes ALGORITHM=NOCOPY
and ALGORITHM=INSTANT
MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE
The format of the clustered index (PRIMARY KEY) is changed as follows:
(1) The FIL_PAGE_TYPE of the root page will be FIL_PAGE_TYPE_INSTANT,
and a new field PAGE_INSTANT will contain the original number of fields
in the clustered index ('core' fields).
If instant ADD COLUMN has not been used or the table becomes empty,
or the very first instant ADD COLUMN operation is rolled back,
the fields PAGE_INSTANT and FIL_PAGE_TYPE will be reset
to 0 and FIL_PAGE_INDEX.
(2) A special 'default row' record is inserted into the leftmost leaf,
between the page infimum and the first user record. This record is
distinguished by the REC_INFO_MIN_REC_FLAG, and it is otherwise in the
same format as records that contain values for the instantly added
columns. This 'default row' always has the same number of fields as
the clustered index according to the table definition. The values of
'core' fields are to be ignored. For other fields, the 'default row'
will contain the default values as they were during the ALTER TABLE
statement. (If the column default values are changed later, those
values will only be stored in the .frm file. The 'default row' will
contain the original evaluated values, which must be the same for
every row.) The 'default row' must be completely hidden from
higher-level access routines. Assertions have been added to ensure
that no 'default row' is ever present in the adaptive hash index
or in locked records. The 'default row' is never delete-marked.
(3) In clustered index leaf page records, the number of fields must
reside between the number of 'core' fields (dict_index_t::n_core_fields
introduced in this work) and dict_index_t::n_fields. If the number
of fields is less than dict_index_t::n_fields, the missing fields
are replaced with the column value of the 'default row'.
Note: The number of fields in the record may shrink if some of the
last instantly added columns are updated to the value that is
in the 'default row'. The function btr_cur_trim() implements this
'compression' on update and rollback; dtuple::trim() implements it
on insert.
(4) In ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC records, the new
status value REC_STATUS_COLUMNS_ADDED will indicate the presence of
a new record header that will encode n_fields-n_core_fields-1 in
1 or 2 bytes. (In ROW_FORMAT=REDUNDANT records, the record header
always explicitly encodes the number of fields.)
We introduce the undo log record type TRX_UNDO_INSERT_DEFAULT for
covering the insert of the 'default row' record when instant ADD COLUMN
is used for the first time. Subsequent instant ADD COLUMN can use
TRX_UNDO_UPD_EXIST_REC.
This is joint work with Vin Chen (陈福荣) from Tencent. The design
that was discussed in April 2017 would not have allowed import or
export of data files, because instead of the 'default row' it would
have introduced a data dictionary table. The test
rpl.rpl_alter_instant is exactly as contributed in pull request #408.
The test innodb.instant_alter is based on a contributed test.
The redo log record format changes for ROW_FORMAT=DYNAMIC and
ROW_FORMAT=COMPACT are as contributed. (With this change present,
crash recovery from MariaDB 10.3.1 will fail in spectacular ways!)
Also the semantics of higher-level redo log records that modify the
PAGE_INSTANT field is changed. The redo log format version identifier
was already changed to LOG_HEADER_FORMAT_CURRENT=103 in MariaDB 10.3.1.
Everything else has been rewritten by me. Thanks to Elena Stepanova,
the code has been tested extensively.
When rolling back an instant ADD COLUMN operation, we must empty the
PAGE_FREE list after deleting or shortening the 'default row' record,
by calling either btr_page_empty() or btr_page_reorganize(). We must
know the size of each entry in the PAGE_FREE list. If rollback left a
freed copy of the 'default row' in the PAGE_FREE list, we would be
unable to determine its size (if it is in ROW_FORMAT=COMPACT or
ROW_FORMAT=DYNAMIC) because it would contain more fields than the
rolled-back definition of the clustered index.
UNIV_SQL_DEFAULT: A new special constant that designates an instantly
added column that is not present in the clustered index record.
len_is_stored(): Check if a length is an actual length. There are
two magic length values: UNIV_SQL_DEFAULT, UNIV_SQL_NULL.
dict_col_t::def_val: The 'default row' value of the column. If the
column is not added instantly, def_val.len will be UNIV_SQL_DEFAULT.
dict_col_t: Add the accessors is_virtual(), is_nullable(), is_instant(),
instant_value().
dict_col_t::remove_instant(): Remove the 'instant ADD' status of
a column.
dict_col_t::name(const dict_table_t& table): Replaces
dict_table_get_col_name().
dict_index_t::n_core_fields: The original number of fields.
For secondary indexes and if instant ADD COLUMN has not been used,
this will be equal to dict_index_t::n_fields.
dict_index_t::n_core_null_bytes: Number of bytes needed to
represent the null flags; usually equal to UT_BITS_IN_BYTES(n_nullable).
dict_index_t::NO_CORE_NULL_BYTES: Magic value signalling that
n_core_null_bytes was not initialized yet from the clustered index
root page.
dict_index_t: Add the accessors is_instant(), is_clust(),
get_n_nullable(), instant_field_value().
dict_index_t::instant_add_field(): Adjust clustered index metadata
for instant ADD COLUMN.
dict_index_t::remove_instant(): Remove the 'instant ADD' status
of a clustered index when the table becomes empty, or the very first
instant ADD COLUMN operation is rolled back.
dict_table_t: Add the accessors is_instant(), is_temporary(),
supports_instant().
dict_table_t::instant_add_column(): Adjust metadata for
instant ADD COLUMN.
dict_table_t::rollback_instant(): Adjust metadata on the rollback
of instant ADD COLUMN.
prepare_inplace_alter_table_dict(): First create the ctx->new_table,
and only then decide if the table really needs to be rebuilt.
We must split the creation of table or index metadata from the
creation of the dictionary table records and the creation of
the data. In this way, we can transform a table-rebuilding operation
into an instant ADD COLUMN operation. Dictionary objects will only
be added to cache when table rebuilding or index creation is needed.
The ctx->instant_table will never be added to cache.
dict_table_t::add_to_cache(): Modified and renamed from
dict_table_add_to_cache(). Do not modify the table metadata.
Let the callers invoke dict_table_add_system_columns() and if needed,
set can_be_evicted.
dict_create_sys_tables_tuple(), dict_create_table_step(): Omit the
system columns (which will now exist in the dict_table_t object
already at this point).
dict_create_table_step(): Expect the callers to invoke
dict_table_add_system_columns().
pars_create_table(): Before creating the table creation execution
graph, invoke dict_table_add_system_columns().
row_create_table_for_mysql(): Expect all callers to invoke
dict_table_add_system_columns().
create_index_dict(): Replaces row_merge_create_index_graph().
innodb_update_n_cols(): Renamed from innobase_update_n_virtual().
Call my_error() if an error occurs.
btr_cur_instant_init(), btr_cur_instant_init_low(),
btr_cur_instant_root_init():
Load additional metadata from the clustered index and set
dict_index_t::n_core_null_bytes. This is invoked
when table metadata is first loaded into the data dictionary.
dict_boot(): Initialize n_core_null_bytes for the four hard-coded
dictionary tables.
dict_create_index_step(): Initialize n_core_null_bytes. This is
executed as part of CREATE TABLE.
dict_index_build_internal_clust(): Initialize n_core_null_bytes to
NO_CORE_NULL_BYTES if table->supports_instant().
row_create_index_for_mysql(): Initialize n_core_null_bytes for
CREATE TEMPORARY TABLE.
commit_cache_norebuild(): Call the code to rename or enlarge columns
in the cache only if instant ADD COLUMN is not being used.
(Instant ADD COLUMN would copy all column metadata from
instant_table to old_table, including the names and lengths.)
PAGE_INSTANT: A new 13-bit field for storing dict_index_t::n_core_fields.
This is repurposing the 16-bit field PAGE_DIRECTION, of which only the
least significant 3 bits were used. The original byte containing
PAGE_DIRECTION will be accessible via the new constant PAGE_DIRECTION_B.
page_get_instant(), page_set_instant(): Accessors for the PAGE_INSTANT.
page_ptr_get_direction(), page_get_direction(),
page_ptr_set_direction(): Accessors for PAGE_DIRECTION.
page_direction_reset(): Reset PAGE_DIRECTION, PAGE_N_DIRECTION.
page_direction_increment(): Increment PAGE_N_DIRECTION
and set PAGE_DIRECTION.
rec_get_offsets(): Use the 'leaf' parameter for non-debug purposes,
and assume that heap_no is always set.
Initialize all dict_index_t::n_fields for ROW_FORMAT=REDUNDANT records,
even if the record contains fewer fields.
rec_offs_make_valid(): Add the parameter 'leaf'.
rec_copy_prefix_to_dtuple(): Assert that the tuple is only built
on the core fields. Instant ADD COLUMN only applies to the
clustered index, and we should never build a search key that has
more than the PRIMARY KEY and possibly DB_TRX_ID,DB_ROLL_PTR.
All these columns are always present.
dict_index_build_data_tuple(): Remove assertions that would be
duplicated in rec_copy_prefix_to_dtuple().
rec_init_offsets(): Support ROW_FORMAT=REDUNDANT records whose
number of fields is between n_core_fields and n_fields.
cmp_rec_rec_with_match(): Implement the comparison between two
MIN_REC_FLAG records.
trx_t::in_rollback: Make the field available in non-debug builds.
trx_start_for_ddl_low(): Remove dangerous error-tolerance.
A dictionary transaction must be flagged as such before it has generated
any undo log records. This is because trx_undo_assign_undo() will mark
the transaction as a dictionary transaction in the undo log header
right before the very first undo log record is being written.
btr_index_rec_validate(): Account for instant ADD COLUMN
row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.
row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.
dtuple_t::trim(index): Omit a redundant suffix of an index tuple right
before insert or update. After instant ADD COLUMN, if the last fields
of a clustered index tuple match the 'default row', there is no
need to store them. While trimming the entry, we must hold a page latch,
so that the table cannot be emptied and the 'default row' be deleted.
btr_cur_optimistic_update(), btr_cur_pessimistic_update(),
row_upd_clust_rec_by_insert(), row_ins_clust_index_entry_low():
Invoke dtuple_t::trim() if needed.
row_ins_clust_index_entry(): Restore dtuple_t::n_fields after calling
row_ins_clust_index_entry_low().
rec_get_converted_size(), rec_get_converted_size_comp(): Allow the number
of fields to be between n_core_fields and n_fields. Do not support
infimum,supremum. They are never supposed to be stored in dtuple_t,
because page creation nowadays uses a lower-level method for initializing
them.
rec_convert_dtuple_to_rec_comp(): Assign the status bits based on the
number of fields.
btr_cur_trim(): In an update, trim the index entry as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
row_log_table_apply_convert_mrec(): Replace 'default row' values if needed.
In the temporary file that is applied by row_log_table_apply(),
we must identify whether the records contain the extra header for
instantly added columns. For now, we will allocate an additional byte
for this for ROW_T_INSERT and ROW_T_UPDATE records when the source table
has been subject to instant ADD COLUMN. The ROW_T_DELETE records are
fine, as they will be converted and will only contain 'core' columns
(PRIMARY KEY and some system columns) that are converted from dtuple_t.
rec_get_converted_size_temp(), rec_init_offsets_temp(),
rec_convert_dtuple_to_temp(): Add the parameter 'status'.
REC_INFO_DEFAULT_ROW = REC_INFO_MIN_REC_FLAG | REC_STATUS_COLUMNS_ADDED:
An info_bits constant for distinguishing the 'default row' record.
rec_comp_status_t: An enum of the status bit values.
rec_leaf_format: An enum that replaces the bool parameter of
rec_init_offsets_comp_ordinary().
2017-10-06 07:00:05 +03:00
|
|
|
? static_cast<rec_comp_status_t>(
|
|
|
|
*(mrec - extra_size))
|
|
|
|
: REC_STATUS_ORDINARY);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
next_mrec = mrec + rec_offs_data_size(offsets);
|
|
|
|
|
|
|
|
if (next_mrec > mrec_end) {
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ut_ad(next_mrec <= mrec_end);
|
2018-04-28 15:49:09 +03:00
|
|
|
log->head.total += ulint(next_mrec - mrec_start);
|
2013-03-26 00:03:13 +02:00
|
|
|
dtuple_set_n_fields_cmp(old_pk, new_index->n_uniq);
|
|
|
|
|
2017-08-07 23:44:45 +03:00
|
|
|
*error = row_log_table_apply_update(
|
|
|
|
thr, new_trx_id_col,
|
|
|
|
mrec, offsets, offsets_heap, heap, dup, old_pk);
|
2013-03-26 00:03:13 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
ut_ad(log->head.total <= log->tail.total);
|
2013-03-26 00:03:13 +02:00
|
|
|
mem_heap_empty(offsets_heap);
|
|
|
|
mem_heap_empty(heap);
|
|
|
|
return(next_mrec);
|
|
|
|
}
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
#ifdef HAVE_PSI_STAGE_INTERFACE
|
|
|
|
/** Estimate how much an ALTER TABLE progress should be incremented per
|
|
|
|
one block of log applied.
|
|
|
|
For the other phases of ALTER TABLE we increment the progress with 1 per
|
|
|
|
page processed.
|
|
|
|
@return amount of abstract units to add to work_completed when one block
|
|
|
|
of log is applied.
|
|
|
|
*/
|
|
|
|
inline
|
|
|
|
ulint
|
|
|
|
row_log_progress_inc_per_block()
|
|
|
|
{
|
|
|
|
/* We must increment the progress once per page (as in
|
2019-02-06 19:50:11 +02:00
|
|
|
srv_page_size, default = innodb_page_size=16KiB).
|
|
|
|
One block here is srv_sort_buf_size (usually 1MiB). */
|
2018-04-27 14:26:43 +03:00
|
|
|
const ulint pages_per_block = std::max<ulint>(
|
|
|
|
ulint(srv_sort_buf_size >> srv_page_size_shift), 1);
|
2016-08-12 11:17:45 +03:00
|
|
|
|
|
|
|
/* Multiply by an artificial factor of 6 to even the pace with
|
|
|
|
the rest of the ALTER TABLE phases, they process page_size amount
|
|
|
|
of data faster. */
|
|
|
|
return(pages_per_block * 6);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Estimate how much work is to be done by the log apply phase
|
|
|
|
of an ALTER TABLE for this index.
|
|
|
|
@param[in] index index whose log to assess
|
|
|
|
@return work to be done by log-apply in abstract units
|
|
|
|
*/
|
|
|
|
ulint
|
|
|
|
row_log_estimate_work(
|
|
|
|
const dict_index_t* index)
|
|
|
|
{
|
|
|
|
if (index == NULL || index->online_log == NULL) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
const row_log_t* l = index->online_log;
|
|
|
|
const ulint bytes_left =
|
|
|
|
static_cast<ulint>(l->tail.total - l->head.total);
|
|
|
|
const ulint blocks_left = bytes_left / srv_sort_buf_size;
|
|
|
|
|
|
|
|
return(blocks_left * row_log_progress_inc_per_block());
|
|
|
|
}
|
|
|
|
#else /* HAVE_PSI_STAGE_INTERFACE */
|
|
|
|
inline
|
|
|
|
ulint
|
|
|
|
row_log_progress_inc_per_block()
|
|
|
|
{
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
#endif /* HAVE_PSI_STAGE_INTERFACE */
|
|
|
|
|
|
|
|
/** Applies operations to a table was rebuilt.
|
|
|
|
@param[in] thr query graph
|
|
|
|
@param[in,out] dup for reporting duplicate key errors
|
|
|
|
@param[in,out] stage performance schema accounting object, used by
|
|
|
|
ALTER TABLE. If not NULL, then stage->inc() will be called for each block
|
|
|
|
of log that is applied.
|
2013-03-26 00:03:13 +02:00
|
|
|
@return DB_SUCCESS, or error code on failure */
|
2016-09-06 09:43:16 +03:00
|
|
|
static MY_ATTRIBUTE((warn_unused_result))
|
2013-03-26 00:03:13 +02:00
|
|
|
dberr_t
|
|
|
|
row_log_table_apply_ops(
|
2016-08-12 11:17:45 +03:00
|
|
|
que_thr_t* thr,
|
|
|
|
row_merge_dup_t* dup,
|
|
|
|
ut_stage_alter_t* stage)
|
2013-03-26 00:03:13 +02:00
|
|
|
{
|
|
|
|
dberr_t error;
|
|
|
|
const mrec_t* mrec = NULL;
|
|
|
|
const mrec_t* next_mrec;
|
|
|
|
const mrec_t* mrec_end = NULL; /* silence bogus warning */
|
|
|
|
const mrec_t* next_mrec_end;
|
|
|
|
mem_heap_t* heap;
|
|
|
|
mem_heap_t* offsets_heap;
|
2020-04-28 10:46:51 +10:00
|
|
|
rec_offs* offsets;
|
2013-03-26 00:03:13 +02:00
|
|
|
bool has_index_lock;
|
|
|
|
dict_index_t* index = const_cast<dict_index_t*>(
|
|
|
|
dup->index);
|
|
|
|
dict_table_t* new_table = index->online_log->table;
|
|
|
|
dict_index_t* new_index = dict_table_get_first_index(
|
|
|
|
new_table);
|
|
|
|
const ulint i = 1 + REC_OFFS_HEADER_SIZE
|
2018-10-10 11:53:23 +03:00
|
|
|
+ std::max<ulint>(index->n_fields,
|
|
|
|
new_index->first_user_field());
|
2013-03-26 00:03:13 +02:00
|
|
|
const ulint new_trx_id_col = dict_col_get_clust_pos(
|
|
|
|
dict_table_get_sys_col(new_table, DATA_TRX_ID), new_index);
|
|
|
|
trx_t* trx = thr_get_trx(thr);
|
|
|
|
|
|
|
|
ut_ad(dict_index_is_clust(index));
|
|
|
|
ut_ad(dict_index_is_online_ddl(index));
|
|
|
|
ut_ad(trx->mysql_thd);
|
2016-08-12 11:17:45 +03:00
|
|
|
ut_ad(rw_lock_own(dict_index_get_lock(index), RW_LOCK_X));
|
2013-03-26 00:03:13 +02:00
|
|
|
ut_ad(!dict_index_is_online_ddl(new_index));
|
2017-08-07 23:44:45 +03:00
|
|
|
ut_ad(dict_col_get_clust_pos(
|
|
|
|
dict_table_get_sys_col(index->table, DATA_TRX_ID), index)
|
|
|
|
!= ULINT_UNDEFINED);
|
2013-03-26 00:03:13 +02:00
|
|
|
ut_ad(new_trx_id_col > 0);
|
|
|
|
ut_ad(new_trx_id_col != ULINT_UNDEFINED);
|
|
|
|
|
MDEV-20377: Make WITH_MSAN more usable
MemorySanitizer (clang -fsanitize=memory) requires that all code
be compiled with instrumentation enabled. The only exception is the
C runtime library. Failure to use instrumented libraries will cause
bogus messages about memory being uninitialized.
In WITH_MSAN builds, we must avoid calling getservbyname(),
because even though it is a standard library function, it is
not instrumented, not even in clang 10.
Note: Before MariaDB Server 10.5, ./mtr will typically fail
due to the old PCRE library, which was updated in MDEV-14024.
The following cmake options were tested on 10.5
in commit 94d0bb4dbeb28a94d1f87fdd55f4297ff3df0157:
cmake \
-DCMAKE_C_FLAGS='-march=native -O2' \
-DCMAKE_CXX_FLAGS='-stdlib=libc++ -march=native -O2' \
-DWITH_EMBEDDED_SERVER=OFF -DWITH_UNIT_TESTS=OFF -DCMAKE_BUILD_TYPE=Debug \
-DWITH_INNODB_{BZIP2,LZ4,LZMA,LZO,SNAPPY}=OFF \
-DPLUGIN_{ARCHIVE,TOKUDB,MROONGA,OQGRAPH,ROCKSDB,CONNECT,SPIDER}=NO \
-DWITH_SAFEMALLOC=OFF \
-DWITH_{ZLIB,SSL,PCRE}=bundled \
-DHAVE_LIBAIO_H=0 \
-DWITH_MSAN=ON
MEM_MAKE_DEFINED(): An alias for VALGRIND_MAKE_MEM_DEFINED()
and __msan_unpoison().
MEM_GET_VBITS(), MEM_SET_VBITS(): Aliases for
VALGRIND_GET_VBITS(), VALGRIND_SET_VBITS(), __msan_copy_shadow().
InnoDB: Replace the UNIV_MEM_ macros with corresponding MEM_ macros.
ut_crc32_8_hw(), ut_crc32_64_low_hw(): Use the compiler built-in
functions instead of inline assembler when building WITH_MSAN.
This will require at least -msse4.2 when building for IA-32 or AMD64.
The inline assembler would not be instrumented, and would thus cause
bogus failures.
2020-07-01 17:23:00 +03:00
|
|
|
MEM_UNDEFINED(&mrec_end, sizeof mrec_end);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2020-04-28 10:46:51 +10:00
|
|
|
offsets = static_cast<rec_offs*>(ut_malloc_nokey(i * sizeof *offsets));
|
MDEV-20950 Reduce size of record offsets
offset_t: this is a type which represents one record offset.
It's unsigned short int.
a lot of functions: replace ulint with offset_t
btr_pcur_restore_position_func(),
page_validate(),
row_ins_scan_sec_index_for_duplicate(),
row_upd_clust_rec_by_insert_inherit_func(),
row_vers_impl_x_locked_low(),
trx_undo_prev_version_build():
allocate record offsets on the stack instead of waiting for rec_get_offsets()
to allocate it from mem_heap_t. So, reducing memory allocations.
RECORD_OFFSET, INDEX_OFFSET:
now it's less convenient to store pointers in offset_t*
array. One pointer occupies now several offset_t. And those constant are start
indexes into array to places where to store pointer values
REC_OFFS_HEADER_SIZE: adjusted for the new reality
REC_OFFS_NORMAL_SIZE:
increase size from 100 to 300 which means less heap allocations.
And sizeof(offset_t[REC_OFFS_NORMAL_SIZE]) now is 600 bytes which
is smaller than previous 800 bytes.
REC_OFFS_SEC_INDEX_SIZE: adjusted for the new reality
rem0rec.h, rem0rec.ic, rem0rec.cc:
various arguments, return values and local variables types were changed to
fix numerous integer conversions issues.
enum field_type_t:
offset types concept was introduces which replaces old offset flags stuff.
Like in earlier version, 2 upper bits are used to store offset type.
And this enum represents those types.
REC_OFFS_SQL_NULL, REC_OFFS_MASK: removed
get_type(), set_type(), get_value(), combine():
these are convenience functions to work with offsets and it's types
rec_offs_base()[0]:
still uses an old scheme with flags REC_OFFS_COMPACT and REC_OFFS_EXTERNAL
rec_offs_base()[i]:
these have type offset_t now. Two upper bits contains type.
2019-11-04 22:30:12 +03:00
|
|
|
rec_offs_set_n_alloc(offsets, i);
|
|
|
|
rec_offs_set_n_fields(offsets, dict_index_get_n_fields(index));
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2018-04-27 13:49:25 +03:00
|
|
|
heap = mem_heap_create(srv_page_size);
|
|
|
|
offsets_heap = mem_heap_create(srv_page_size);
|
2013-03-26 00:03:13 +02:00
|
|
|
has_index_lock = true;
|
|
|
|
|
|
|
|
next_block:
|
|
|
|
ut_ad(has_index_lock);
|
2016-08-12 11:17:45 +03:00
|
|
|
ut_ad(rw_lock_own(dict_index_get_lock(index), RW_LOCK_X));
|
2013-03-26 00:03:13 +02:00
|
|
|
ut_ad(index->online_log->head.bytes == 0);
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
stage->inc(row_log_progress_inc_per_block());
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
if (trx_is_interrupted(trx)) {
|
|
|
|
goto interrupted;
|
|
|
|
}
|
|
|
|
|
2018-05-07 15:30:57 +03:00
|
|
|
if (index->is_corrupted()) {
|
2013-03-26 00:03:13 +02:00
|
|
|
error = DB_INDEX_CORRUPT;
|
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
ut_ad(dict_index_is_online_ddl(index));
|
|
|
|
|
|
|
|
error = index->online_log->error;
|
|
|
|
|
|
|
|
if (error != DB_SUCCESS) {
|
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (UNIV_UNLIKELY(index->online_log->head.blocks
|
|
|
|
> index->online_log->tail.blocks)) {
|
|
|
|
unexpected_eof:
|
2016-08-12 11:17:45 +03:00
|
|
|
ib::error() << "Unexpected end of temporary file for table "
|
|
|
|
<< index->table->name;
|
2013-03-26 00:03:13 +02:00
|
|
|
corruption:
|
|
|
|
error = DB_CORRUPTION;
|
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (index->online_log->head.blocks
|
|
|
|
== index->online_log->tail.blocks) {
|
|
|
|
if (index->online_log->head.blocks) {
|
|
|
|
#ifdef HAVE_FTRUNCATE
|
|
|
|
/* Truncate the file in order to save space. */
|
2016-08-12 11:17:45 +03:00
|
|
|
if (index->online_log->fd > 0
|
2016-02-16 12:07:18 +01:00
|
|
|
&& ftruncate(index->online_log->fd, 0) == -1) {
|
2016-08-12 11:17:45 +03:00
|
|
|
ib::error()
|
|
|
|
<< "\'" << index->name + 1
|
|
|
|
<< "\' failed with error "
|
|
|
|
<< errno << ":" << strerror(errno);
|
|
|
|
|
2013-08-23 09:03:57 +03:00
|
|
|
goto corruption;
|
|
|
|
}
|
2013-03-26 00:03:13 +02:00
|
|
|
#endif /* HAVE_FTRUNCATE */
|
|
|
|
index->online_log->head.blocks
|
|
|
|
= index->online_log->tail.blocks = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
next_mrec = index->online_log->tail.block;
|
|
|
|
next_mrec_end = next_mrec + index->online_log->tail.bytes;
|
|
|
|
|
|
|
|
if (next_mrec_end == next_mrec) {
|
|
|
|
/* End of log reached. */
|
|
|
|
all_done:
|
|
|
|
ut_ad(has_index_lock);
|
|
|
|
ut_ad(index->online_log->head.blocks == 0);
|
|
|
|
ut_ad(index->online_log->tail.blocks == 0);
|
|
|
|
index->online_log->head.bytes = 0;
|
|
|
|
index->online_log->tail.bytes = 0;
|
|
|
|
error = DB_SUCCESS;
|
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
os_offset_t ofs;
|
|
|
|
|
|
|
|
ofs = (os_offset_t) index->online_log->head.blocks
|
|
|
|
* srv_sort_buf_size;
|
|
|
|
|
|
|
|
ut_ad(has_index_lock);
|
|
|
|
has_index_lock = false;
|
|
|
|
rw_lock_x_unlock(dict_index_get_lock(index));
|
|
|
|
|
|
|
|
log_free_check();
|
|
|
|
|
|
|
|
ut_ad(dict_index_is_online_ddl(index));
|
|
|
|
|
2014-05-05 18:20:28 +02:00
|
|
|
if (!row_log_block_allocate(index->online_log->head)) {
|
|
|
|
error = DB_OUT_OF_MEMORY;
|
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
|
2017-05-23 11:09:47 +03:00
|
|
|
IORequest request(IORequest::READ);
|
2017-09-16 22:31:53 +03:00
|
|
|
byte* buf = index->online_log->head.block;
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2019-04-27 20:41:31 +03:00
|
|
|
if (os_file_read_no_error_handling(
|
2017-09-16 22:31:53 +03:00
|
|
|
request, index->online_log->fd,
|
2019-04-27 20:41:31 +03:00
|
|
|
buf, ofs, srv_sort_buf_size, 0) != DB_SUCCESS) {
|
2016-08-12 11:17:45 +03:00
|
|
|
ib::error()
|
|
|
|
<< "Unable to read temporary file"
|
2018-03-23 17:25:56 +02:00
|
|
|
" for table " << index->table->name;
|
2013-03-26 00:03:13 +02:00
|
|
|
goto corruption;
|
|
|
|
}
|
|
|
|
|
2017-09-14 09:23:20 +03:00
|
|
|
if (log_tmp_is_encrypted()) {
|
2017-09-16 22:31:53 +03:00
|
|
|
if (!log_tmp_block_decrypt(
|
|
|
|
buf, srv_sort_buf_size,
|
2019-06-27 16:23:03 +05:30
|
|
|
index->online_log->crypt_head, ofs)) {
|
2017-09-14 09:23:20 +03:00
|
|
|
error = DB_DECRYPTION_FAILED;
|
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
srv_stats.n_rowlog_blocks_decrypted.inc();
|
2017-09-16 22:31:53 +03:00
|
|
|
memcpy(buf, index->online_log->crypt_head,
|
|
|
|
srv_sort_buf_size);
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef POSIX_FADV_DONTNEED
|
|
|
|
/* Each block is read exactly once. Free up the file cache. */
|
|
|
|
posix_fadvise(index->online_log->fd,
|
|
|
|
ofs, srv_sort_buf_size, POSIX_FADV_DONTNEED);
|
|
|
|
#endif /* POSIX_FADV_DONTNEED */
|
|
|
|
|
|
|
|
next_mrec = index->online_log->head.block;
|
|
|
|
next_mrec_end = next_mrec + srv_sort_buf_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This read is not protected by index->online_log->mutex for
|
|
|
|
performance reasons. We will eventually notice any error that
|
|
|
|
was flagged by a DML thread. */
|
|
|
|
error = index->online_log->error;
|
|
|
|
|
|
|
|
if (error != DB_SUCCESS) {
|
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mrec) {
|
|
|
|
/* A partial record was read from the previous block.
|
|
|
|
Copy the temporary buffer full, as we do not know the
|
|
|
|
length of the record. Parse subsequent records from
|
|
|
|
the bigger buffer index->online_log->head.block
|
|
|
|
or index->online_log->tail.block. */
|
|
|
|
|
|
|
|
ut_ad(mrec == index->online_log->head.buf);
|
|
|
|
ut_ad(mrec_end > mrec);
|
|
|
|
ut_ad(mrec_end < (&index->online_log->head.buf)[1]);
|
|
|
|
|
|
|
|
memcpy((mrec_t*) mrec_end, next_mrec,
|
2018-04-28 15:49:09 +03:00
|
|
|
ulint((&index->online_log->head.buf)[1] - mrec_end));
|
2013-03-26 00:03:13 +02:00
|
|
|
mrec = row_log_table_apply_op(
|
2017-08-07 23:44:45 +03:00
|
|
|
thr, new_trx_id_col,
|
2013-03-26 00:03:13 +02:00
|
|
|
dup, &error, offsets_heap, heap,
|
|
|
|
index->online_log->head.buf,
|
|
|
|
(&index->online_log->head.buf)[1], offsets);
|
|
|
|
if (error != DB_SUCCESS) {
|
|
|
|
goto func_exit;
|
|
|
|
} else if (UNIV_UNLIKELY(mrec == NULL)) {
|
|
|
|
/* The record was not reassembled properly. */
|
|
|
|
goto corruption;
|
|
|
|
}
|
|
|
|
/* The record was previously found out to be
|
|
|
|
truncated. Now that the parse buffer was extended,
|
|
|
|
it should proceed beyond the old end of the buffer. */
|
|
|
|
ut_a(mrec > mrec_end);
|
|
|
|
|
2018-04-28 15:49:09 +03:00
|
|
|
index->online_log->head.bytes = ulint(mrec - mrec_end);
|
2013-03-26 00:03:13 +02:00
|
|
|
next_mrec += index->online_log->head.bytes;
|
|
|
|
}
|
|
|
|
|
|
|
|
ut_ad(next_mrec <= next_mrec_end);
|
|
|
|
/* The following loop must not be parsing the temporary
|
|
|
|
buffer, but head.block or tail.block. */
|
|
|
|
|
|
|
|
/* mrec!=NULL means that the next record starts from the
|
|
|
|
middle of the block */
|
|
|
|
ut_ad((mrec == NULL) == (index->online_log->head.bytes == 0));
|
|
|
|
|
|
|
|
#ifdef UNIV_DEBUG
|
|
|
|
if (next_mrec_end == index->online_log->head.block
|
|
|
|
+ srv_sort_buf_size) {
|
|
|
|
/* If tail.bytes == 0, next_mrec_end can also be at
|
|
|
|
the end of tail.block. */
|
|
|
|
if (index->online_log->tail.bytes == 0) {
|
|
|
|
ut_ad(next_mrec == next_mrec_end);
|
|
|
|
ut_ad(index->online_log->tail.blocks == 0);
|
|
|
|
ut_ad(index->online_log->head.blocks == 0);
|
|
|
|
ut_ad(index->online_log->head.bytes == 0);
|
|
|
|
} else {
|
|
|
|
ut_ad(next_mrec == index->online_log->head.block
|
|
|
|
+ index->online_log->head.bytes);
|
|
|
|
ut_ad(index->online_log->tail.blocks
|
|
|
|
> index->online_log->head.blocks);
|
|
|
|
}
|
|
|
|
} else if (next_mrec_end == index->online_log->tail.block
|
|
|
|
+ index->online_log->tail.bytes) {
|
|
|
|
ut_ad(next_mrec == index->online_log->tail.block
|
|
|
|
+ index->online_log->head.bytes);
|
|
|
|
ut_ad(index->online_log->tail.blocks == 0);
|
|
|
|
ut_ad(index->online_log->head.blocks == 0);
|
|
|
|
ut_ad(index->online_log->head.bytes
|
|
|
|
<= index->online_log->tail.bytes);
|
|
|
|
} else {
|
|
|
|
ut_error;
|
|
|
|
}
|
|
|
|
#endif /* UNIV_DEBUG */
|
|
|
|
|
|
|
|
mrec_end = next_mrec_end;
|
|
|
|
|
|
|
|
while (!trx_is_interrupted(trx)) {
|
|
|
|
mrec = next_mrec;
|
2018-04-21 17:37:24 +02:00
|
|
|
ut_ad(mrec <= mrec_end);
|
|
|
|
|
|
|
|
if (mrec == mrec_end) {
|
|
|
|
/* We are at the end of the log.
|
|
|
|
Mark the replay all_done. */
|
|
|
|
if (has_index_lock) {
|
|
|
|
goto all_done;
|
|
|
|
}
|
|
|
|
}
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
if (!has_index_lock) {
|
|
|
|
/* We are applying operations from a different
|
|
|
|
block than the one that is being written to.
|
|
|
|
We do not hold index->lock in order to
|
|
|
|
allow other threads to concurrently buffer
|
|
|
|
modifications. */
|
|
|
|
ut_ad(mrec >= index->online_log->head.block);
|
|
|
|
ut_ad(mrec_end == index->online_log->head.block
|
|
|
|
+ srv_sort_buf_size);
|
|
|
|
ut_ad(index->online_log->head.bytes
|
|
|
|
< srv_sort_buf_size);
|
|
|
|
|
|
|
|
/* Take the opportunity to do a redo log
|
|
|
|
checkpoint if needed. */
|
|
|
|
log_free_check();
|
|
|
|
} else {
|
|
|
|
/* We are applying operations from the last block.
|
|
|
|
Do not allow other threads to buffer anything,
|
|
|
|
so that we can finally catch up and synchronize. */
|
|
|
|
ut_ad(index->online_log->head.blocks == 0);
|
|
|
|
ut_ad(index->online_log->tail.blocks == 0);
|
|
|
|
ut_ad(mrec_end == index->online_log->tail.block
|
|
|
|
+ index->online_log->tail.bytes);
|
|
|
|
ut_ad(mrec >= index->online_log->tail.block);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This read is not protected by index->online_log->mutex
|
|
|
|
for performance reasons. We will eventually notice any
|
|
|
|
error that was flagged by a DML thread. */
|
|
|
|
error = index->online_log->error;
|
|
|
|
|
|
|
|
if (error != DB_SUCCESS) {
|
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
next_mrec = row_log_table_apply_op(
|
2017-08-07 23:44:45 +03:00
|
|
|
thr, new_trx_id_col,
|
2013-03-26 00:03:13 +02:00
|
|
|
dup, &error, offsets_heap, heap,
|
|
|
|
mrec, mrec_end, offsets);
|
|
|
|
|
|
|
|
if (error != DB_SUCCESS) {
|
|
|
|
goto func_exit;
|
|
|
|
} else if (next_mrec == next_mrec_end) {
|
|
|
|
/* The record happened to end on a block boundary.
|
|
|
|
Do we have more blocks left? */
|
|
|
|
if (has_index_lock) {
|
|
|
|
/* The index will be locked while
|
|
|
|
applying the last block. */
|
|
|
|
goto all_done;
|
|
|
|
}
|
|
|
|
|
|
|
|
mrec = NULL;
|
|
|
|
process_next_block:
|
|
|
|
rw_lock_x_lock(dict_index_get_lock(index));
|
|
|
|
has_index_lock = true;
|
|
|
|
|
|
|
|
index->online_log->head.bytes = 0;
|
|
|
|
index->online_log->head.blocks++;
|
|
|
|
goto next_block;
|
|
|
|
} else if (next_mrec != NULL) {
|
|
|
|
ut_ad(next_mrec < next_mrec_end);
|
2018-04-28 15:49:09 +03:00
|
|
|
index->online_log->head.bytes
|
|
|
|
+= ulint(next_mrec - mrec);
|
2013-03-26 00:03:13 +02:00
|
|
|
} else if (has_index_lock) {
|
|
|
|
/* When mrec is within tail.block, it should
|
|
|
|
be a complete record, because we are holding
|
|
|
|
index->lock and thus excluding the writer. */
|
|
|
|
ut_ad(index->online_log->tail.blocks == 0);
|
|
|
|
ut_ad(mrec_end == index->online_log->tail.block
|
|
|
|
+ index->online_log->tail.bytes);
|
|
|
|
ut_ad(0);
|
|
|
|
goto unexpected_eof;
|
|
|
|
} else {
|
|
|
|
memcpy(index->online_log->head.buf, mrec,
|
2018-04-28 15:49:09 +03:00
|
|
|
ulint(mrec_end - mrec));
|
|
|
|
mrec_end += ulint(index->online_log->head.buf - mrec);
|
2013-03-26 00:03:13 +02:00
|
|
|
mrec = index->online_log->head.buf;
|
|
|
|
goto process_next_block;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
interrupted:
|
|
|
|
error = DB_INTERRUPTED;
|
|
|
|
func_exit:
|
|
|
|
if (!has_index_lock) {
|
|
|
|
rw_lock_x_lock(dict_index_get_lock(index));
|
|
|
|
}
|
|
|
|
|
|
|
|
mem_heap_free(offsets_heap);
|
|
|
|
mem_heap_free(heap);
|
2014-05-05 18:20:28 +02:00
|
|
|
row_log_block_free(index->online_log->head);
|
2013-03-26 00:03:13 +02:00
|
|
|
ut_free(offsets);
|
|
|
|
return(error);
|
|
|
|
}
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
/** Apply the row_log_table log to a table upon completing rebuild.
|
|
|
|
@param[in] thr query graph
|
|
|
|
@param[in] old_table old table
|
|
|
|
@param[in,out] table MySQL table (for reporting duplicates)
|
|
|
|
@param[in,out] stage performance schema accounting object, used by
|
|
|
|
ALTER TABLE. stage->begin_phase_log_table() will be called initially and then
|
|
|
|
stage->inc() will be called for each block of log that is applied.
|
2018-06-26 14:10:58 +05:30
|
|
|
@param[in] new_table Altered table
|
2013-03-26 00:03:13 +02:00
|
|
|
@return DB_SUCCESS, or error code on failure */
|
|
|
|
dberr_t
|
|
|
|
row_log_table_apply(
|
2016-08-12 11:17:45 +03:00
|
|
|
que_thr_t* thr,
|
|
|
|
dict_table_t* old_table,
|
|
|
|
struct TABLE* table,
|
2018-06-26 14:10:58 +05:30
|
|
|
ut_stage_alter_t* stage,
|
|
|
|
dict_table_t* new_table)
|
2013-03-26 00:03:13 +02:00
|
|
|
{
|
|
|
|
dberr_t error;
|
|
|
|
dict_index_t* clust_index;
|
|
|
|
|
|
|
|
thr_get_trx(thr)->error_key_num = 0;
|
2016-08-12 11:17:45 +03:00
|
|
|
DBUG_EXECUTE_IF("innodb_trx_duplicates",
|
|
|
|
thr_get_trx(thr)->duplicates = TRX_DUP_REPLACE;);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
stage->begin_phase_log_table();
|
|
|
|
|
2019-05-17 15:17:37 +03:00
|
|
|
ut_ad(!rw_lock_own(&dict_sys.latch, RW_LOCK_S));
|
2013-03-26 00:03:13 +02:00
|
|
|
clust_index = dict_table_get_first_index(old_table);
|
|
|
|
|
2018-06-25 18:16:29 +05:30
|
|
|
if (clust_index->online_log->n_rows == 0) {
|
2018-06-26 14:10:58 +05:30
|
|
|
clust_index->online_log->n_rows = new_table->stat_n_rows;
|
2018-06-25 18:16:29 +05:30
|
|
|
}
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
rw_lock_x_lock(dict_index_get_lock(clust_index));
|
|
|
|
|
|
|
|
if (!clust_index->online_log) {
|
|
|
|
ut_ad(dict_index_get_online_status(clust_index)
|
|
|
|
== ONLINE_INDEX_COMPLETE);
|
|
|
|
/* This function should not be called unless
|
|
|
|
rebuilding a table online. Build in some fault
|
|
|
|
tolerance. */
|
|
|
|
ut_ad(0);
|
|
|
|
error = DB_ERROR;
|
|
|
|
} else {
|
|
|
|
row_merge_dup_t dup = {
|
|
|
|
clust_index, table,
|
|
|
|
clust_index->online_log->col_map, 0
|
|
|
|
};
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
error = row_log_table_apply_ops(thr, &dup, stage);
|
2014-02-01 09:33:26 +01:00
|
|
|
|
|
|
|
ut_ad(error != DB_SUCCESS
|
|
|
|
|| clust_index->online_log->head.total
|
|
|
|
== clust_index->online_log->tail.total);
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
rw_lock_x_unlock(dict_index_get_lock(clust_index));
|
2016-08-12 11:17:45 +03:00
|
|
|
DBUG_EXECUTE_IF("innodb_trx_duplicates",
|
|
|
|
thr_get_trx(thr)->duplicates = 0;);
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
return(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************//**
|
|
|
|
Allocate the row log for an index and flag the index
|
|
|
|
for online creation.
|
|
|
|
@retval true if success, false if not */
|
|
|
|
bool
|
|
|
|
row_log_allocate(
|
|
|
|
/*=============*/
|
MDEV-13654 Various crashes due to DB_TRX_ID mismatch in table-rebuilding ALTER TABLE…LOCK=NONE
After MDEV-12288 and MDEV-13536, the DB_TRX_ID of old clustered index
records for which no history is available should be reset to 0.
This caused crashes in online table-rebuilding ALTER, because the
row_log_table_apply() is built on the assumption that the PRIMARY KEY
together with DB_TRX_ID,DB_ROLL_PTR identifies the record.
Both when copying the old table and when writing log about changes to
the old table, we must map "old" DB_TRX_ID to 0. "old" here is simply
"older than the trx_id of the ALTER TABLE transaction", because
the MDL_EXCLUSIVE (and exclusive InnoDB table lock) in
ha_innobase::prepare_inplace_alter_table() forces any transactions
accessing the table to commit or rollback. So, we know that we can
safely reset any DB_TRX_ID in the table that is older than the
transaction ID of the ALTER TABLE, because the undo log history would be
lost in a table-rebuilding ALTER.
Note: After a table-rebuilding online ALTER TABLE, the rebuilt table
may end up containing some nonzero DB_TRX_ID columns. The apply logic
identifies the rows by the combination of PRIMARY KEY and DB_TRX_ID.
These nonzero DB_TRX_ID would necessarily refer to concurrent DML
operations that were started during ha_innobase::inplace_alter_table().
row_log_allocate(): Add a parameter for the ALTER TABLE transaction.
row_log_t::min_trx: The ALTER TABLE transaction ID.
trx_id_check(): A debug function to check that DB_TRX_ID makes sense
(is either 0 or bigger than the ALTER TABLE transaction ID).
reset_trx_id[]: The reset DB_TRX_ID,DB_ROLL_PTR columns.
row_log_table_delete(), row_log_table_get_pk(): Reset the
DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE transaction.
row_log_table_apply_delete(), row_log_table_apply_update():
Assert trx_id_check().
row_merge_insert_index_tuples(): Remove the unused parameter trx_id.
row_merge_read_clustered_index(): In a table-rebuilding ALTER,
reset the DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE
transaction. Assert trx_id_check() on clustered index records that
are being buffered.
2017-08-31 18:03:50 +03:00
|
|
|
const trx_t* trx, /*!< in: the ALTER TABLE transaction */
|
2013-03-26 00:03:13 +02:00
|
|
|
dict_index_t* index, /*!< in/out: index */
|
|
|
|
dict_table_t* table, /*!< in/out: new table being rebuilt,
|
|
|
|
or NULL when creating a secondary index */
|
|
|
|
bool same_pk,/*!< in: whether the definition of the
|
|
|
|
PRIMARY KEY has remained the same */
|
2018-04-24 13:15:35 +05:30
|
|
|
const dtuple_t* defaults,
|
2013-03-26 00:03:13 +02:00
|
|
|
/*!< in: default values of
|
2018-04-24 13:15:35 +05:30
|
|
|
added, changed columns, or NULL */
|
2016-09-06 09:43:16 +03:00
|
|
|
const ulint* col_map,/*!< in: mapping of old column
|
2013-03-26 00:03:13 +02:00
|
|
|
numbers to new ones, or NULL if !table */
|
2018-04-24 13:15:35 +05:30
|
|
|
const char* path, /*!< in: where to create temporary file */
|
2018-06-25 14:52:38 +05:30
|
|
|
const TABLE* old_table, /*!< in: table definition before alter */
|
|
|
|
const bool allow_not_null) /*!< in: allow null to not-null
|
|
|
|
conversion */
|
2013-03-26 00:03:13 +02:00
|
|
|
{
|
|
|
|
row_log_t* log;
|
2014-02-01 09:33:26 +01:00
|
|
|
DBUG_ENTER("row_log_allocate");
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
ut_ad(!dict_index_is_online_ddl(index));
|
|
|
|
ut_ad(dict_index_is_clust(index) == !!table);
|
|
|
|
ut_ad(!table || index->table != table);
|
|
|
|
ut_ad(same_pk || table);
|
|
|
|
ut_ad(!table || col_map);
|
2018-04-24 13:15:35 +05:30
|
|
|
ut_ad(!defaults || col_map);
|
2016-08-12 11:17:45 +03:00
|
|
|
ut_ad(rw_lock_own(dict_index_get_lock(index), RW_LOCK_X));
|
MDEV-13654 Various crashes due to DB_TRX_ID mismatch in table-rebuilding ALTER TABLE…LOCK=NONE
After MDEV-12288 and MDEV-13536, the DB_TRX_ID of old clustered index
records for which no history is available should be reset to 0.
This caused crashes in online table-rebuilding ALTER, because the
row_log_table_apply() is built on the assumption that the PRIMARY KEY
together with DB_TRX_ID,DB_ROLL_PTR identifies the record.
Both when copying the old table and when writing log about changes to
the old table, we must map "old" DB_TRX_ID to 0. "old" here is simply
"older than the trx_id of the ALTER TABLE transaction", because
the MDL_EXCLUSIVE (and exclusive InnoDB table lock) in
ha_innobase::prepare_inplace_alter_table() forces any transactions
accessing the table to commit or rollback. So, we know that we can
safely reset any DB_TRX_ID in the table that is older than the
transaction ID of the ALTER TABLE, because the undo log history would be
lost in a table-rebuilding ALTER.
Note: After a table-rebuilding online ALTER TABLE, the rebuilt table
may end up containing some nonzero DB_TRX_ID columns. The apply logic
identifies the rows by the combination of PRIMARY KEY and DB_TRX_ID.
These nonzero DB_TRX_ID would necessarily refer to concurrent DML
operations that were started during ha_innobase::inplace_alter_table().
row_log_allocate(): Add a parameter for the ALTER TABLE transaction.
row_log_t::min_trx: The ALTER TABLE transaction ID.
trx_id_check(): A debug function to check that DB_TRX_ID makes sense
(is either 0 or bigger than the ALTER TABLE transaction ID).
reset_trx_id[]: The reset DB_TRX_ID,DB_ROLL_PTR columns.
row_log_table_delete(), row_log_table_get_pk(): Reset the
DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE transaction.
row_log_table_apply_delete(), row_log_table_apply_update():
Assert trx_id_check().
row_merge_insert_index_tuples(): Remove the unused parameter trx_id.
row_merge_read_clustered_index(): In a table-rebuilding ALTER,
reset the DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE
transaction. Assert trx_id_check() on clustered index records that
are being buffered.
2017-08-31 18:03:50 +03:00
|
|
|
ut_ad(trx_state_eq(trx, TRX_STATE_ACTIVE));
|
|
|
|
ut_ad(trx->id);
|
2016-08-12 11:17:45 +03:00
|
|
|
|
|
|
|
log = static_cast<row_log_t*>(ut_malloc_nokey(sizeof *log));
|
|
|
|
|
|
|
|
if (log == NULL) {
|
2014-02-01 09:33:26 +01:00
|
|
|
DBUG_RETURN(false);
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
2018-04-15 15:29:55 +01:00
|
|
|
log->fd = OS_FILE_CLOSED;
|
2016-08-12 11:17:45 +03:00
|
|
|
mutex_create(LATCH_ID_INDEX_ONLINE_LOG, &log->mutex);
|
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
log->blobs = NULL;
|
2013-03-26 00:03:13 +02:00
|
|
|
log->table = table;
|
|
|
|
log->same_pk = same_pk;
|
2018-04-24 13:15:35 +05:30
|
|
|
log->defaults = defaults;
|
2013-03-26 00:03:13 +02:00
|
|
|
log->col_map = col_map;
|
|
|
|
log->error = DB_SUCCESS;
|
MDEV-13654 Various crashes due to DB_TRX_ID mismatch in table-rebuilding ALTER TABLE…LOCK=NONE
After MDEV-12288 and MDEV-13536, the DB_TRX_ID of old clustered index
records for which no history is available should be reset to 0.
This caused crashes in online table-rebuilding ALTER, because the
row_log_table_apply() is built on the assumption that the PRIMARY KEY
together with DB_TRX_ID,DB_ROLL_PTR identifies the record.
Both when copying the old table and when writing log about changes to
the old table, we must map "old" DB_TRX_ID to 0. "old" here is simply
"older than the trx_id of the ALTER TABLE transaction", because
the MDL_EXCLUSIVE (and exclusive InnoDB table lock) in
ha_innobase::prepare_inplace_alter_table() forces any transactions
accessing the table to commit or rollback. So, we know that we can
safely reset any DB_TRX_ID in the table that is older than the
transaction ID of the ALTER TABLE, because the undo log history would be
lost in a table-rebuilding ALTER.
Note: After a table-rebuilding online ALTER TABLE, the rebuilt table
may end up containing some nonzero DB_TRX_ID columns. The apply logic
identifies the rows by the combination of PRIMARY KEY and DB_TRX_ID.
These nonzero DB_TRX_ID would necessarily refer to concurrent DML
operations that were started during ha_innobase::inplace_alter_table().
row_log_allocate(): Add a parameter for the ALTER TABLE transaction.
row_log_t::min_trx: The ALTER TABLE transaction ID.
trx_id_check(): A debug function to check that DB_TRX_ID makes sense
(is either 0 or bigger than the ALTER TABLE transaction ID).
reset_trx_id[]: The reset DB_TRX_ID,DB_ROLL_PTR columns.
row_log_table_delete(), row_log_table_get_pk(): Reset the
DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE transaction.
row_log_table_apply_delete(), row_log_table_apply_update():
Assert trx_id_check().
row_merge_insert_index_tuples(): Remove the unused parameter trx_id.
row_merge_read_clustered_index(): In a table-rebuilding ALTER,
reset the DB_TRX_ID,DB_ROLL_PTR when they precede the ALTER TABLE
transaction. Assert trx_id_check() on clustered index records that
are being buffered.
2017-08-31 18:03:50 +03:00
|
|
|
log->min_trx = trx->id;
|
2013-03-26 00:03:13 +02:00
|
|
|
log->max_trx = 0;
|
|
|
|
log->tail.blocks = log->tail.bytes = 0;
|
2014-02-01 09:33:26 +01:00
|
|
|
log->tail.total = 0;
|
2014-05-05 18:20:28 +02:00
|
|
|
log->tail.block = log->head.block = NULL;
|
2017-09-16 22:31:53 +03:00
|
|
|
log->crypt_tail = log->crypt_head = NULL;
|
2013-03-26 00:03:13 +02:00
|
|
|
log->head.blocks = log->head.bytes = 0;
|
2014-02-01 09:33:26 +01:00
|
|
|
log->head.total = 0;
|
2016-09-06 09:43:16 +03:00
|
|
|
log->path = path;
|
2018-05-03 15:17:16 +03:00
|
|
|
log->n_core_fields = index->n_core_fields;
|
MDEV-15662 Instant DROP COLUMN or changing the order of columns
Allow ADD COLUMN anywhere in a table, not only adding as the
last column.
Allow instant DROP COLUMN and instant changing the order of columns.
The added columns will always be added last in clustered index records.
In new records, instantly dropped columns will be stored as NULL or
empty when possible.
Information about dropped and reordered columns will be written in
a metadata BLOB (mblob), which is stored before the first 'user' field
in the hidden metadata record at the start of the clustered index.
The presence of mblob is indicated by setting the delete-mark flag in
the metadata record.
The metadata BLOB stores the number of clustered index fields,
followed by an array of column information for each field.
For dropped columns, we store the NOT NULL flag, the fixed length,
and for variable-length columns, whether the maximum length exceeded
255 bytes. For non-dropped columns, we store the column position.
Unlike with MDEV-11369, when a table becomes empty, it cannot
be converted back to the canonical format. The reason for this is
that other threads may hold cached objects such as
row_prebuilt_t::ins_node that could refer to dropped or reordered
index fields.
For instant DROP COLUMN and ROW_FORMAT=COMPACT or ROW_FORMAT=DYNAMIC,
we must store the n_core_null_bytes in the root page, so that the
chain of node pointer records can be followed in order to reach the
leftmost leaf page where the metadata record is located.
If the mblob is present, we will zero-initialize the strings
"infimum" and "supremum" in the root page, and use the last byte of
"supremum" for storing the number of null bytes (which are allocated
but useless on node pointer pages). This is necessary for
btr_cur_instant_init_metadata() to be able to navigate to the mblob.
If the PRIMARY KEY contains any variable-length column and some
nullable columns were instantly dropped, the dict_index_t::n_nullable
in the data dictionary could be smaller than it actually is in the
non-leaf pages. Because of this, the non-leaf pages could use more
bytes for the null flags than the data dictionary expects, and we
could be reading the lengths of the variable-length columns from the
wrong offset, and thus reading the child page number from wrong place.
This is the result of two design mistakes that involve unnecessary
storage of data: First, it is nonsense to store any data fields for
the leftmost node pointer records, because the comparisons would be
resolved by the MIN_REC_FLAG alone. Second, there cannot be any null
fields in the clustered index node pointer fields, but we nevertheless
reserve space for all the null flags.
Limitations (future work):
MDEV-17459 Allow instant ALTER TABLE even if FULLTEXT INDEX exists
MDEV-17468 Avoid table rebuild on operations on generated columns
MDEV-17494 Refuse ALGORITHM=INSTANT when the row size is too large
btr_page_reorganize_low(): Preserve any metadata in the root page.
Call lock_move_reorganize_page() only after restoring the "infimum"
and "supremum" records, to avoid a memcmp() assertion failure.
dict_col_t::DROPPED: Magic value for dict_col_t::ind.
dict_col_t::clear_instant(): Renamed from dict_col_t::remove_instant().
Do not assert that the column was instantly added, because we
sometimes call this unconditionally for all columns.
Convert an instantly added column to a "core column". The old name
remove_instant() could be mistaken to refer to "instant DROP COLUMN".
dict_col_t::is_added(): Rename from dict_col_t::is_instant().
dtype_t::metadata_blob_init(): Initialize the mblob data type.
dtuple_t::is_metadata(), dtuple_t::is_alter_metadata(),
upd_t::is_metadata(), upd_t::is_alter_metadata(): Check if info_bits
refer to a metadata record.
dict_table_t::instant: Metadata about dropped or reordered columns.
dict_table_t::prepare_instant(): Prepare
ha_innobase_inplace_ctx::instant_table for instant ALTER TABLE.
innobase_instant_try() will pass this to dict_table_t::instant_column().
On rollback, dict_table_t::rollback_instant() will be called.
dict_table_t::instant_column(): Renamed from instant_add_column().
Add the parameter col_map so that columns can be reordered.
Copy and adjust v_cols[] as well.
dict_table_t::find(): Find an old column based on a new column number.
dict_table_t::serialise_columns(), dict_table_t::deserialise_columns():
Convert the mblob.
dict_index_t::instant_metadata(): Create the metadata record
for instant ALTER TABLE. Invoke dict_table_t::serialise_columns().
dict_index_t::reconstruct_fields(): Invoked by
dict_table_t::deserialise_columns().
dict_index_t::clear_instant_alter(): Move the fields for the
dropped columns to the end, and sort the surviving index fields
in ascending order of column position.
ha_innobase::check_if_supported_inplace_alter(): Do not allow
adding a FTS_DOC_ID column if a hidden FTS_DOC_ID column exists
due to FULLTEXT INDEX. (This always required ALGORITHM=COPY.)
instant_alter_column_possible(): Add a parameter for InnoDB table,
to check for additional conditions, such as the maximum number of
index fields.
ha_innobase_inplace_ctx::first_alter_pos: The first column whose position
is affected by instant ADD, DROP, or changing the order of columns.
innobase_build_col_map(): Skip added virtual columns.
prepare_inplace_add_virtual(): Correctly compute num_to_add_vcol.
Remove some unnecessary code. Note that the call to
innodb_base_col_setup() should be executed later.
commit_try_norebuild(): If ctx->is_instant(), let the virtual
columns be added or dropped by innobase_instant_try().
innobase_instant_try(): Fill in a zero default value for the
hidden column FTS_DOC_ID (to reduce the work needed in MDEV-17459).
If any columns were dropped or reordered (or added not last),
delete any SYS_COLUMNS records for the following columns, and
insert SYS_COLUMNS records for all subsequent stored columns as well
as for all virtual columns. If any virtual column is dropped, rewrite
all virtual column metadata. Use a shortcut only for adding
virtual columns. This is because innobase_drop_virtual_try()
assumes that the dropped virtual columns still exist in ctx->old_table.
innodb_update_cols(): Renamed from innodb_update_n_cols().
innobase_add_one_virtual(), innobase_insert_sys_virtual(): Change
the return type to bool, and invoke my_error() when detecting an error.
innodb_insert_sys_columns(): Insert a record into SYS_COLUMNS.
Refactored from innobase_add_one_virtual() and innobase_instant_add_col().
innobase_instant_add_col(): Replace the parameter dfield with type.
innobase_instant_drop_cols(): Drop matching columns from SYS_COLUMNS
and all columns from SYS_VIRTUAL.
innobase_add_virtual_try(), innobase_drop_virtual_try(): Let
the caller invoke innodb_update_cols().
innobase_rename_column_try(): Skip dropped columns.
commit_cache_norebuild(): Update table->fts->doc_col.
dict_mem_table_col_rename_low(): Skip dropped columns.
trx_undo_rec_get_partial_row(): Skip dropped columns.
trx_undo_update_rec_get_update(): Handle the metadata BLOB correctly.
trx_undo_page_report_modify(): Avoid out-of-bounds access to record fields.
Log metadata records consistently.
Apparently, the first fields of a clustered index may be updated
in an update_undo vector when the index is ID_IND of SYS_FOREIGN,
as part of renaming the table during ALTER TABLE. Normally, updates of
the PRIMARY KEY should be logged as delete-mark and an insert.
row_undo_mod_parse_undo_rec(), row_purge_parse_undo_rec():
Use trx_undo_metadata.
row_undo_mod_clust_low(): On metadata rollback, roll back the root page too.
row_undo_mod_clust(): Relax an assertion. The delete-mark flag was
repurposed for ALTER TABLE metadata records.
row_rec_to_index_entry_impl(): Add the template parameter mblob
and the optional parameter info_bits for specifying the desired new
info bits. For the metadata tuple, allow conversion between the original
format (ADD COLUMN only) and the generic format (with hidden BLOB).
Add the optional parameter "pad" to determine whether the tuple should
be padded to the index fields (on ALTER TABLE it should), or whether
it should remain at its original size (on rollback).
row_build_index_entry_low(): Clean up the code, removing
redundant variables and conditions. For instantly dropped columns,
generate a dummy value that is NULL, the empty string, or a
fixed length of NUL bytes, depending on the type of the dropped column.
row_upd_clust_rec_by_insert_inherit_func(): On the update of PRIMARY KEY
of a record that contained a dropped column whose value was stored
externally, we will be inserting a dummy NULL or empty string value
to the field of the dropped column. The externally stored column would
eventually be dropped when purge removes the delete-marked record for
the old PRIMARY KEY value.
btr_index_rec_validate(): Recognize the metadata record.
btr_discard_only_page_on_level(): Preserve the generic instant
ALTER TABLE metadata.
btr_set_instant(): Replaces page_set_instant(). This sets a clustered
index root page to the appropriate format, or upgrades from
the MDEV-11369 instant ADD COLUMN to generic ALTER TABLE format.
btr_cur_instant_init_low(): Read and validate the metadata BLOB page
before reconstructing the dictionary information based on it.
btr_cur_instant_init_metadata(): Do not read any lengths from the
metadata record header before reading the BLOB. At this point, we
would not actually know how many nullable fields the metadata record
contains.
btr_cur_instant_root_init(): Initialize n_core_null_bytes in one
of two possible ways.
btr_cur_trim(): Handle the mblob record.
row_metadata_to_tuple(): Convert a metadata record to a data tuple,
based on the new info_bits of the metadata record.
btr_cur_pessimistic_update(): Invoke row_metadata_to_tuple() if needed.
Invoke dtuple_convert_big_rec() for metadata records if the record is
too large, or if the mblob is not yet marked as externally stored.
btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
When the last user record is deleted, do not delete the
generic instant ALTER TABLE metadata record. Only delete
MDEV-11369 instant ADD COLUMN metadata records.
btr_cur_optimistic_insert(): Avoid unnecessary computation of rec_size.
btr_pcur_store_position(): Allow a logically empty page to contain
a metadata record for generic ALTER TABLE.
REC_INFO_DEFAULT_ROW_ADD: Renamed from REC_INFO_DEFAULT_ROW.
This is for the old instant ADD COLUMN (MDEV-11369) only.
REC_INFO_DEFAULT_ROW_ALTER: The more generic metadata record,
with additional information for dropped or reordered columns.
rec_info_bits_valid(): Remove. The only case when this would fail
is when the record is the generic ALTER TABLE metadata record.
rec_is_alter_metadata(): Check if a record is the metadata record
for instant ALTER TABLE (other than ADD COLUMN). NOTE: This function
must not be invoked on node pointer records, because the delete-mark
flag in those records may be set (it is garbage), and then a debug
assertion could fail because index->is_instant() does not necessarily
hold.
rec_is_add_metadata(): Check if a record is MDEV-11369 ADD COLUMN metadata
record (not more generic instant ALTER TABLE).
rec_get_converted_size_comp_prefix_low(): Assume that the metadata
field will be stored externally. In dtuple_convert_big_rec() during
the rec_get_converted_size() call, it would not be there yet.
rec_get_converted_size_comp(): Replace status,fields,n_fields with tuple.
rec_init_offsets_comp_ordinary(), rec_get_converted_size_comp_prefix_low(),
rec_convert_dtuple_to_rec_comp(): Add template<bool mblob = false>.
With mblob=true, process a record with a metadata BLOB.
rec_copy_prefix_to_buf(): Assert that no fields beyond the key and
system columns are being copied. Exclude the metadata BLOB field.
rec_convert_dtuple_to_metadata_comp(): Convert an alter metadata tuple
into a record.
row_upd_index_replace_metadata(): Apply an update vector to an
alter_metadata tuple.
row_log_allocate(): Replace dict_index_t::is_instant()
with a more appropriate condition that ignores dict_table_t::instant.
Only a table on which the MDEV-11369 ADD COLUMN was performed
can "lose its instantness" when it becomes empty. After
instant DROP COLUMN or reordering columns, we cannot simply
convert the table to the canonical format, because the data
dictionary cache and all possibly existing references to it
from other client connection threads would have to be adjusted.
row_quiesce_write_index_fields(): Do not crash when the table contains
an instantly dropped column.
Thanks to Thirunarayanan Balathandayuthapani for discussing the design
and implementing an initial prototype of this.
Thanks to Matthias Leich for testing.
2018-10-19 16:49:54 +03:00
|
|
|
ut_ad(!table || log->is_instant(index)
|
|
|
|
== (index->n_core_fields < index->n_fields));
|
2018-06-25 14:52:38 +05:30
|
|
|
log->allow_not_null = allow_not_null;
|
|
|
|
log->old_table = old_table;
|
2018-06-25 18:16:29 +05:30
|
|
|
log->n_rows = 0;
|
2016-08-12 11:17:45 +03:00
|
|
|
|
2018-07-26 22:52:53 +03:00
|
|
|
if (table && index->is_instant()) {
|
|
|
|
const unsigned n = log->n_core_fields;
|
|
|
|
log->non_core_fields = UT_NEW_ARRAY_NOKEY(
|
|
|
|
dict_col_t::def_t, index->n_fields - n);
|
|
|
|
for (unsigned i = n; i < index->n_fields; i++) {
|
|
|
|
log->non_core_fields[i - n]
|
|
|
|
= index->fields[i].col->def_val;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log->non_core_fields = NULL;
|
|
|
|
}
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
dict_index_set_online_status(index, ONLINE_INDEX_CREATION);
|
|
|
|
|
2017-09-14 09:23:20 +03:00
|
|
|
if (log_tmp_is_encrypted()) {
|
2019-07-26 17:01:36 +02:00
|
|
|
ulint size = srv_sort_buf_size;
|
2017-09-14 09:23:20 +03:00
|
|
|
log->crypt_head = static_cast<byte *>(os_mem_alloc_large(&size));
|
|
|
|
log->crypt_tail = static_cast<byte *>(os_mem_alloc_large(&size));
|
|
|
|
|
|
|
|
if (!log->crypt_head || !log->crypt_tail) {
|
|
|
|
row_log_free(log);
|
|
|
|
DBUG_RETURN(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-25 10:54:38 +02:00
|
|
|
index->online_log = log;
|
2013-03-26 00:03:13 +02:00
|
|
|
/* While we might be holding an exclusive data dictionary lock
|
|
|
|
here, in row_log_abort_sec() we will not always be holding it. Use
|
|
|
|
atomic operations in both cases. */
|
|
|
|
MONITOR_ATOMIC_INC(MONITOR_ONLINE_CREATE_INDEX);
|
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
DBUG_RETURN(true);
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************//**
|
|
|
|
Free the row log for an index that was being created online. */
|
|
|
|
void
|
|
|
|
row_log_free(
|
|
|
|
/*=========*/
|
2020-11-25 10:54:38 +02:00
|
|
|
row_log_t* log) /*!< in,own: row log */
|
2013-03-26 00:03:13 +02:00
|
|
|
{
|
|
|
|
MONITOR_ATOMIC_DEC(MONITOR_ONLINE_CREATE_INDEX);
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
UT_DELETE(log->blobs);
|
2018-07-26 22:52:53 +03:00
|
|
|
UT_DELETE_ARRAY(log->non_core_fields);
|
2014-05-05 18:20:28 +02:00
|
|
|
row_log_block_free(log->tail);
|
|
|
|
row_log_block_free(log->head);
|
2013-03-26 00:03:13 +02:00
|
|
|
row_merge_file_destroy_low(log->fd);
|
2017-09-14 09:23:20 +03:00
|
|
|
|
|
|
|
if (log->crypt_head) {
|
2019-07-26 17:01:36 +02:00
|
|
|
os_mem_free_large(log->crypt_head, srv_sort_buf_size);
|
2017-09-14 09:23:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (log->crypt_tail) {
|
2019-07-26 17:01:36 +02:00
|
|
|
os_mem_free_large(log->crypt_tail, srv_sort_buf_size);
|
2017-09-14 09:23:20 +03:00
|
|
|
}
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
mutex_free(&log->mutex);
|
2014-05-05 18:20:28 +02:00
|
|
|
ut_free(log);
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************//**
|
|
|
|
Get the latest transaction ID that has invoked row_log_online_op()
|
|
|
|
during online creation.
|
|
|
|
@return latest transaction ID, or 0 if nothing was logged */
|
|
|
|
trx_id_t
|
|
|
|
row_log_get_max_trx(
|
|
|
|
/*================*/
|
|
|
|
dict_index_t* index) /*!< in: index, must be locked */
|
|
|
|
{
|
|
|
|
ut_ad(dict_index_get_online_status(index) == ONLINE_INDEX_CREATION);
|
2016-08-12 11:17:45 +03:00
|
|
|
|
|
|
|
ut_ad((rw_lock_own(dict_index_get_lock(index), RW_LOCK_S)
|
2013-03-26 00:03:13 +02:00
|
|
|
&& mutex_own(&index->online_log->mutex))
|
2016-08-12 11:17:45 +03:00
|
|
|
|| rw_lock_own(dict_index_get_lock(index), RW_LOCK_X));
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
return(index->online_log->max_trx);
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************//**
|
|
|
|
Applies an operation to a secondary index that was being created. */
|
2016-06-21 14:21:03 +02:00
|
|
|
static MY_ATTRIBUTE((nonnull))
|
2013-03-26 00:03:13 +02:00
|
|
|
void
|
|
|
|
row_log_apply_op_low(
|
|
|
|
/*=================*/
|
|
|
|
dict_index_t* index, /*!< in/out: index */
|
|
|
|
row_merge_dup_t*dup, /*!< in/out: for reporting
|
|
|
|
duplicate key errors */
|
|
|
|
dberr_t* error, /*!< out: DB_SUCCESS or error code */
|
|
|
|
mem_heap_t* offsets_heap, /*!< in/out: memory heap for
|
|
|
|
allocating offsets; can be emptied */
|
|
|
|
bool has_index_lock, /*!< in: true if holding index->lock
|
|
|
|
in exclusive mode */
|
|
|
|
enum row_op op, /*!< in: operation being applied */
|
|
|
|
trx_id_t trx_id, /*!< in: transaction identifier */
|
|
|
|
const dtuple_t* entry) /*!< in: row */
|
|
|
|
{
|
|
|
|
mtr_t mtr;
|
|
|
|
btr_cur_t cursor;
|
2020-04-28 10:46:51 +10:00
|
|
|
rec_offs* offsets = NULL;
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
ut_ad(!dict_index_is_clust(index));
|
2016-08-12 11:17:45 +03:00
|
|
|
|
|
|
|
ut_ad(rw_lock_own(dict_index_get_lock(index), RW_LOCK_X)
|
2013-03-26 00:03:13 +02:00
|
|
|
== has_index_lock);
|
2016-08-12 11:17:45 +03:00
|
|
|
|
2018-05-07 15:30:57 +03:00
|
|
|
ut_ad(!index->is_corrupted());
|
2013-03-26 00:03:13 +02:00
|
|
|
ut_ad(trx_id != 0 || op == ROW_OP_DELETE);
|
|
|
|
|
2017-02-16 09:16:11 +02:00
|
|
|
DBUG_LOG("ib_create_index",
|
|
|
|
(op == ROW_OP_INSERT ? "insert " : "delete ")
|
|
|
|
<< (has_index_lock ? "locked index " : "unlocked index ")
|
|
|
|
<< index->id << ',' << ib::hex(trx_id) << ": "
|
|
|
|
<< rec_printer(entry).str());
|
2016-08-12 11:17:45 +03:00
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
mtr_start(&mtr);
|
2018-03-22 15:30:54 +02:00
|
|
|
index->set_modified(mtr);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
/* We perform the pessimistic variant of the operations if we
|
|
|
|
already hold index->lock exclusively. First, search the
|
|
|
|
record. The operation may already have been performed,
|
|
|
|
depending on when the row in the clustered index was
|
|
|
|
scanned. */
|
|
|
|
btr_cur_search_to_nth_level(index, 0, entry, PAGE_CUR_LE,
|
|
|
|
has_index_lock
|
|
|
|
? BTR_MODIFY_TREE
|
|
|
|
: BTR_MODIFY_LEAF,
|
MDEV-27927 row_sel_try_search_shortcut_for_mysql() does not latch a page, violating read view isolation
btr_search_guess_on_hash() would only acquire an index page latch if it
is invoked with ahi_latch=NULL. If it's invoked from
row_sel_try_search_shortcut_for_mysql() with ahi_latch!=NULL, a page
will not be latched, and row_search_mvcc() will get a pointer to the
record, which can be changed by some other transaction before the record
was stored in result buffer with row_sel_store_mysql_rec() call.
ahi_latch argument of btr_cur_search_to_nth_level_func() and
btr_pcur_open_with_no_init_func() is used only for
row_sel_try_search_shortcut_for_mysql().
btr_cur_search_to_nth_level_func(..., ahi_latch !=0, ...) is invoked
only from btr_pcur_open_with_no_init_func(..., ahi_latch !=0, ...),
which, in turns, is invoked only from
row_sel_try_search_shortcut_for_mysql().
I suppose that separate case with ahi_latch!=0 was intentionally
implemented to protect row_sel_store_mysql_rec() call in
row_search_mvcc() just after row_sel_try_search_shortcut_for_mysql()
call. After the ahi_latch was moved from row_seach_mvcc() to
row_sel_try_search_shortcut_for_mysql(), there is no need in it at all
if btr_search_guess_on_hash() latches a page unconditionally. And if
btr_search_guess_on_hash() latched the page, any access to the record in
row_sel_try_search_shortcut_for_mysql() after btr_pcur_open_with_no_init()
call will be protected with the page latch.
The fix is to remove ahi_latch argument from
btr_pcur_open_with_no_init_func(), btr_cur_search_to_nth_level_func()
and btr_search_guess_on_hash().
There will not be test, as to test it we need to freeze some SELECT
execution in the point between row_sel_try_search_shortcut_for_mysql()
and row_sel_store_mysql_rec() calls in row_search_mvcc(), and to change
the record in some other transaction to let row_sel_store_mysql_rec() to
store changed record in result buffer. Buf we can't do this with the
fix, as the page will be latched in btr_search_guess_on_hash() call.
2022-08-31 17:49:38 +03:00
|
|
|
&cursor, __FILE__, __LINE__,
|
2013-03-26 00:03:13 +02:00
|
|
|
&mtr);
|
|
|
|
|
|
|
|
ut_ad(dict_index_get_n_unique(index) > 0);
|
|
|
|
/* This test is somewhat similar to row_ins_must_modify_rec(),
|
|
|
|
but not identical for unique secondary indexes. */
|
|
|
|
if (cursor.low_match >= dict_index_get_n_unique(index)
|
|
|
|
&& !page_rec_is_infimum(btr_cur_get_rec(&cursor))) {
|
|
|
|
/* We have a matching record. */
|
|
|
|
bool exists = (cursor.low_match
|
|
|
|
== dict_index_get_n_fields(index));
|
|
|
|
#ifdef UNIV_DEBUG
|
|
|
|
rec_t* rec = btr_cur_get_rec(&cursor);
|
|
|
|
ut_ad(page_rec_is_user_rec(rec));
|
|
|
|
ut_ad(!rec_get_deleted_flag(rec, page_rec_is_comp(rec)));
|
|
|
|
#endif /* UNIV_DEBUG */
|
|
|
|
|
|
|
|
ut_ad(exists || dict_index_is_unique(index));
|
|
|
|
|
|
|
|
switch (op) {
|
|
|
|
case ROW_OP_DELETE:
|
|
|
|
if (!exists) {
|
2014-02-26 19:23:04 +01:00
|
|
|
/* The existing record matches the
|
|
|
|
unique secondary index key, but the
|
|
|
|
PRIMARY KEY columns differ. So, this
|
|
|
|
exact record does not exist. For
|
|
|
|
example, we could detect a duplicate
|
|
|
|
key error in some old index before
|
|
|
|
logging an ROW_OP_INSERT for our
|
|
|
|
index. This ROW_OP_DELETE could have
|
|
|
|
been logged for rolling back
|
|
|
|
TRX_UNDO_INSERT_REC. */
|
2013-03-26 00:03:13 +02:00
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (btr_cur_optimistic_delete(
|
|
|
|
&cursor, BTR_CREATE_FLAG, &mtr)) {
|
|
|
|
*error = DB_SUCCESS;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!has_index_lock) {
|
|
|
|
/* This needs a pessimistic operation.
|
|
|
|
Lock the index tree exclusively. */
|
|
|
|
mtr_commit(&mtr);
|
|
|
|
mtr_start(&mtr);
|
2018-03-22 15:30:54 +02:00
|
|
|
index->set_modified(mtr);
|
2013-03-26 00:03:13 +02:00
|
|
|
btr_cur_search_to_nth_level(
|
|
|
|
index, 0, entry, PAGE_CUR_LE,
|
MDEV-27927 row_sel_try_search_shortcut_for_mysql() does not latch a page, violating read view isolation
btr_search_guess_on_hash() would only acquire an index page latch if it
is invoked with ahi_latch=NULL. If it's invoked from
row_sel_try_search_shortcut_for_mysql() with ahi_latch!=NULL, a page
will not be latched, and row_search_mvcc() will get a pointer to the
record, which can be changed by some other transaction before the record
was stored in result buffer with row_sel_store_mysql_rec() call.
ahi_latch argument of btr_cur_search_to_nth_level_func() and
btr_pcur_open_with_no_init_func() is used only for
row_sel_try_search_shortcut_for_mysql().
btr_cur_search_to_nth_level_func(..., ahi_latch !=0, ...) is invoked
only from btr_pcur_open_with_no_init_func(..., ahi_latch !=0, ...),
which, in turns, is invoked only from
row_sel_try_search_shortcut_for_mysql().
I suppose that separate case with ahi_latch!=0 was intentionally
implemented to protect row_sel_store_mysql_rec() call in
row_search_mvcc() just after row_sel_try_search_shortcut_for_mysql()
call. After the ahi_latch was moved from row_seach_mvcc() to
row_sel_try_search_shortcut_for_mysql(), there is no need in it at all
if btr_search_guess_on_hash() latches a page unconditionally. And if
btr_search_guess_on_hash() latched the page, any access to the record in
row_sel_try_search_shortcut_for_mysql() after btr_pcur_open_with_no_init()
call will be protected with the page latch.
The fix is to remove ahi_latch argument from
btr_pcur_open_with_no_init_func(), btr_cur_search_to_nth_level_func()
and btr_search_guess_on_hash().
There will not be test, as to test it we need to freeze some SELECT
execution in the point between row_sel_try_search_shortcut_for_mysql()
and row_sel_store_mysql_rec() calls in row_search_mvcc(), and to change
the record in some other transaction to let row_sel_store_mysql_rec() to
store changed record in result buffer. Buf we can't do this with the
fix, as the page will be latched in btr_search_guess_on_hash() call.
2022-08-31 17:49:38 +03:00
|
|
|
BTR_MODIFY_TREE, &cursor,
|
2013-03-26 00:03:13 +02:00
|
|
|
__FILE__, __LINE__, &mtr);
|
|
|
|
|
|
|
|
/* No other thread than the current one
|
|
|
|
is allowed to modify the index tree.
|
|
|
|
Thus, the record should still exist. */
|
|
|
|
ut_ad(cursor.low_match
|
|
|
|
>= dict_index_get_n_fields(index));
|
|
|
|
ut_ad(page_rec_is_user_rec(
|
|
|
|
btr_cur_get_rec(&cursor)));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* As there are no externally stored fields in
|
|
|
|
a secondary index record, the parameter
|
2016-08-12 11:17:45 +03:00
|
|
|
rollback=false will be ignored. */
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
btr_cur_pessimistic_delete(
|
|
|
|
error, FALSE, &cursor,
|
2016-08-12 11:17:45 +03:00
|
|
|
BTR_CREATE_FLAG, false, &mtr);
|
2013-03-26 00:03:13 +02:00
|
|
|
break;
|
|
|
|
case ROW_OP_INSERT:
|
|
|
|
if (exists) {
|
|
|
|
/* The record already exists. There
|
2014-02-26 19:23:04 +01:00
|
|
|
is nothing to be inserted.
|
|
|
|
This could happen when processing
|
|
|
|
TRX_UNDO_DEL_MARK_REC in statement
|
|
|
|
rollback:
|
|
|
|
|
|
|
|
UPDATE of PRIMARY KEY can lead to
|
|
|
|
statement rollback if the updated
|
|
|
|
value of the PRIMARY KEY already
|
|
|
|
exists. In this case, the UPDATE would
|
|
|
|
be mapped to DELETE;INSERT, and we
|
|
|
|
only wrote undo log for the DELETE
|
|
|
|
part. The duplicate key error would be
|
|
|
|
triggered before logging the INSERT
|
|
|
|
part.
|
|
|
|
|
|
|
|
Theoretically, we could also get a
|
|
|
|
similar situation when a DELETE operation
|
|
|
|
is blocked by a FOREIGN KEY constraint. */
|
2013-03-26 00:03:13 +02:00
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dtuple_contains_null(entry)) {
|
|
|
|
/* The UNIQUE KEY columns match, but
|
|
|
|
there is a NULL value in the key, and
|
|
|
|
NULL!=NULL. */
|
|
|
|
goto insert_the_rec;
|
|
|
|
}
|
|
|
|
|
2014-02-26 19:23:04 +01:00
|
|
|
goto duplicate;
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
switch (op) {
|
|
|
|
rec_t* rec;
|
|
|
|
big_rec_t* big_rec;
|
|
|
|
case ROW_OP_DELETE:
|
2014-02-26 19:23:04 +01:00
|
|
|
/* The record does not exist. For example, we
|
|
|
|
could detect a duplicate key error in some old
|
|
|
|
index before logging an ROW_OP_INSERT for our
|
|
|
|
index. This ROW_OP_DELETE could be logged for
|
|
|
|
rolling back TRX_UNDO_INSERT_REC. */
|
2013-03-26 00:03:13 +02:00
|
|
|
goto func_exit;
|
|
|
|
case ROW_OP_INSERT:
|
|
|
|
if (dict_index_is_unique(index)
|
|
|
|
&& (cursor.up_match
|
|
|
|
>= dict_index_get_n_unique(index)
|
|
|
|
|| cursor.low_match
|
|
|
|
>= dict_index_get_n_unique(index))
|
|
|
|
&& (!index->n_nullable
|
|
|
|
|| !dtuple_contains_null(entry))) {
|
2014-02-26 19:23:04 +01:00
|
|
|
duplicate:
|
2013-03-26 00:03:13 +02:00
|
|
|
/* Duplicate key */
|
2014-02-26 19:23:04 +01:00
|
|
|
ut_ad(dict_index_is_unique(index));
|
2013-03-26 00:03:13 +02:00
|
|
|
row_merge_dup_report(dup, entry->fields);
|
2014-02-26 19:23:04 +01:00
|
|
|
*error = DB_DUPLICATE_KEY;
|
2013-03-26 00:03:13 +02:00
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
insert_the_rec:
|
|
|
|
/* Insert the record. As we are inserting into
|
|
|
|
a secondary index, there cannot be externally
|
|
|
|
stored columns (!big_rec). */
|
|
|
|
*error = btr_cur_optimistic_insert(
|
|
|
|
BTR_NO_UNDO_LOG_FLAG
|
|
|
|
| BTR_NO_LOCKING_FLAG
|
|
|
|
| BTR_CREATE_FLAG,
|
|
|
|
&cursor, &offsets, &offsets_heap,
|
|
|
|
const_cast<dtuple_t*>(entry),
|
|
|
|
&rec, &big_rec, 0, NULL, &mtr);
|
|
|
|
ut_ad(!big_rec);
|
|
|
|
if (*error != DB_FAIL) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!has_index_lock) {
|
|
|
|
/* This needs a pessimistic operation.
|
|
|
|
Lock the index tree exclusively. */
|
|
|
|
mtr_commit(&mtr);
|
|
|
|
mtr_start(&mtr);
|
2018-03-22 15:30:54 +02:00
|
|
|
index->set_modified(mtr);
|
2013-03-26 00:03:13 +02:00
|
|
|
btr_cur_search_to_nth_level(
|
|
|
|
index, 0, entry, PAGE_CUR_LE,
|
MDEV-27927 row_sel_try_search_shortcut_for_mysql() does not latch a page, violating read view isolation
btr_search_guess_on_hash() would only acquire an index page latch if it
is invoked with ahi_latch=NULL. If it's invoked from
row_sel_try_search_shortcut_for_mysql() with ahi_latch!=NULL, a page
will not be latched, and row_search_mvcc() will get a pointer to the
record, which can be changed by some other transaction before the record
was stored in result buffer with row_sel_store_mysql_rec() call.
ahi_latch argument of btr_cur_search_to_nth_level_func() and
btr_pcur_open_with_no_init_func() is used only for
row_sel_try_search_shortcut_for_mysql().
btr_cur_search_to_nth_level_func(..., ahi_latch !=0, ...) is invoked
only from btr_pcur_open_with_no_init_func(..., ahi_latch !=0, ...),
which, in turns, is invoked only from
row_sel_try_search_shortcut_for_mysql().
I suppose that separate case with ahi_latch!=0 was intentionally
implemented to protect row_sel_store_mysql_rec() call in
row_search_mvcc() just after row_sel_try_search_shortcut_for_mysql()
call. After the ahi_latch was moved from row_seach_mvcc() to
row_sel_try_search_shortcut_for_mysql(), there is no need in it at all
if btr_search_guess_on_hash() latches a page unconditionally. And if
btr_search_guess_on_hash() latched the page, any access to the record in
row_sel_try_search_shortcut_for_mysql() after btr_pcur_open_with_no_init()
call will be protected with the page latch.
The fix is to remove ahi_latch argument from
btr_pcur_open_with_no_init_func(), btr_cur_search_to_nth_level_func()
and btr_search_guess_on_hash().
There will not be test, as to test it we need to freeze some SELECT
execution in the point between row_sel_try_search_shortcut_for_mysql()
and row_sel_store_mysql_rec() calls in row_search_mvcc(), and to change
the record in some other transaction to let row_sel_store_mysql_rec() to
store changed record in result buffer. Buf we can't do this with the
fix, as the page will be latched in btr_search_guess_on_hash() call.
2022-08-31 17:49:38 +03:00
|
|
|
BTR_MODIFY_TREE, &cursor,
|
2013-03-26 00:03:13 +02:00
|
|
|
__FILE__, __LINE__, &mtr);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We already determined that the
|
|
|
|
record did not exist. No other thread
|
|
|
|
than the current one is allowed to
|
|
|
|
modify the index tree. Thus, the
|
|
|
|
record should still not exist. */
|
|
|
|
|
|
|
|
*error = btr_cur_pessimistic_insert(
|
|
|
|
BTR_NO_UNDO_LOG_FLAG
|
|
|
|
| BTR_NO_LOCKING_FLAG
|
|
|
|
| BTR_CREATE_FLAG,
|
|
|
|
&cursor, &offsets, &offsets_heap,
|
|
|
|
const_cast<dtuple_t*>(entry),
|
|
|
|
&rec, &big_rec,
|
|
|
|
0, NULL, &mtr);
|
|
|
|
ut_ad(!big_rec);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
mem_heap_empty(offsets_heap);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*error == DB_SUCCESS && trx_id) {
|
|
|
|
page_update_max_trx_id(btr_cur_get_block(&cursor),
|
|
|
|
btr_cur_get_page_zip(&cursor),
|
|
|
|
trx_id, &mtr);
|
|
|
|
}
|
|
|
|
|
|
|
|
func_exit:
|
|
|
|
mtr_commit(&mtr);
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************//**
|
|
|
|
Applies an operation to a secondary index that was being created.
|
|
|
|
@return NULL on failure (mrec corruption) or when out of data;
|
|
|
|
pointer to next record on success */
|
2016-06-21 14:21:03 +02:00
|
|
|
static MY_ATTRIBUTE((nonnull, warn_unused_result))
|
2013-03-26 00:03:13 +02:00
|
|
|
const mrec_t*
|
|
|
|
row_log_apply_op(
|
|
|
|
/*=============*/
|
|
|
|
dict_index_t* index, /*!< in/out: index */
|
|
|
|
row_merge_dup_t*dup, /*!< in/out: for reporting
|
|
|
|
duplicate key errors */
|
|
|
|
dberr_t* error, /*!< out: DB_SUCCESS or error code */
|
|
|
|
mem_heap_t* offsets_heap, /*!< in/out: memory heap for
|
|
|
|
allocating offsets; can be emptied */
|
|
|
|
mem_heap_t* heap, /*!< in/out: memory heap for
|
|
|
|
allocating data tuples */
|
|
|
|
bool has_index_lock, /*!< in: true if holding index->lock
|
|
|
|
in exclusive mode */
|
|
|
|
const mrec_t* mrec, /*!< in: merge record */
|
|
|
|
const mrec_t* mrec_end, /*!< in: end of buffer */
|
2020-04-28 10:46:51 +10:00
|
|
|
rec_offs* offsets) /*!< in/out: work area for
|
2013-03-26 00:03:13 +02:00
|
|
|
rec_init_offsets_temp() */
|
|
|
|
|
|
|
|
{
|
|
|
|
enum row_op op;
|
|
|
|
ulint extra_size;
|
|
|
|
ulint data_size;
|
|
|
|
dtuple_t* entry;
|
|
|
|
trx_id_t trx_id;
|
|
|
|
|
|
|
|
/* Online index creation is only used for secondary indexes. */
|
|
|
|
ut_ad(!dict_index_is_clust(index));
|
2016-08-12 11:17:45 +03:00
|
|
|
|
|
|
|
ut_ad(rw_lock_own(dict_index_get_lock(index), RW_LOCK_X)
|
2013-03-26 00:03:13 +02:00
|
|
|
== has_index_lock);
|
|
|
|
|
2018-05-07 15:30:57 +03:00
|
|
|
if (index->is_corrupted()) {
|
2013-03-26 00:03:13 +02:00
|
|
|
*error = DB_INDEX_CORRUPT;
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
*error = DB_SUCCESS;
|
|
|
|
|
|
|
|
if (mrec + ROW_LOG_HEADER_SIZE >= mrec_end) {
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (*mrec) {
|
|
|
|
case ROW_OP_INSERT:
|
|
|
|
if (ROW_LOG_HEADER_SIZE + DATA_TRX_ID_LEN + mrec >= mrec_end) {
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
op = static_cast<enum row_op>(*mrec++);
|
|
|
|
trx_id = trx_read_trx_id(mrec);
|
|
|
|
mrec += DATA_TRX_ID_LEN;
|
|
|
|
break;
|
|
|
|
case ROW_OP_DELETE:
|
|
|
|
op = static_cast<enum row_op>(*mrec++);
|
|
|
|
trx_id = 0;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
corrupted:
|
|
|
|
ut_ad(0);
|
|
|
|
*error = DB_CORRUPTION;
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
extra_size = *mrec++;
|
|
|
|
|
|
|
|
ut_ad(mrec < mrec_end);
|
|
|
|
|
|
|
|
if (extra_size >= 0x80) {
|
|
|
|
/* Read another byte of extra_size. */
|
|
|
|
|
|
|
|
extra_size = (extra_size & 0x7f) << 8;
|
|
|
|
extra_size |= *mrec++;
|
|
|
|
}
|
|
|
|
|
|
|
|
mrec += extra_size;
|
|
|
|
|
|
|
|
if (mrec > mrec_end) {
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
rec_init_offsets_temp(mrec, index, offsets);
|
|
|
|
|
|
|
|
if (rec_offs_any_extern(offsets)) {
|
|
|
|
/* There should never be any externally stored fields
|
|
|
|
in a secondary index, which is what online index
|
|
|
|
creation is used for. Therefore, the log file must be
|
|
|
|
corrupted. */
|
|
|
|
goto corrupted;
|
|
|
|
}
|
|
|
|
|
|
|
|
data_size = rec_offs_data_size(offsets);
|
|
|
|
|
|
|
|
mrec += data_size;
|
|
|
|
|
|
|
|
if (mrec > mrec_end) {
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
entry = row_rec_to_index_entry_low(
|
2020-01-17 11:11:19 +02:00
|
|
|
mrec - data_size, index, offsets, heap);
|
2013-03-26 00:03:13 +02:00
|
|
|
/* Online index creation is only implemented for secondary
|
|
|
|
indexes, which never contain off-page columns. */
|
2020-01-17 11:11:19 +02:00
|
|
|
ut_ad(dtuple_get_n_ext(entry) == 0);
|
2016-08-12 11:17:45 +03:00
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
row_log_apply_op_low(index, dup, error, offsets_heap,
|
|
|
|
has_index_lock, op, trx_id, entry);
|
|
|
|
return(mrec);
|
|
|
|
}
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
/** Applies operations to a secondary index that was being created.
|
|
|
|
@param[in] trx transaction (for checking if the operation was
|
|
|
|
interrupted)
|
|
|
|
@param[in,out] index index
|
|
|
|
@param[in,out] dup for reporting duplicate key errors
|
|
|
|
@param[in,out] stage performance schema accounting object, used by
|
|
|
|
ALTER TABLE. If not NULL, then stage->inc() will be called for each block
|
|
|
|
of log that is applied.
|
2013-03-26 00:03:13 +02:00
|
|
|
@return DB_SUCCESS, or error code on failure */
|
2016-09-06 09:43:16 +03:00
|
|
|
static
|
2013-03-26 00:03:13 +02:00
|
|
|
dberr_t
|
|
|
|
row_log_apply_ops(
|
2016-08-12 11:17:45 +03:00
|
|
|
const trx_t* trx,
|
|
|
|
dict_index_t* index,
|
|
|
|
row_merge_dup_t* dup,
|
|
|
|
ut_stage_alter_t* stage)
|
2013-03-26 00:03:13 +02:00
|
|
|
{
|
|
|
|
dberr_t error;
|
|
|
|
const mrec_t* mrec = NULL;
|
|
|
|
const mrec_t* next_mrec;
|
|
|
|
const mrec_t* mrec_end= NULL; /* silence bogus warning */
|
|
|
|
const mrec_t* next_mrec_end;
|
|
|
|
mem_heap_t* offsets_heap;
|
|
|
|
mem_heap_t* heap;
|
2020-04-28 10:46:51 +10:00
|
|
|
rec_offs* offsets;
|
2013-03-26 00:03:13 +02:00
|
|
|
bool has_index_lock;
|
|
|
|
const ulint i = 1 + REC_OFFS_HEADER_SIZE
|
|
|
|
+ dict_index_get_n_fields(index);
|
|
|
|
|
|
|
|
ut_ad(dict_index_is_online_ddl(index));
|
2016-08-12 11:17:45 +03:00
|
|
|
ut_ad(!index->is_committed());
|
|
|
|
ut_ad(rw_lock_own(dict_index_get_lock(index), RW_LOCK_X));
|
2013-03-26 00:03:13 +02:00
|
|
|
ut_ad(index->online_log);
|
2020-07-02 16:52:13 +03:00
|
|
|
|
MDEV-20377: Make WITH_MSAN more usable
MemorySanitizer (clang -fsanitize=memory) requires that all code
be compiled with instrumentation enabled. The only exception is the
C runtime library. Failure to use instrumented libraries will cause
bogus messages about memory being uninitialized.
In WITH_MSAN builds, we must avoid calling getservbyname(),
because even though it is a standard library function, it is
not instrumented, not even in clang 10.
Note: Before MariaDB Server 10.5, ./mtr will typically fail
due to the old PCRE library, which was updated in MDEV-14024.
The following cmake options were tested on 10.5
in commit 94d0bb4dbeb28a94d1f87fdd55f4297ff3df0157:
cmake \
-DCMAKE_C_FLAGS='-march=native -O2' \
-DCMAKE_CXX_FLAGS='-stdlib=libc++ -march=native -O2' \
-DWITH_EMBEDDED_SERVER=OFF -DWITH_UNIT_TESTS=OFF -DCMAKE_BUILD_TYPE=Debug \
-DWITH_INNODB_{BZIP2,LZ4,LZMA,LZO,SNAPPY}=OFF \
-DPLUGIN_{ARCHIVE,TOKUDB,MROONGA,OQGRAPH,ROCKSDB,CONNECT,SPIDER}=NO \
-DWITH_SAFEMALLOC=OFF \
-DWITH_{ZLIB,SSL,PCRE}=bundled \
-DHAVE_LIBAIO_H=0 \
-DWITH_MSAN=ON
MEM_MAKE_DEFINED(): An alias for VALGRIND_MAKE_MEM_DEFINED()
and __msan_unpoison().
MEM_GET_VBITS(), MEM_SET_VBITS(): Aliases for
VALGRIND_GET_VBITS(), VALGRIND_SET_VBITS(), __msan_copy_shadow().
InnoDB: Replace the UNIV_MEM_ macros with corresponding MEM_ macros.
ut_crc32_8_hw(), ut_crc32_64_low_hw(): Use the compiler built-in
functions instead of inline assembler when building WITH_MSAN.
This will require at least -msse4.2 when building for IA-32 or AMD64.
The inline assembler would not be instrumented, and would thus cause
bogus failures.
2020-07-01 17:23:00 +03:00
|
|
|
MEM_UNDEFINED(&mrec_end, sizeof mrec_end);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2020-04-28 10:46:51 +10:00
|
|
|
offsets = static_cast<rec_offs*>(ut_malloc_nokey(i * sizeof *offsets));
|
MDEV-20950 Reduce size of record offsets
offset_t: this is a type which represents one record offset.
It's unsigned short int.
a lot of functions: replace ulint with offset_t
btr_pcur_restore_position_func(),
page_validate(),
row_ins_scan_sec_index_for_duplicate(),
row_upd_clust_rec_by_insert_inherit_func(),
row_vers_impl_x_locked_low(),
trx_undo_prev_version_build():
allocate record offsets on the stack instead of waiting for rec_get_offsets()
to allocate it from mem_heap_t. So, reducing memory allocations.
RECORD_OFFSET, INDEX_OFFSET:
now it's less convenient to store pointers in offset_t*
array. One pointer occupies now several offset_t. And those constant are start
indexes into array to places where to store pointer values
REC_OFFS_HEADER_SIZE: adjusted for the new reality
REC_OFFS_NORMAL_SIZE:
increase size from 100 to 300 which means less heap allocations.
And sizeof(offset_t[REC_OFFS_NORMAL_SIZE]) now is 600 bytes which
is smaller than previous 800 bytes.
REC_OFFS_SEC_INDEX_SIZE: adjusted for the new reality
rem0rec.h, rem0rec.ic, rem0rec.cc:
various arguments, return values and local variables types were changed to
fix numerous integer conversions issues.
enum field_type_t:
offset types concept was introduces which replaces old offset flags stuff.
Like in earlier version, 2 upper bits are used to store offset type.
And this enum represents those types.
REC_OFFS_SQL_NULL, REC_OFFS_MASK: removed
get_type(), set_type(), get_value(), combine():
these are convenience functions to work with offsets and it's types
rec_offs_base()[0]:
still uses an old scheme with flags REC_OFFS_COMPACT and REC_OFFS_EXTERNAL
rec_offs_base()[i]:
these have type offset_t now. Two upper bits contains type.
2019-11-04 22:30:12 +03:00
|
|
|
rec_offs_set_n_alloc(offsets, i);
|
|
|
|
rec_offs_set_n_fields(offsets, dict_index_get_n_fields(index));
|
2013-03-26 00:03:13 +02:00
|
|
|
|
2018-04-27 13:49:25 +03:00
|
|
|
offsets_heap = mem_heap_create(srv_page_size);
|
|
|
|
heap = mem_heap_create(srv_page_size);
|
2013-03-26 00:03:13 +02:00
|
|
|
has_index_lock = true;
|
|
|
|
|
|
|
|
next_block:
|
|
|
|
ut_ad(has_index_lock);
|
2016-08-12 11:17:45 +03:00
|
|
|
ut_ad(rw_lock_own(dict_index_get_lock(index), RW_LOCK_X));
|
2013-03-26 00:03:13 +02:00
|
|
|
ut_ad(index->online_log->head.bytes == 0);
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
stage->inc(row_log_progress_inc_per_block());
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
if (trx_is_interrupted(trx)) {
|
|
|
|
goto interrupted;
|
|
|
|
}
|
|
|
|
|
2014-05-05 18:20:28 +02:00
|
|
|
error = index->online_log->error;
|
|
|
|
if (error != DB_SUCCESS) {
|
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
|
2018-05-07 15:30:57 +03:00
|
|
|
if (index->is_corrupted()) {
|
2013-03-26 00:03:13 +02:00
|
|
|
error = DB_INDEX_CORRUPT;
|
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (UNIV_UNLIKELY(index->online_log->head.blocks
|
|
|
|
> index->online_log->tail.blocks)) {
|
|
|
|
unexpected_eof:
|
2016-08-12 11:17:45 +03:00
|
|
|
ib::error() << "Unexpected end of temporary file for index "
|
|
|
|
<< index->name;
|
2013-03-26 00:03:13 +02:00
|
|
|
corruption:
|
|
|
|
error = DB_CORRUPTION;
|
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (index->online_log->head.blocks
|
|
|
|
== index->online_log->tail.blocks) {
|
|
|
|
if (index->online_log->head.blocks) {
|
|
|
|
#ifdef HAVE_FTRUNCATE
|
|
|
|
/* Truncate the file in order to save space. */
|
2016-08-12 11:17:45 +03:00
|
|
|
if (index->online_log->fd > 0
|
2016-02-16 12:07:18 +01:00
|
|
|
&& ftruncate(index->online_log->fd, 0) == -1) {
|
2016-08-12 11:17:45 +03:00
|
|
|
ib::error()
|
|
|
|
<< "\'" << index->name + 1
|
|
|
|
<< "\' failed with error "
|
|
|
|
<< errno << ":" << strerror(errno);
|
|
|
|
|
2013-08-23 09:03:57 +03:00
|
|
|
goto corruption;
|
|
|
|
}
|
2013-03-26 00:03:13 +02:00
|
|
|
#endif /* HAVE_FTRUNCATE */
|
|
|
|
index->online_log->head.blocks
|
|
|
|
= index->online_log->tail.blocks = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
next_mrec = index->online_log->tail.block;
|
|
|
|
next_mrec_end = next_mrec + index->online_log->tail.bytes;
|
|
|
|
|
|
|
|
if (next_mrec_end == next_mrec) {
|
|
|
|
/* End of log reached. */
|
|
|
|
all_done:
|
|
|
|
ut_ad(has_index_lock);
|
|
|
|
ut_ad(index->online_log->head.blocks == 0);
|
|
|
|
ut_ad(index->online_log->tail.blocks == 0);
|
|
|
|
error = DB_SUCCESS;
|
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
} else {
|
2017-05-23 11:09:47 +03:00
|
|
|
os_offset_t ofs = static_cast<os_offset_t>(
|
|
|
|
index->online_log->head.blocks)
|
2013-03-26 00:03:13 +02:00
|
|
|
* srv_sort_buf_size;
|
2017-05-23 11:09:47 +03:00
|
|
|
IORequest request(IORequest::READ);
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
ut_ad(has_index_lock);
|
|
|
|
has_index_lock = false;
|
|
|
|
rw_lock_x_unlock(dict_index_get_lock(index));
|
|
|
|
|
|
|
|
log_free_check();
|
|
|
|
|
2014-05-05 18:20:28 +02:00
|
|
|
if (!row_log_block_allocate(index->online_log->head)) {
|
|
|
|
error = DB_OUT_OF_MEMORY;
|
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
|
2017-09-16 22:31:53 +03:00
|
|
|
byte* buf = index->online_log->head.block;
|
2017-09-14 09:23:20 +03:00
|
|
|
|
2019-04-27 20:41:31 +03:00
|
|
|
if (os_file_read_no_error_handling(
|
2017-09-16 22:31:53 +03:00
|
|
|
request, index->online_log->fd,
|
2019-04-27 20:41:31 +03:00
|
|
|
buf, ofs, srv_sort_buf_size, 0) != DB_SUCCESS) {
|
2016-08-12 11:17:45 +03:00
|
|
|
ib::error()
|
|
|
|
<< "Unable to read temporary file"
|
|
|
|
" for index " << index->name;
|
2013-03-26 00:03:13 +02:00
|
|
|
goto corruption;
|
|
|
|
}
|
|
|
|
|
2017-09-14 09:23:20 +03:00
|
|
|
if (log_tmp_is_encrypted()) {
|
2017-09-16 22:31:53 +03:00
|
|
|
if (!log_tmp_block_decrypt(
|
|
|
|
buf, srv_sort_buf_size,
|
2019-06-27 16:23:03 +05:30
|
|
|
index->online_log->crypt_head, ofs)) {
|
2017-09-14 09:23:20 +03:00
|
|
|
error = DB_DECRYPTION_FAILED;
|
|
|
|
goto func_exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
srv_stats.n_rowlog_blocks_decrypted.inc();
|
|
|
|
memcpy(buf, index->online_log->crypt_head, srv_sort_buf_size);
|
|
|
|
}
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
#ifdef POSIX_FADV_DONTNEED
|
|
|
|
/* Each block is read exactly once. Free up the file cache. */
|
|
|
|
posix_fadvise(index->online_log->fd,
|
|
|
|
ofs, srv_sort_buf_size, POSIX_FADV_DONTNEED);
|
|
|
|
#endif /* POSIX_FADV_DONTNEED */
|
|
|
|
|
|
|
|
next_mrec = index->online_log->head.block;
|
|
|
|
next_mrec_end = next_mrec + srv_sort_buf_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mrec) {
|
|
|
|
/* A partial record was read from the previous block.
|
|
|
|
Copy the temporary buffer full, as we do not know the
|
|
|
|
length of the record. Parse subsequent records from
|
|
|
|
the bigger buffer index->online_log->head.block
|
|
|
|
or index->online_log->tail.block. */
|
|
|
|
|
|
|
|
ut_ad(mrec == index->online_log->head.buf);
|
|
|
|
ut_ad(mrec_end > mrec);
|
|
|
|
ut_ad(mrec_end < (&index->online_log->head.buf)[1]);
|
|
|
|
|
|
|
|
memcpy((mrec_t*) mrec_end, next_mrec,
|
2018-04-28 15:49:09 +03:00
|
|
|
ulint((&index->online_log->head.buf)[1] - mrec_end));
|
2013-03-26 00:03:13 +02:00
|
|
|
mrec = row_log_apply_op(
|
|
|
|
index, dup, &error, offsets_heap, heap,
|
|
|
|
has_index_lock, index->online_log->head.buf,
|
|
|
|
(&index->online_log->head.buf)[1], offsets);
|
|
|
|
if (error != DB_SUCCESS) {
|
|
|
|
goto func_exit;
|
|
|
|
} else if (UNIV_UNLIKELY(mrec == NULL)) {
|
|
|
|
/* The record was not reassembled properly. */
|
|
|
|
goto corruption;
|
|
|
|
}
|
|
|
|
/* The record was previously found out to be
|
|
|
|
truncated. Now that the parse buffer was extended,
|
|
|
|
it should proceed beyond the old end of the buffer. */
|
|
|
|
ut_a(mrec > mrec_end);
|
|
|
|
|
2018-04-28 15:49:09 +03:00
|
|
|
index->online_log->head.bytes = ulint(mrec - mrec_end);
|
2013-03-26 00:03:13 +02:00
|
|
|
next_mrec += index->online_log->head.bytes;
|
|
|
|
}
|
|
|
|
|
|
|
|
ut_ad(next_mrec <= next_mrec_end);
|
|
|
|
/* The following loop must not be parsing the temporary
|
|
|
|
buffer, but head.block or tail.block. */
|
|
|
|
|
|
|
|
/* mrec!=NULL means that the next record starts from the
|
|
|
|
middle of the block */
|
|
|
|
ut_ad((mrec == NULL) == (index->online_log->head.bytes == 0));
|
|
|
|
|
|
|
|
#ifdef UNIV_DEBUG
|
|
|
|
if (next_mrec_end == index->online_log->head.block
|
|
|
|
+ srv_sort_buf_size) {
|
|
|
|
/* If tail.bytes == 0, next_mrec_end can also be at
|
|
|
|
the end of tail.block. */
|
|
|
|
if (index->online_log->tail.bytes == 0) {
|
|
|
|
ut_ad(next_mrec == next_mrec_end);
|
|
|
|
ut_ad(index->online_log->tail.blocks == 0);
|
|
|
|
ut_ad(index->online_log->head.blocks == 0);
|
|
|
|
ut_ad(index->online_log->head.bytes == 0);
|
|
|
|
} else {
|
|
|
|
ut_ad(next_mrec == index->online_log->head.block
|
|
|
|
+ index->online_log->head.bytes);
|
|
|
|
ut_ad(index->online_log->tail.blocks
|
|
|
|
> index->online_log->head.blocks);
|
|
|
|
}
|
|
|
|
} else if (next_mrec_end == index->online_log->tail.block
|
|
|
|
+ index->online_log->tail.bytes) {
|
|
|
|
ut_ad(next_mrec == index->online_log->tail.block
|
|
|
|
+ index->online_log->head.bytes);
|
|
|
|
ut_ad(index->online_log->tail.blocks == 0);
|
|
|
|
ut_ad(index->online_log->head.blocks == 0);
|
|
|
|
ut_ad(index->online_log->head.bytes
|
|
|
|
<= index->online_log->tail.bytes);
|
|
|
|
} else {
|
|
|
|
ut_error;
|
|
|
|
}
|
|
|
|
#endif /* UNIV_DEBUG */
|
|
|
|
|
|
|
|
mrec_end = next_mrec_end;
|
|
|
|
|
|
|
|
while (!trx_is_interrupted(trx)) {
|
|
|
|
mrec = next_mrec;
|
|
|
|
ut_ad(mrec < mrec_end);
|
|
|
|
|
|
|
|
if (!has_index_lock) {
|
|
|
|
/* We are applying operations from a different
|
|
|
|
block than the one that is being written to.
|
|
|
|
We do not hold index->lock in order to
|
|
|
|
allow other threads to concurrently buffer
|
|
|
|
modifications. */
|
|
|
|
ut_ad(mrec >= index->online_log->head.block);
|
|
|
|
ut_ad(mrec_end == index->online_log->head.block
|
|
|
|
+ srv_sort_buf_size);
|
|
|
|
ut_ad(index->online_log->head.bytes
|
|
|
|
< srv_sort_buf_size);
|
|
|
|
|
|
|
|
/* Take the opportunity to do a redo log
|
|
|
|
checkpoint if needed. */
|
|
|
|
log_free_check();
|
|
|
|
} else {
|
|
|
|
/* We are applying operations from the last block.
|
|
|
|
Do not allow other threads to buffer anything,
|
|
|
|
so that we can finally catch up and synchronize. */
|
|
|
|
ut_ad(index->online_log->head.blocks == 0);
|
|
|
|
ut_ad(index->online_log->tail.blocks == 0);
|
|
|
|
ut_ad(mrec_end == index->online_log->tail.block
|
|
|
|
+ index->online_log->tail.bytes);
|
|
|
|
ut_ad(mrec >= index->online_log->tail.block);
|
|
|
|
}
|
|
|
|
|
|
|
|
next_mrec = row_log_apply_op(
|
|
|
|
index, dup, &error, offsets_heap, heap,
|
|
|
|
has_index_lock, mrec, mrec_end, offsets);
|
|
|
|
|
|
|
|
if (error != DB_SUCCESS) {
|
|
|
|
goto func_exit;
|
|
|
|
} else if (next_mrec == next_mrec_end) {
|
|
|
|
/* The record happened to end on a block boundary.
|
|
|
|
Do we have more blocks left? */
|
|
|
|
if (has_index_lock) {
|
|
|
|
/* The index will be locked while
|
|
|
|
applying the last block. */
|
|
|
|
goto all_done;
|
|
|
|
}
|
|
|
|
|
|
|
|
mrec = NULL;
|
|
|
|
process_next_block:
|
|
|
|
rw_lock_x_lock(dict_index_get_lock(index));
|
|
|
|
has_index_lock = true;
|
|
|
|
|
|
|
|
index->online_log->head.bytes = 0;
|
|
|
|
index->online_log->head.blocks++;
|
|
|
|
goto next_block;
|
|
|
|
} else if (next_mrec != NULL) {
|
|
|
|
ut_ad(next_mrec < next_mrec_end);
|
2018-04-28 15:49:09 +03:00
|
|
|
index->online_log->head.bytes
|
|
|
|
+= ulint(next_mrec - mrec);
|
2013-03-26 00:03:13 +02:00
|
|
|
} else if (has_index_lock) {
|
|
|
|
/* When mrec is within tail.block, it should
|
|
|
|
be a complete record, because we are holding
|
|
|
|
index->lock and thus excluding the writer. */
|
|
|
|
ut_ad(index->online_log->tail.blocks == 0);
|
|
|
|
ut_ad(mrec_end == index->online_log->tail.block
|
|
|
|
+ index->online_log->tail.bytes);
|
|
|
|
ut_ad(0);
|
|
|
|
goto unexpected_eof;
|
|
|
|
} else {
|
|
|
|
memcpy(index->online_log->head.buf, mrec,
|
2018-04-28 15:49:09 +03:00
|
|
|
ulint(mrec_end - mrec));
|
|
|
|
mrec_end += ulint(index->online_log->head.buf - mrec);
|
2013-03-26 00:03:13 +02:00
|
|
|
mrec = index->online_log->head.buf;
|
|
|
|
goto process_next_block;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
interrupted:
|
|
|
|
error = DB_INTERRUPTED;
|
|
|
|
func_exit:
|
|
|
|
if (!has_index_lock) {
|
|
|
|
rw_lock_x_lock(dict_index_get_lock(index));
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (error) {
|
|
|
|
case DB_SUCCESS:
|
|
|
|
break;
|
|
|
|
case DB_INDEX_CORRUPT:
|
|
|
|
if (((os_offset_t) index->online_log->tail.blocks + 1)
|
|
|
|
* srv_sort_buf_size >= srv_online_max_size) {
|
|
|
|
/* The log file grew too big. */
|
|
|
|
error = DB_ONLINE_LOG_TOO_BIG;
|
|
|
|
}
|
|
|
|
/* fall through */
|
|
|
|
default:
|
|
|
|
/* We set the flag directly instead of invoking
|
|
|
|
dict_set_corrupted_index_cache_only(index) here,
|
|
|
|
because the index is not "public" yet. */
|
|
|
|
index->type |= DICT_CORRUPT;
|
|
|
|
}
|
|
|
|
|
|
|
|
mem_heap_free(heap);
|
|
|
|
mem_heap_free(offsets_heap);
|
2014-05-05 18:20:28 +02:00
|
|
|
row_log_block_free(index->online_log->head);
|
2013-03-26 00:03:13 +02:00
|
|
|
ut_free(offsets);
|
|
|
|
return(error);
|
|
|
|
}
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
/** Apply the row log to the index upon completing index creation.
|
|
|
|
@param[in] trx transaction (for checking if the operation was
|
|
|
|
interrupted)
|
|
|
|
@param[in,out] index secondary index
|
|
|
|
@param[in,out] table MySQL table (for reporting duplicates)
|
|
|
|
@param[in,out] stage performance schema accounting object, used by
|
|
|
|
ALTER TABLE. stage->begin_phase_log_index() will be called initially and then
|
|
|
|
stage->inc() will be called for each block of log that is applied.
|
2013-03-26 00:03:13 +02:00
|
|
|
@return DB_SUCCESS, or error code on failure */
|
|
|
|
dberr_t
|
|
|
|
row_log_apply(
|
2016-08-12 11:17:45 +03:00
|
|
|
const trx_t* trx,
|
|
|
|
dict_index_t* index,
|
|
|
|
struct TABLE* table,
|
|
|
|
ut_stage_alter_t* stage)
|
2013-03-26 00:03:13 +02:00
|
|
|
{
|
|
|
|
dberr_t error;
|
|
|
|
row_log_t* log;
|
|
|
|
row_merge_dup_t dup = { index, table, NULL, 0 };
|
2014-02-01 09:33:26 +01:00
|
|
|
DBUG_ENTER("row_log_apply");
|
2013-03-26 00:03:13 +02:00
|
|
|
|
|
|
|
ut_ad(dict_index_is_online_ddl(index));
|
|
|
|
ut_ad(!dict_index_is_clust(index));
|
|
|
|
|
2016-08-12 11:17:45 +03:00
|
|
|
stage->begin_phase_log_index();
|
|
|
|
|
2013-03-26 00:03:13 +02:00
|
|
|
log_free_check();
|
|
|
|
|
|
|
|
rw_lock_x_lock(dict_index_get_lock(index));
|
|
|
|
|
|
|
|
if (!dict_table_is_corrupted(index->table)) {
|
2016-08-12 11:17:45 +03:00
|
|
|
error = row_log_apply_ops(trx, index, &dup, stage);
|
2013-03-26 00:03:13 +02:00
|
|
|
} else {
|
|
|
|
error = DB_SUCCESS;
|
|
|
|
}
|
|
|
|
|
2014-02-26 19:23:04 +01:00
|
|
|
if (error != DB_SUCCESS) {
|
2018-05-12 10:11:38 +03:00
|
|
|
ut_ad(index->table->space);
|
2013-03-26 00:03:13 +02:00
|
|
|
/* We set the flag directly instead of invoking
|
|
|
|
dict_set_corrupted_index_cache_only(index) here,
|
|
|
|
because the index is not "public" yet. */
|
|
|
|
index->type |= DICT_CORRUPT;
|
|
|
|
index->table->drop_aborted = TRUE;
|
|
|
|
|
|
|
|
dict_index_set_online_status(index, ONLINE_INDEX_ABORTED);
|
|
|
|
} else {
|
2014-02-26 19:23:04 +01:00
|
|
|
ut_ad(dup.n_dup == 0);
|
2013-03-26 00:03:13 +02:00
|
|
|
dict_index_set_online_status(index, ONLINE_INDEX_COMPLETE);
|
|
|
|
}
|
|
|
|
|
|
|
|
log = index->online_log;
|
|
|
|
index->online_log = NULL;
|
|
|
|
rw_lock_x_unlock(dict_index_get_lock(index));
|
|
|
|
|
|
|
|
row_log_free(log);
|
|
|
|
|
2014-02-01 09:33:26 +01:00
|
|
|
DBUG_RETURN(error);
|
2013-03-26 00:03:13 +02:00
|
|
|
}
|
2021-03-06 05:55:14 +05:30
|
|
|
|
|
|
|
unsigned row_log_get_n_core_fields(const dict_index_t *index)
|
|
|
|
{
|
|
|
|
ut_ad(index->online_log);
|
|
|
|
return index->online_log->n_core_fields;
|
|
|
|
}
|