From 5c0acefaf5381d53020c316d5a9e449f0d809518 Mon Sep 17 00:00:00 2001 From: Meng-Hsiu Chiang Date: Fri, 17 May 2024 22:58:39 +0000 Subject: [PATCH] PS-9222 Include reordered fields when calculating mlog buffer size https://perconadev.atlassian.net/browse/PS-9222 Problem ======= Upstream commit e6e13a8f tries to fix an issue that doesn't log the fields that changes orders when using ALGORITHM=instant, by checking if the fields are also reordered, then adding the columns into the list. However when calculating the size of the buffer this fix doesn't take account the extra fields that may be logged, and causing the assertion on the buffer size failed eventually. Solution ======== To calculate the buffer size correctly, we move the logic of finding reordered fiedls before buffer size calculation, then count the number of fields with the same logic when deciding if a field needs to be logged. --- .../instant_alter_stored_column_order.result | 14 ++++ .../t/instant_alter_stored_column_order.test | 31 ++++++++ storage/innobase/mtr/mtr0log.cc | 72 +++++++++++-------- 3 files changed, 89 insertions(+), 28 deletions(-) create mode 100644 mysql-test/suite/innodb/r/instant_alter_stored_column_order.result create mode 100644 mysql-test/suite/innodb/t/instant_alter_stored_column_order.test diff --git a/mysql-test/suite/innodb/r/instant_alter_stored_column_order.result b/mysql-test/suite/innodb/r/instant_alter_stored_column_order.result new file mode 100644 index 000000000000..6ecafbb6597d --- /dev/null +++ b/mysql-test/suite/innodb/r/instant_alter_stored_column_order.result @@ -0,0 +1,14 @@ +# PS-9222 - Testing if ALGORITHM=instant crashes server +CREATE TABLE t1 (c1 TINYTEXT COLLATE ascii_bin NOT NULL, c2 DATETIME(3) NOT NULL, c3 TEXT, PRIMARY KEY (c1(30))); +INSERT INTO t1 (c1, c2, c3) VALUE ('k1','2021-12-21','something'); +INSERT INTO t1 (c1, c2, c3) VALUE ('k3','2021-12-21','something else'); +ALTER TABLE t1 ADD COLUMN c4 VARCHAR(18) NOT NULL, algorithm=instant; +UPDATE t1 SET c4 = 'value' WHERE c1 = 'k1'; +# Restart the server and reload the table to see if tables are corrupted. +# Kill and restart +# Run a select to confirm that the database started up successfully +SELECT * FROM t1; +c1 c2 c3 c4 +k1 2021-12-21 00:00:00.000 something value +k3 2021-12-21 00:00:00.000 something else +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/t/instant_alter_stored_column_order.test b/mysql-test/suite/innodb/t/instant_alter_stored_column_order.test new file mode 100644 index 000000000000..eecadcd5e0a6 --- /dev/null +++ b/mysql-test/suite/innodb/t/instant_alter_stored_column_order.test @@ -0,0 +1,31 @@ +--disable_query_log +--disable_warnings +DROP DATABASE IF EXISTS instant_alter_stored_column_order; +CREATE DATABASE instant_alter_stored_column_order; +USE instant_alter_stored_column_order; +--enable_warnings +--enable_query_log + +# PS-9222 Testing regression introduced by e6e13a8f, which causes engine crashed + +--echo # PS-9222 - Testing if ALGORITHM=instant crashes server +CREATE TABLE t1 (c1 TINYTEXT COLLATE ascii_bin NOT NULL, c2 DATETIME(3) NOT NULL, c3 TEXT, PRIMARY KEY (c1(30))); +INSERT INTO t1 (c1, c2, c3) VALUE ('k1','2021-12-21','something'); +INSERT INTO t1 (c1, c2, c3) VALUE ('k3','2021-12-21','something else'); + +ALTER TABLE t1 ADD COLUMN c4 VARCHAR(18) NOT NULL, algorithm=instant; +UPDATE t1 SET c4 = 'value' WHERE c1 = 'k1'; + +--echo # Restart the server and reload the table to see if tables are corrupted. +--source include/kill_and_restart_mysqld.inc + +-- echo # Run a select to confirm that the database started up successfully +SELECT * FROM t1; +DROP TABLE t1; + +# cleanup +--disable_query_log +--disable_warnings +DROP DATABASE instant_alter_stored_column_order; +--enable_warnings +--enable_query_log diff --git a/storage/innobase/mtr/mtr0log.cc b/storage/innobase/mtr/mtr0log.cc index 51704f1446d1..00815df97649 100644 --- a/storage/innobase/mtr/mtr0log.cc +++ b/storage/innobase/mtr/mtr0log.cc @@ -515,18 +515,28 @@ constexpr size_t inst_col_info_size = 6; @param[in] is_comp true if COMP @param[in] is_versioned if table has row versions @param[in] is_instant true if table has INSTANT cols +@param[in] changed_order array indicating fields changed position @param[out] size_needed total size needed on REDO LOG */ static void log_index_get_size_needed(const dict_index_t *index, size_t size, uint16_t n, bool is_comp, bool is_versioned, bool is_instant, + const bool *changed_order, size_t &size_needed) { - auto size_for_versioned_fields = [](const dict_index_t *ind) { + auto size_for_versioned_fields = [n, changed_order](const dict_index_t *ind) { size_t _size = 0; /* 2 bytes for number of columns with version */ _size += 2; - size_t n_versioned_fields = ind->table->get_n_instant_add_cols() + - ind->table->get_n_instant_drop_cols(); + size_t n_versioned_fields = 0; + for (size_t i = 0; i < n; i++) { + dict_field_t *field = ind->get_field(i); + const dict_col_t *col = field->col; + if (col->is_instant_added() || col->is_instant_dropped() || + changed_order[i]) { + n_versioned_fields += 1; + } + } + ut_ad(n_versioned_fields != 0); _size += n_versioned_fields * inst_col_info_size; @@ -644,6 +654,7 @@ static bool close_and_reopen_log(byte *&log_ptr, const byte *&log_start, const byte *&log_end, mtr_t *&mtr, size_t &alloc, size_t &total) { mlog_close(mtr, log_ptr); + ut_a(total > (ulint)(log_ptr - log_start)); total -= log_ptr - log_start; alloc = total; @@ -799,9 +810,37 @@ bool mlog_open_and_write_index(mtr_t *mtr, const byte *rec, n = DICT_INDEX_SPATIAL_NODEPTR_SIZE; } + /* Ordinal position of an existing field can't be changed with INSTANT + algorithm. But when it is combined with ADD/DROP COLUMN, ordinal position + of a filed can be changed. This bool array of size #fields in index, + represents if ordinal position of an existing filed is changed. */ + + // Move the logic to find columns that changed order before calculating + // the buffer size + bool *fields_with_changed_order = nullptr; + if (is_versioned) { + fields_with_changed_order = new bool[n]; + memset(fields_with_changed_order, 0, (sizeof(bool) * n)); + + uint16_t phy_pos = 0; + for (size_t i = 0; i < n; i++) { + dict_field_t *field = index->get_field(i); + const dict_col_t *col = field->col; + + if (col->is_instant_added() || col->is_instant_dropped()) { + continue; + } + if (col->get_phy_pos() >= phy_pos) { + phy_pos = col->get_phy_pos(); + } else { + fields_with_changed_order[i] = true; + } + } + } + size_t size_needed = 0; log_index_get_size_needed(index, size, n, is_comp, is_versioned, is_instant, - size_needed); + fields_with_changed_order, size_needed); size_t total = size_needed; size_t alloc = total; if (alloc > mtr_buf_t::MAX_DATA_SIZE) { @@ -810,6 +849,7 @@ bool mlog_open_and_write_index(mtr_t *mtr, const byte *rec, if (!mlog_open(mtr, alloc, log_ptr)) { /* logging is disabled */ + delete[] fields_with_changed_order; return (false); } @@ -846,30 +886,6 @@ bool mlog_open_and_write_index(mtr_t *mtr, const byte *rec, return true; }; - /* Ordinal position of an existing field can't be changed with INSTANT - algorithm. But when it is combined with ADD/DROP COLUMN, ordinal position - of a filed can be changed. This bool array of size #fields in index, - represents if ordinal position of an existing filed is changed. */ - bool *fields_with_changed_order = nullptr; - if (is_versioned) { - fields_with_changed_order = new bool[n]; - memset(fields_with_changed_order, false, (sizeof(bool) * n)); - - uint16_t phy_pos = 0; - for (size_t i = 0; i < n; i++) { - dict_field_t *field = index->get_field(i); - const dict_col_t *col = field->col; - - if (col->is_instant_added() || col->is_instant_dropped()) { - continue; - } else if (col->get_phy_pos() >= phy_pos) { - phy_pos = col->get_phy_pos(); - } else { - fields_with_changed_order[i] = true; - } - } - } - if (is_comp) { /* Write fields info. */ if (!log_index_fields(index, n, is_versioned, instant_fields_to_log,