From ee10295605de4f04ccadaa196d881903acde8003 Mon Sep 17 00:00:00 2001 From: Catalin Besleaga Date: Wed, 22 Apr 2026 10:07:50 +0300 Subject: [PATCH] PS-9616 [9.x] Inconsistent DDL behaviour for ALTER TABLE with INPLACE Changing the COLLATE attribute for a column is done without rebuilding the table when split in two steps: ALTER the column and then alter the table's default COLLATE attribute. When done in a single step, mysql decides to switch to rebuilding the table which is unnnecessary when the column is not indexed. The problem was with the if statement that was checking how to proceeed with the operation and did not take into consideration that both ALTER_COLUMN_EQUAL_PACK_LENGTH and CHANGE_CREATE_OPTION may be present in one single statement at once. The fix was to take into consideration all the altering flags (ALTER_COLUMN_EQUAL_PACK_LENGTH | CHANGE_CREATE_OPTION) when deciding which algorithm to use and it takes into consideration that the ALTER_STORED_COLUMN_TYPE flag is set instead of ALTER_COLUMN_EQUAL_PACK_LENGTH when the column is indexed. --- .../suite/percona_innodb/r/ps_9616.result | 109 ++++++++++++++++ .../suite/percona_innodb/t/ps_9616.test | 118 ++++++++++++++++++ storage/innobase/handler/handler0alter.cc | 17 +-- 3 files changed, 237 insertions(+), 7 deletions(-) create mode 100644 mysql-test/suite/percona_innodb/r/ps_9616.result create mode 100644 mysql-test/suite/percona_innodb/t/ps_9616.test diff --git a/mysql-test/suite/percona_innodb/r/ps_9616.result b/mysql-test/suite/percona_innodb/r/ps_9616.result new file mode 100644 index 000000000000..c8f478ec10e8 --- /dev/null +++ b/mysql-test/suite/percona_innodb/r/ps_9616.result @@ -0,0 +1,109 @@ +# utf8mb4_general_ci ===>>> utf8mb4_0900_ai_ci ### in 2 steps +CREATE TABLE `test_table` +( +`field1` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, +`field2` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +ALTER TABLE test_table +MODIFY field1 varchar (10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, +MODIFY field2 text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci, +ALGORITHM=INPLACE; +ALTER TABLE test_table COLLATE=utf8mb4_0900_ai_ci, ALGORITHM=INPLACE; +SHOW CREATE TABLE test_table; +Table Create Table +test_table CREATE TABLE `test_table` ( + `field1` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, + `field2` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +DROP TABLE test_table; +# utf8mb4_general_ci ===>>> utf8mb4_0900_ai_ci ### in 1 step +CREATE TABLE `test_table` +( +`field1` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, +`field2` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +ALTER TABLE test_table +MODIFY field1 varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, +MODIFY field2 text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci, +COLLATE=utf8mb4_0900_ai_ci, +ALGORITHM=INPLACE; +SHOW CREATE TABLE test_table; +Table Create Table +test_table CREATE TABLE `test_table` ( + `field1` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, + `field2` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +DROP TABLE test_table; +# with indexed columns +CREATE TABLE `test_table` +( +`field1` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, +`field2` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, +INDEX(`field1`), +INDEX(`field2`(10)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +ALTER TABLE test_table +MODIFY field1 varchar (10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, +MODIFY field2 text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci, +ALGORITHM=INPLACE; +ERROR 0A000: ALGORITHM=INPLACE is not supported. Reason: Cannot change column type INPLACE. Try ALGORITHM=COPY. +ALTER TABLE test_table COLLATE=utf8mb4_0900_ai_ci, ALGORITHM=INPLACE; +SHOW CREATE TABLE test_table; +Table Create Table +test_table CREATE TABLE `test_table` ( + `field1` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `field2` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, + KEY `field1` (`field1`), + KEY `field2` (`field2`(10)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +DROP TABLE test_table; +# utf8mb4_general_ci ===>>> utf8mb4_0900_ai_ci ### in 1 step +CREATE TABLE `test_table` +( +`field1` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, +`field2` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, +INDEX(`field1`), +INDEX(`field2`(10)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +ALTER TABLE test_table +MODIFY field1 varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, +MODIFY field2 text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci, +COLLATE=utf8mb4_0900_ai_ci, +ALGORITHM=INPLACE; +ERROR 0A000: ALGORITHM=INPLACE is not supported. Reason: Cannot change column type INPLACE. Try ALGORITHM=COPY. +SHOW CREATE TABLE test_table; +Table Create Table +test_table CREATE TABLE `test_table` ( + `field1` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `field2` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, + KEY `field1` (`field1`), + KEY `field2` (`field2`(10)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci +DROP TABLE test_table; +# ADD INDEX + AUTO_INCREMENT= in 1 step +CREATE TABLE test_table (a INT, b INT) ENGINE=InnoDB; +ALTER TABLE test_table ADD INDEX idx_b(b), AUTO_INCREMENT=7, ALGORITHM=INPLACE; +SHOW CREATE TABLE test_table; +Table Create Table +test_table CREATE TABLE `test_table` ( + `a` int DEFAULT NULL, + `b` int DEFAULT NULL, + KEY `idx_b` (`b`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +DROP TABLE test_table; +# DROP INDEX + COLLATE= in 1 step +CREATE TABLE `test_table` +( +`field1` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, +INDEX(`field1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +ALTER TABLE test_table +DROP INDEX field1, +COLLATE=utf8mb4_0900_ai_ci, +ALGORITHM=INPLACE; +SHOW CREATE TABLE test_table; +Table Create Table +test_table CREATE TABLE `test_table` ( + `field1` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +DROP TABLE test_table; diff --git a/mysql-test/suite/percona_innodb/t/ps_9616.test b/mysql-test/suite/percona_innodb/t/ps_9616.test new file mode 100644 index 000000000000..83b37e88b880 --- /dev/null +++ b/mysql-test/suite/percona_innodb/t/ps_9616.test @@ -0,0 +1,118 @@ +--echo # utf8mb4_general_ci ===>>> utf8mb4_0900_ai_ci ### in 2 steps +CREATE TABLE `test_table` +( + `field1` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `field2` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + + +# ha_alter_info->handler_flags = ALTER_COLUMN_EQUAL_PACK_LENGTH & ALTER_COLUMN_DEFAULT +ALTER TABLE test_table + MODIFY field1 varchar (10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, + MODIFY field2 text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci, + ALGORITHM=INPLACE; + +# ha_alter_info->handler_flags = CHANGE_CREATE_OPTION +ALTER TABLE test_table COLLATE=utf8mb4_0900_ai_ci, ALGORITHM=INPLACE; + +SHOW CREATE TABLE test_table; + +DROP TABLE test_table; + + +--echo # utf8mb4_general_ci ===>>> utf8mb4_0900_ai_ci ### in 1 step +# before the patch, this innodb created a new table +# check with SET GLOBAL innodb_print_ddl_logs = ON; SET GLOBAL log_error_verbosity = 3; + +CREATE TABLE `test_table` +( + `field1` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `field2` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +# ha_alter_info->handler_flags = ALTER_COLUMN_EQUAL_PACK_LENGTH & ALTER_COLUMN_DEFAULT & CHANGE_CREATE_OPTION +ALTER TABLE test_table + MODIFY field1 varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, + MODIFY field2 text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci, + COLLATE=utf8mb4_0900_ai_ci, + ALGORITHM=INPLACE; + +SHOW CREATE TABLE test_table; + +DROP TABLE test_table; + +--echo # with indexed columns +CREATE TABLE `test_table` +( + `field1` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `field2` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, + INDEX(`field1`), + INDEX(`field2`(10)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + + +--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON +ALTER TABLE test_table + MODIFY field1 varchar (10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, + MODIFY field2 text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci, + ALGORITHM=INPLACE; + +ALTER TABLE test_table COLLATE=utf8mb4_0900_ai_ci, ALGORITHM=INPLACE; + +SHOW CREATE TABLE test_table; + +DROP TABLE test_table; + + +--echo # utf8mb4_general_ci ===>>> utf8mb4_0900_ai_ci ### in 1 step +# before the patch, innodb created a new table +# check with SET GLOBAL innodb_print_ddl_logs = ON; SET GLOBAL log_error_verbosity = 3; + +CREATE TABLE `test_table` +( + `field1` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `field2` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, + INDEX(`field1`), + INDEX(`field2`(10)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON +ALTER TABLE test_table + MODIFY field1 varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, + MODIFY field2 text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci, + COLLATE=utf8mb4_0900_ai_ci, + ALGORITHM=INPLACE; + +SHOW CREATE TABLE test_table; + +DROP TABLE test_table; + +# Verify that combining ADD INDEX with AUTO_INCREMENT= does not crash the +# server (regression: INNOBASE_INPLACE_NO_REBUILD incorrectly masked ADD_INDEX, +# causing prepare/alter to early-exit while commit tried to find the unbuilt index). +--echo # ADD INDEX + AUTO_INCREMENT= in 1 step +CREATE TABLE test_table (a INT, b INT) ENGINE=InnoDB; + +ALTER TABLE test_table ADD INDEX idx_b(b), AUTO_INCREMENT=7, ALGORITHM=INPLACE; + +SHOW CREATE TABLE test_table; + +DROP TABLE test_table; + +# Verify that combining DROP INDEX with COLLATE= in one INPLACE +# step does not silently discard the index drop. +--echo # DROP INDEX + COLLATE= in 1 step +CREATE TABLE `test_table` +( + `field1` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + INDEX(`field1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +ALTER TABLE test_table + DROP INDEX field1, + COLLATE=utf8mb4_0900_ai_ci, + ALGORITHM=INPLACE; + +SHOW CREATE TABLE test_table; + +DROP TABLE test_table; diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc index 258149ba6009..bad5f4b591ec 100644 --- a/storage/innobase/handler/handler0alter.cc +++ b/storage/innobase/handler/handler0alter.cc @@ -931,10 +931,12 @@ static inline bool is_instant(const Alter_inplace_info *ha_alter_info) { return (false); } - Alter_inplace_info::HA_ALTER_FLAGS alter_inplace_flags = - ha_alter_info->handler_flags & ~(INNOBASE_INPLACE_IGNORE); + bool has_change_create_option = + (ha_alter_info->handler_flags & ~INNOBASE_INPLACE_IGNORE & + ~Alter_inplace_info::ALTER_COLUMN_EQUAL_PACK_LENGTH) == + Alter_inplace_info::CHANGE_CREATE_OPTION; - if (alter_inplace_flags == Alter_inplace_info::CHANGE_CREATE_OPTION && + if (has_change_create_option && !(ha_alter_info->create_info->used_fields & (HA_CREATE_USED_ROW_FORMAT | HA_CREATE_USED_KEY_BLOCK_SIZE | HA_CREATE_USED_TABLESPACE))) { @@ -5960,7 +5962,8 @@ bool ha_innobase::prepare_inplace_alter_table_impl( } if (!(ha_alter_info->handler_flags & INNOBASE_ALTER_DATA) || - ((ha_alter_info->handler_flags & ~INNOBASE_INPLACE_IGNORE) == + ((ha_alter_info->handler_flags & ~INNOBASE_INPLACE_IGNORE & + ~Alter_inplace_info::ALTER_COLUMN_EQUAL_PACK_LENGTH) == Alter_inplace_info::CHANGE_CREATE_OPTION && !innobase_need_rebuild(ha_alter_info))) { if (heap) { @@ -6002,9 +6005,8 @@ bool ha_innobase::prepare_inplace_alter_table_impl( } return dd_prepare_inplace_alter_table(m_user_thd, ctx->old_table, ctx->new_table, old_dd_tab); - } else { - return false; } + return false; } /* If we are to build a full-text search index, check whether @@ -6215,7 +6217,8 @@ bool ha_innobase::inplace_alter_table_impl(TABLE *altered_table, return all_ok(); } - if (((ha_alter_info->handler_flags & ~INNOBASE_INPLACE_IGNORE) == + if (((ha_alter_info->handler_flags & ~INNOBASE_INPLACE_IGNORE & + ~Alter_inplace_info::ALTER_COLUMN_EQUAL_PACK_LENGTH) == Alter_inplace_info::CHANGE_CREATE_OPTION && !innobase_need_rebuild(ha_alter_info))) { return all_ok();