From cc8eefb0dca1372378905fbae11044f20364c42d Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Tue, 30 Jul 2024 11:59:01 +0530 Subject: [PATCH] MDEV-33087 ALTER TABLE...ALGORITHM=COPY should build indexes more efficiently - During copy algorithm, InnoDB should use bulk insert operation for row by row insert operation. By doing this, copy algorithm can effectively build indexes. This optimization is disabled for temporary table, versioning table and table which has foreign key relation. Introduced the variable innodb_alter_copy_bulk to allow the bulk insert operation for copy alter operation inside InnoDB. This is enabled by default ha_innobase::extra(): HA_EXTRA_END_ALTER_COPY mode tries to apply the buffered bulk insert operation, updates the non-persistent table stats. row_merge_bulk_t::write_to_index(): Update stat_n_rows after applying the bulk insert operation row_ins_clust_index_entry_low(): In case of copy algorithm, switch to bulk insert operation. copy_data_error_ignore(): Handles the error while copying the data from source to target file. --- mysql-test/main/rowid_filter_innodb.result | 4 +- .../suite/innodb/r/alter_copy_bulk,OFF.rdiff | 11 ++ .../suite/innodb/r/alter_copy_bulk.result | 24 ++++ mysql-test/suite/innodb/r/foreign_key.result | 22 ++++ .../innodb/t/alter_copy_bulk.combinations | 5 + .../suite/innodb/t/alter_copy_bulk.test | 44 +++++++ mysql-test/suite/innodb/t/foreign_key.test | 24 ++++ .../suite/sys_vars/r/sysvars_innodb.result | 12 ++ sql/sql_insert.cc | 11 +- sql/sql_table.cc | 122 ++++++++++-------- storage/innobase/handler/ha_innodb.cc | 25 ++++ storage/innobase/handler/handler0alter.cc | 13 +- storage/innobase/include/dict0mem.h | 2 +- storage/innobase/include/row0merge.h | 8 ++ storage/innobase/include/srv0srv.h | 1 + storage/innobase/include/trx0trx.h | 4 +- storage/innobase/row/row0ins.cc | 60 ++++++--- storage/innobase/row/row0merge.cc | 14 +- storage/innobase/srv/srv0srv.cc | 4 + storage/innobase/trx/trx0rec.cc | 2 +- storage/innobase/trx/trx0roll.cc | 2 +- storage/innobase/trx/trx0undo.cc | 2 +- 22 files changed, 325 insertions(+), 91 deletions(-) create mode 100644 mysql-test/suite/innodb/r/alter_copy_bulk,OFF.rdiff create mode 100644 mysql-test/suite/innodb/r/alter_copy_bulk.result create mode 100644 mysql-test/suite/innodb/t/alter_copy_bulk.combinations create mode 100644 mysql-test/suite/innodb/t/alter_copy_bulk.test diff --git a/mysql-test/main/rowid_filter_innodb.result b/mysql-test/main/rowid_filter_innodb.result index 60588e6891d..46d754cfaf8 100644 --- a/mysql-test/main/rowid_filter_innodb.result +++ b/mysql-test/main/rowid_filter_innodb.result @@ -1986,7 +1986,7 @@ ANALYZE "r_table_time_ms": "REPLACED", "r_other_time_ms": "REPLACED", "r_engine_stats": { - "pages_accessed": 3 + "pages_accessed": 2 }, "filtered": "REPLACED", "r_filtered": 66.66666667, @@ -2140,7 +2140,7 @@ ANALYZE "r_table_time_ms": "REPLACED", "r_other_time_ms": "REPLACED", "r_engine_stats": { - "pages_accessed": 3 + "pages_accessed": 2 }, "filtered": "REPLACED", "r_filtered": 66.66666667, diff --git a/mysql-test/suite/innodb/r/alter_copy_bulk,OFF.rdiff b/mysql-test/suite/innodb/r/alter_copy_bulk,OFF.rdiff new file mode 100644 index 00000000000..a644139ea78 --- /dev/null +++ b/mysql-test/suite/innodb/r/alter_copy_bulk,OFF.rdiff @@ -0,0 +1,11 @@ +--- bulk_copy_alter.result ++++ bulk_copy_alter,non_bulk_alter_copy.result +@@ -5,7 +5,7 @@ + INSERT INTO t1 SELECT repeat('b', 200), seq FROM seq_3_to_65536; + ALTER TABLE t1 ADD INDEX(f2); + ALTER TABLE t1 ADD PRIMARY KEY(f1(2)); +-ERROR 23000: Duplicate entry 'bb' for key 'PRIMARY' ++ERROR 23000: Duplicate entry 'aa' for key 'PRIMARY' + INSERT INTO t1 VALUES(repeat('a', 200), 1); + ALTER TABLE t1 ADD UNIQUE KEY(f2); + ERROR 23000: Duplicate entry '1' for key 'f2_2' diff --git a/mysql-test/suite/innodb/r/alter_copy_bulk.result b/mysql-test/suite/innodb/r/alter_copy_bulk.result new file mode 100644 index 00000000000..fa232c12dc3 --- /dev/null +++ b/mysql-test/suite/innodb/r/alter_copy_bulk.result @@ -0,0 +1,24 @@ +SET @@alter_algorithm=COPY; +CREATE TABLE t1(f1 CHAR(200), f2 INT NOT NULL)engine=InnoDB; +INSERT INTO t1 SELECT repeat('a', 200), seq FROM seq_1_to_2; +ALTER TABLE t1 FORCE; +INSERT INTO t1 SELECT repeat('b', 200), seq FROM seq_3_to_65536; +ALTER TABLE t1 ADD INDEX(f2); +ALTER TABLE t1 ADD PRIMARY KEY(f1(2)); +ERROR 23000: Duplicate entry 'bb' for key 'PRIMARY' +INSERT INTO t1 VALUES(repeat('a', 200), 1); +ALTER TABLE t1 ADD UNIQUE KEY(f2); +ERROR 23000: Duplicate entry '1' for key 'f2_2' +ALTER IGNORE TABLE t1 MODIFY f1 CHAR(200) NOT NULL; +CREATE TABLE t2(f1 INT NOT NULL, +FOREIGN KEY(f1) REFERENCES t1(f2))ENGINE=InnoDB; +INSERT INTO t2 VALUES(1); +ALTER TABLE t2 FORCE; +DROP TABLE t2, t1; +CREATE TABLE t1 (f1 INT, f2 INT) ENGINE=InnoDB PARTITION BY HASH(f1) PARTITIONS 2; +INSERT INTO t1 VALUES(1, 1); +INSERT INTO t1 SELECT seq, seq * 2 FROM seq_1_to_2; +ALTER TABLE t1 FORCE; +INSERT INTO t1 SELECT seq, seq * 2 FROM seq_3_to_65536; +ALTER TABLE t1 ADD INDEX(f2); +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/r/foreign_key.result b/mysql-test/suite/innodb/r/foreign_key.result index 80a0afb8d06..9f14194b6dd 100644 --- a/mysql-test/suite/innodb/r/foreign_key.result +++ b/mysql-test/suite/innodb/r/foreign_key.result @@ -1119,4 +1119,26 @@ test.collections check status OK disconnect con1; DROP TABLE binaries, collections; # End of 10.6 tests +CREATE TABLE t1 +( +f1 VARCHAR(32)BINARY NOT NULL, +f2 VARCHAR(32)BINARY NOT NULL, +PRIMARY KEY (f1), +INDEX(f2) +) ENGINE=InnoDB; +INSERT INTO t1 VALUES('MySQL', 'InnoDB'), ('MariaDB', 'NDB'); +CREATE TABLE t2 +( +f1 VARCHAR(32)BINARY NOT NULL, +f2 VARCHAR(255)BINARY NOT NULL, +f3 int, PRIMARY KEY (f1), INDEX(f1), INDEX(f2) +) ENGINE=InnoDB; +INSERT INTO t2 VALUES('MySQL', 'MySQL', 1), +('MariaDB', 'MariaDB', 1); +ALTER TABLE t1 ADD FOREIGN KEY (f1) REFERENCES t2 (f2); +ALTER TABLE t2 ADD FOREIGN KEY (f2) REFERENCES t2 (f2), +ADD UNIQUE INDEX(f3); +ERROR HY000: Cannot delete rows from table which is parent in a foreign key constraint 't1_ibfk_1' of table 't1' +drop table t1, t2; SET GLOBAL innodb_stats_persistent = @save_stats_persistent; +# End of 10.11 tests diff --git a/mysql-test/suite/innodb/t/alter_copy_bulk.combinations b/mysql-test/suite/innodb/t/alter_copy_bulk.combinations new file mode 100644 index 00000000000..602a6b5ac57 --- /dev/null +++ b/mysql-test/suite/innodb/t/alter_copy_bulk.combinations @@ -0,0 +1,5 @@ +[ON] +--innodb_alter_copy_bulk=ON + +[OFF] +--innodb_alter_copy_bulk=OFF diff --git a/mysql-test/suite/innodb/t/alter_copy_bulk.test b/mysql-test/suite/innodb/t/alter_copy_bulk.test new file mode 100644 index 00000000000..797724dcf2e --- /dev/null +++ b/mysql-test/suite/innodb/t/alter_copy_bulk.test @@ -0,0 +1,44 @@ +--source include/have_innodb.inc +--source include/have_partition.inc +--source include/have_sequence.inc +SET @@alter_algorithm=COPY; + +CREATE TABLE t1(f1 CHAR(200), f2 INT NOT NULL)engine=InnoDB; +INSERT INTO t1 SELECT repeat('a', 200), seq FROM seq_1_to_2; +# Buffer fits in the memory +ALTER TABLE t1 FORCE; + +# Insert more entries +INSERT INTO t1 SELECT repeat('b', 200), seq FROM seq_3_to_65536; +# Alter should use temporary file for sorting +ALTER TABLE t1 ADD INDEX(f2); + +# Error while buffering the insert operation +--error ER_DUP_ENTRY +ALTER TABLE t1 ADD PRIMARY KEY(f1(2)); + +INSERT INTO t1 VALUES(repeat('a', 200), 1); +# Error while applying the bulk insert operation +--error ER_DUP_ENTRY +ALTER TABLE t1 ADD UNIQUE KEY(f2); + +# Ignore shouldn't go through bulk operation +ALTER IGNORE TABLE t1 MODIFY f1 CHAR(200) NOT NULL; + +CREATE TABLE t2(f1 INT NOT NULL, + FOREIGN KEY(f1) REFERENCES t1(f2))ENGINE=InnoDB; +INSERT INTO t2 VALUES(1); +# Bulk operation shouldn't happen because of foreign key constraints +ALTER TABLE t2 FORCE; +DROP TABLE t2, t1; + +CREATE TABLE t1 (f1 INT, f2 INT) ENGINE=InnoDB PARTITION BY HASH(f1) PARTITIONS 2; +INSERT INTO t1 VALUES(1, 1); +INSERT INTO t1 SELECT seq, seq * 2 FROM seq_1_to_2; +# Buffer fits in the memory +ALTER TABLE t1 FORCE; +# Insert more entries +INSERT INTO t1 SELECT seq, seq * 2 FROM seq_3_to_65536; +# Alter should use temporary file for sorting +ALTER TABLE t1 ADD INDEX(f2); +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/t/foreign_key.test b/mysql-test/suite/innodb/t/foreign_key.test index e793e261abd..8c6ce939bab 100644 --- a/mysql-test/suite/innodb/t/foreign_key.test +++ b/mysql-test/suite/innodb/t/foreign_key.test @@ -1186,6 +1186,30 @@ DROP TABLE binaries, collections; --echo # End of 10.6 tests +CREATE TABLE t1 +( +f1 VARCHAR(32)BINARY NOT NULL, +f2 VARCHAR(32)BINARY NOT NULL, +PRIMARY KEY (f1), +INDEX(f2) +) ENGINE=InnoDB; +INSERT INTO t1 VALUES('MySQL', 'InnoDB'), ('MariaDB', 'NDB'); + +CREATE TABLE t2 +( +f1 VARCHAR(32)BINARY NOT NULL, +f2 VARCHAR(255)BINARY NOT NULL, +f3 int, PRIMARY KEY (f1), INDEX(f1), INDEX(f2) +) ENGINE=InnoDB; +INSERT INTO t2 VALUES('MySQL', 'MySQL', 1), + ('MariaDB', 'MariaDB', 1); +ALTER TABLE t1 ADD FOREIGN KEY (f1) REFERENCES t2 (f2); +# MDEV-33927 TODO: change the warning message +--error ER_FK_CANNOT_DELETE_PARENT +ALTER TABLE t2 ADD FOREIGN KEY (f2) REFERENCES t2 (f2), +ADD UNIQUE INDEX(f3); +drop table t1, t2; SET GLOBAL innodb_stats_persistent = @save_stats_persistent; +--echo # End of 10.11 tests --source include/wait_until_count_sessions.inc diff --git a/mysql-test/suite/sys_vars/r/sysvars_innodb.result b/mysql-test/suite/sys_vars/r/sysvars_innodb.result index f72e0d6f05d..f86a67bbb7b 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_innodb.result +++ b/mysql-test/suite/sys_vars/r/sysvars_innodb.result @@ -55,6 +55,18 @@ NUMERIC_BLOCK_SIZE 0 ENUM_VALUE_LIST NULL READ_ONLY YES COMMAND_LINE_ARGUMENT OPTIONAL +VARIABLE_NAME INNODB_ALTER_COPY_BULK +SESSION_VALUE NULL +DEFAULT_VALUE ON +VARIABLE_SCOPE GLOBAL +VARIABLE_TYPE BOOLEAN +VARIABLE_COMMENT Allow bulk insert operation for copy alter operation +NUMERIC_MIN_VALUE NULL +NUMERIC_MAX_VALUE NULL +NUMERIC_BLOCK_SIZE NULL +ENUM_VALUE_LIST OFF,ON +READ_ONLY NO +COMMAND_LINE_ARGUMENT NONE VARIABLE_NAME INNODB_AUTOEXTEND_INCREMENT SESSION_VALUE NULL DEFAULT_VALUE 64 diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 4353d45e0fc..5b2fe9e24ea 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -4356,7 +4356,16 @@ bool select_insert::prepare_eof() if (info.ignore || info.handle_duplicates != DUP_ERROR) if (table->file->ha_table_flags() & HA_DUPLICATE_POS) table->file->ha_rnd_end(); - table->file->extra(HA_EXTRA_END_ALTER_COPY); + if (error <= 0) + { + error= table->file->extra(HA_EXTRA_END_ALTER_COPY); + if (error == HA_ERR_FOUND_DUPP_KEY) + { + uint key_nr= table->file->get_dup_key(error); + if ((int)key_nr >= 0 && key_nr < table->s->keys) + print_keydup_error(table, &table->key_info[key_nr], MYF(0)); + } + } table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE); diff --git a/sql/sql_table.cc b/sql/sql_table.cc index c149112e23a..3b69a0190b6 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -11766,6 +11766,59 @@ bool mysql_trans_commit_alter_copy_data(THD *thd) DBUG_RETURN(error); } +/** Handle the error when copying data from source to target table. +@param error error code +@param ignore alter ignore statement +@param to target table handler +@param thd Mysql Thread +@param alter_ctx Runtime context for alter statement +@retval false in case of error +@retval true in case of skipping the row and continue alter operation */ +static bool +copy_data_error_ignore(int &error, bool ignore, TABLE *to, + THD *thd, Alter_table_ctx *alter_ctx) +{ + if (to->file->is_fatal_error(error, HA_CHECK_DUP)) + { + /* Not a duplicate key error. */ + to->file->print_error(error, MYF(0)); + error= 1; + return false; + } + /* Duplicate key error. */ + if (unlikely(alter_ctx->fk_error_if_delete_row)) + { + /* We are trying to omit a row from the table which serves + as parent in a foreign key. This might have broken + referential integrity so emit an error. Note that we + can't ignore this error even if we are + executing ALTER IGNORE TABLE. IGNORE allows to skip rows, but + doesn't allow to break unique or foreign key constraints, */ + my_error(ER_FK_CANNOT_DELETE_PARENT, MYF(0), + alter_ctx->fk_error_id, + alter_ctx->fk_error_table); + return false; + } + if (ignore) + return true; + /* Ordinary ALTER TABLE. Report duplicate key error. */ + uint key_nr= to->file->get_dup_key(error); + if (key_nr <= MAX_KEY) + { + const char *err_msg= ER_THD(thd, ER_DUP_ENTRY_WITH_KEY_NAME); + if (key_nr == 0 && to->s->keys > 0 && + (to->key_info[0].key_part[0].field->flags & + AUTO_INCREMENT_FLAG)) + err_msg= ER_THD(thd, ER_DUP_ENTRY_AUTOINCREMENT_CASE); + print_keydup_error(to, + key_nr >= to->s->keys ? NULL : + &to->key_info[key_nr], + err_msg, MYF(0)); + } + else + to->file->print_error(error, MYF(0)); + return false; +} static int copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, bool ignore, @@ -12027,58 +12080,11 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, bool ignore, to->auto_increment_field_not_null= FALSE; if (unlikely(error)) { - if (to->file->is_fatal_error(error, HA_CHECK_DUP)) - { - /* Not a duplicate key error. */ - to->file->print_error(error, MYF(0)); - error= 1; - break; - } - else - { - /* Duplicate key error. */ - if (unlikely(alter_ctx->fk_error_if_delete_row)) - { - /* - We are trying to omit a row from the table which serves as parent - in a foreign key. This might have broken referential integrity so - emit an error. Note that we can't ignore this error even if we are - executing ALTER IGNORE TABLE. IGNORE allows to skip rows, but - doesn't allow to break unique or foreign key constraints, - */ - my_error(ER_FK_CANNOT_DELETE_PARENT, MYF(0), - alter_ctx->fk_error_id, - alter_ctx->fk_error_table); - break; - } - - if (ignore) - { - /* This ALTER IGNORE TABLE. Simply skip row and continue. */ - to->file->restore_auto_increment(prev_insert_id); - delete_count++; - } - else - { - /* Ordinary ALTER TABLE. Report duplicate key error. */ - uint key_nr= to->file->get_dup_key(error); - if ((int) key_nr >= 0) - { - const char *err_msg= ER_THD(thd, ER_DUP_ENTRY_WITH_KEY_NAME); - if (key_nr == 0 && to->s->keys > 0 && - (to->key_info[0].key_part[0].field->flags & - AUTO_INCREMENT_FLAG)) - err_msg= ER_THD(thd, ER_DUP_ENTRY_AUTOINCREMENT_CASE); - print_keydup_error(to, - key_nr >= to->s->keys ? NULL : - &to->key_info[key_nr], - err_msg, MYF(0)); - } - else - to->file->print_error(error, MYF(0)); - break; - } - } + if (!copy_data_error_ignore(error, ignore, to, thd, alter_ctx)) + break; + DBUG_ASSERT(ignore); + to->file->restore_auto_increment(prev_insert_id); + delete_count++; } else { @@ -12105,9 +12111,15 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, bool ignore, error= 1; } bulk_insert_started= 0; - if (!ignore) - to->file->extra(HA_EXTRA_END_ALTER_COPY); - + if (!ignore && error <= 0) + { + int alt_error= to->file->extra(HA_EXTRA_END_ALTER_COPY); + if (alt_error > 0) + { + error= alt_error; + copy_data_error_ignore(error, false, to, thd, alter_ctx); + } + } cleanup_done= 1; to->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 26e48eb3e5b..c694619d852 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -15779,6 +15779,26 @@ ha_innobase::extra( break; case HA_EXTRA_END_ALTER_COPY: trx = check_trx_exists(ha_thd()); + if (m_prebuilt->table->skip_alter_undo) { + if (dberr_t err= trx->bulk_insert_apply()) { + m_prebuilt->table->skip_alter_undo = 0; + return convert_error_code_to_mysql( + err, + m_prebuilt->table->flags, + trx->mysql_thd); + } + + trx->end_bulk_insert(*m_prebuilt->table); + trx->bulk_insert = false; + /* During copy alter operation, InnoDB + updates the stats only for non-persistent + tables. */ + if (!dict_stats_is_persistent_enabled( + m_prebuilt->table)) { + dict_stats_update_if_needed( + m_prebuilt->table, *trx); + } + } m_prebuilt->table->skip_alter_undo = 0; if (!m_prebuilt->table->is_temporary() && !high_level_read_only) { @@ -19731,6 +19751,10 @@ static MYSQL_SYSVAR_BOOL(force_primary_key, "Do not allow creating a table without primary key (off by default)", NULL, NULL, FALSE); +static MYSQL_SYSVAR_BOOL(alter_copy_bulk, innodb_alter_copy_bulk, + PLUGIN_VAR_NOCMDARG, + "Allow bulk insert operation for copy alter operation", NULL, NULL, TRUE); + const char *page_compression_algorithms[]= { "none", "zlib", "lz4", "lzo", "lzma", "bzip2", "snappy", 0 }; static TYPELIB page_compression_algorithms_typelib= { @@ -19966,6 +19990,7 @@ static struct st_mysql_sys_var* innobase_system_variables[]= { MYSQL_SYSVAR(saved_page_number_debug), #endif /* UNIV_DEBUG */ MYSQL_SYSVAR(force_primary_key), + MYSQL_SYSVAR(alter_copy_bulk), MYSQL_SYSVAR(fatal_semaphore_wait_threshold), /* Table page compression feature */ MYSQL_SYSVAR(compression_default), diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc index 71d3177323f..3aa4b51ab5f 100644 --- a/storage/innobase/handler/handler0alter.cc +++ b/storage/innobase/handler/handler0alter.cc @@ -845,14 +845,13 @@ inline void dict_table_t::rollback_instant( } } -/* Report an InnoDB error to the client by invoking my_error(). */ -static ATTRIBUTE_COLD __attribute__((nonnull)) +/* Report an InnoDB error to the client by invoking my_error(). +@param error InnoDB error code +@param table table name +@param flags table flags */ +ATTRIBUTE_COLD __attribute__((nonnull)) void -my_error_innodb( -/*============*/ - dberr_t error, /*!< in: InnoDB error code */ - const char* table, /*!< in: table name */ - ulint flags) /*!< in: table flags */ +my_error_innodb(dberr_t error, const char *table, ulint flags) { switch (error) { case DB_MISSING_HISTORY: diff --git a/storage/innobase/include/dict0mem.h b/storage/innobase/include/dict0mem.h index e9ae48ef8da..3b8ecdd2c87 100644 --- a/storage/innobase/include/dict0mem.h +++ b/storage/innobase/include/dict0mem.h @@ -2435,7 +2435,7 @@ public: } /** @return whether a DDL operation is in progress on this table */ - bool is_active_ddl() const + bool is_native_online_ddl() const { return UT_LIST_GET_FIRST(indexes)->online_log; } diff --git a/storage/innobase/include/row0merge.h b/storage/innobase/include/row0merge.h index 1c2af128229..fc39a9d24d9 100644 --- a/storage/innobase/include/row0merge.h +++ b/storage/innobase/include/row0merge.h @@ -416,6 +416,14 @@ row_merge_read_rec( ulint space) /*!< in: space id */ MY_ATTRIBUTE((warn_unused_result)); +/* Report an InnoDB error to the client by invoking my_error(). +@param error InnoDB error code +@param table table name +@param flags table flags */ +ATTRIBUTE_COLD __attribute__((nonnull)) +void +my_error_innodb(dberr_t error, const char *table, ulint flags); + /** Buffer for bulk insert */ class row_merge_bulk_t { diff --git a/storage/innobase/include/srv0srv.h b/storage/innobase/include/srv0srv.h index 6e75359c9f1..0fe42e277a3 100644 --- a/storage/innobase/include/srv0srv.h +++ b/storage/innobase/include/srv0srv.h @@ -310,6 +310,7 @@ extern ulong srv_checksum_algorithm; extern my_bool srv_force_primary_key; +extern my_bool innodb_alter_copy_bulk; extern ulong srv_max_purge_lag; extern ulong srv_max_purge_lag_delay; diff --git a/storage/innobase/include/trx0trx.h b/storage/innobase/include/trx0trx.h index 152553544df..3d2017e4131 100644 --- a/storage/innobase/include/trx0trx.h +++ b/storage/innobase/include/trx0trx.h @@ -1172,8 +1172,8 @@ public: { if (UNIV_LIKELY(!bulk_insert)) return nullptr; - ut_ad(!check_unique_secondary); - ut_ad(!check_foreigns); + ut_ad(table->skip_alter_undo || !check_unique_secondary); + ut_ad(table->skip_alter_undo || !check_foreigns); auto it= mod_tables.find(table); if (it == mod_tables.end() || !it->second.bulk_buffer_exist()) return nullptr; diff --git a/storage/innobase/row/row0ins.cc b/storage/innobase/row/row0ins.cc index f39dad8aee7..8704dbfe65c 100644 --- a/storage/innobase/row/row0ins.cc +++ b/storage/innobase/row/row0ins.cc @@ -2701,7 +2701,7 @@ err_exit: block = btr_pcur_get_block(&pcur); - DBUG_EXECUTE_IF("row_ins_row_level", goto skip_bulk_insert;); + DBUG_EXECUTE_IF("row_ins_row_level", goto row_level_insert;); if (!(flags & BTR_NO_UNDO_LOG_FLAG) && page_is_empty(block->page.frame) @@ -2709,37 +2709,33 @@ err_exit: && !trx->check_unique_secondary && !trx->check_foreigns && !trx->dict_operation && block->page.id().page_no() == index->page - && !index->table->skip_alter_undo - && !index->table->n_rec_locks - && !index->table->is_active_ddl() - && !index->table->has_spatial_index() - && !index->table->versioned() - && (!dict_table_is_partition(index->table) + && !index->table->is_native_online_ddl() + && (!dict_table_is_partition(index->table) || thd_sql_command(trx->mysql_thd) == SQLCOM_INSERT)) { - DEBUG_SYNC_C("empty_root_page_insert"); - trx->bulk_insert = true; + if (!index->table->n_rec_locks + && !index->table->versioned() + && !index->table->is_temporary() + && !index->table->has_spatial_index()) { - if (!index->table->is_temporary()) { + ut_ad(!index->table->skip_alter_undo); + trx->bulk_insert = true; err = lock_table(index->table, NULL, LOCK_X, thr); - if (err != DB_SUCCESS) { trx->error_state = err; trx->bulk_insert = false; goto err_exit; } - if (index->table->n_rec_locks) { avoid_bulk: trx->bulk_insert = false; - goto skip_bulk_insert; + goto row_level_insert; } - #ifdef WITH_WSREP if (trx->is_wsrep()) { if (!wsrep_thd_is_local_transaction(trx->mysql_thd)) - goto skip_bulk_insert; + goto row_level_insert; if (wsrep_append_table_key(trx->mysql_thd, *index->table)) { trx->error_state = DB_ROLLBACK; @@ -2774,9 +2770,32 @@ avoid_bulk: export_vars.innodb_bulk_operations++; goto err_exit; } + } else if (flags == (BTR_NO_UNDO_LOG_FLAG | BTR_NO_LOCKING_FLAG) + && !index->table->n_rec_locks) { + + ut_ad(index->table->skip_alter_undo); + ut_ad(!entry->is_metadata()); + if (innodb_alter_copy_bulk + && !index->table->is_temporary() + && !index->table->versioned() + && !index->table->has_spatial_index()) { + ut_ad(page_is_empty(block->page.frame)); + /* This code path has been executed at the + start of the alter operation. Consecutive + insert operation are buffered in the + bulk buffer and doesn't check for constraint + validity of foreign key relationship. */ + trx_start_if_not_started(trx, true); + trx->bulk_insert = true; + auto m = trx->mod_tables.emplace(index->table, 0); + m.first->second.start_bulk_insert(index->table); + err = m.first->second.bulk_insert_buffered( + *entry, *index, trx); + goto err_exit; + } } -skip_bulk_insert: +row_level_insert: if (UNIV_UNLIKELY(entry->info_bits != 0)) { const rec_t* rec = btr_pcur_get_rec(&pcur); @@ -3386,9 +3405,12 @@ row_ins_index_entry( if (index->is_btree()) { if (auto t= trx->check_bulk_buffer(index->table)) { - /* MDEV-25036 FIXME: check also foreign key - constraints */ - ut_ad(!trx->check_foreigns); + /* MDEV-25036 FIXME: + row_ins_check_foreign_constraint() check + should be done before buffering the insert + operation. */ + ut_ad(index->table->skip_alter_undo + || !trx->check_foreigns); return t->bulk_insert_buffered(*entry, *index, trx); } } diff --git a/storage/innobase/row/row0merge.cc b/storage/innobase/row/row0merge.cc index 6fb530f0309..4ce2e79887a 100644 --- a/storage/innobase/row/row0merge.cc +++ b/storage/innobase/row/row0merge.cc @@ -5320,6 +5320,9 @@ func_exit: else if (index->is_primary() && table->persistent_autoinc) btr_write_autoinc(index, table->autoinc - 1); err= btr_bulk.finish(err); + if (err == DB_SUCCESS && index->is_clust()) + table->stat_n_rows= (file && file->fd != OS_FILE_CLOSED) + ? file->n_rec : buf.n_tuples; return err; } @@ -5333,8 +5336,17 @@ dberr_t row_merge_bulk_t::write_to_table(dict_table_t *table, trx_t *trx) continue; dberr_t err= write_to_index(i, trx); - if (err != DB_SUCCESS) + switch (err) { + default: + if (table->skip_alter_undo) + my_error_innodb(err, table->name.m_name, table->flags); return err; + case DB_SUCCESS: + break; + case DB_DUPLICATE_KEY: + trx->error_info= index; + return err; + } i++; } diff --git a/storage/innobase/srv/srv0srv.cc b/storage/innobase/srv/srv0srv.cc index 7c0c4b92c47..2bbc3d1daf8 100644 --- a/storage/innobase/srv/srv0srv.cc +++ b/storage/innobase/srv/srv0srv.cc @@ -353,6 +353,10 @@ my_bool srv_print_innodb_lock_monitor; PRIMARY KEY */ my_bool srv_force_primary_key; +/** innodb_alter_copy_bulk; Whether to allow bulk insert operation +inside InnoDB alter for copy algorithm; */ +my_bool innodb_alter_copy_bulk; + /** Key version to encrypt the temporary tablespace */ my_bool innodb_encrypt_temporary_tables; diff --git a/storage/innobase/trx/trx0rec.cc b/storage/innobase/trx/trx0rec.cc index 2923dc6477d..cf6c050e623 100644 --- a/storage/innobase/trx/trx0rec.cc +++ b/storage/innobase/trx/trx0rec.cc @@ -1840,7 +1840,7 @@ trx_undo_report_row_operation( auto m = trx->mod_tables.emplace(index->table, trx->undo_no); ut_ad(m.first->second.valid(trx->undo_no)); - if (m.second && index->table->is_active_ddl()) { + if (m.second && index->table->is_native_online_ddl()) { trx->apply_online_log= true; } diff --git a/storage/innobase/trx/trx0roll.cc b/storage/innobase/trx/trx0roll.cc index 126d8aadacb..fd88caa84d0 100644 --- a/storage/innobase/trx/trx0roll.cc +++ b/storage/innobase/trx/trx0roll.cc @@ -151,7 +151,7 @@ inline void trx_t::rollback_low(trx_savept_t *savept) mod_tables.erase(j); } else if (!apply_online_log) - apply_online_log= j->first->is_active_ddl(); + apply_online_log= j->first->is_native_online_ddl(); } MONITOR_INC(MONITOR_TRX_ROLLBACK_SAVEPOINT); } diff --git a/storage/innobase/trx/trx0undo.cc b/storage/innobase/trx/trx0undo.cc index c0f5b1fb22c..af876f1e45b 100644 --- a/storage/innobase/trx/trx0undo.cc +++ b/storage/innobase/trx/trx0undo.cc @@ -309,7 +309,7 @@ inline void UndorecApplier::apply_undo_rec(const trx_undo_rec_t *rec) dict_sys.unfreeze(); ut_ad(table); - if (!table->is_active_ddl()) + if (!table->is_native_online_ddl()) return; dict_index_t *index= dict_table_get_first_index(table);