From 0936c13809f85972ed176abb764b925edd954256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Thu, 25 Apr 2024 13:44:10 +0300 Subject: [PATCH] MDEV-33993 Possible server hang on DROP INDEX or RENAME INDEX commit_try_norebuild(): Add the parameter statistics_exist, similar to commit_try_rebuild(). If the InnoDB statistics tables did not exist, we will not attempt to update statistics later on during the transaction. Thanks to Matthias Leich for originally reproducing this scenario. --- .../suite/innodb/r/innodb-alter-debug.result | 15 +++++++++++-- .../suite/innodb/t/innodb-alter-debug.test | 22 +++++++++++++++---- storage/innobase/handler/handler0alter.cc | 16 ++++++++++++-- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/mysql-test/suite/innodb/r/innodb-alter-debug.result b/mysql-test/suite/innodb/r/innodb-alter-debug.result index c55b4a3ddd5..fd33581ffab 100644 --- a/mysql-test/suite/innodb/r/innodb-alter-debug.result +++ b/mysql-test/suite/innodb/r/innodb-alter-debug.result @@ -108,11 +108,11 @@ DROP TABLE t; # MDEV-26772 InnoDB DDL fails with DUPLICATE KEY error # create table t1(f1 int not null primary key, -f2 int not null, index idx(f2))engine=innodb; +f2 int not null, index idx(f2), index i(f2,f1))engine=innodb; insert into t1 values(1, 1); connect con1,localhost,root,,,; SET DEBUG_SYNC='before_delete_table_stats SIGNAL blocked WAIT_FOR go'; -SET innodb_lock_wait_timeout=0; +SET STATEMENT innodb_lock_wait_timeout=0 FOR ALTER TABLE t1 FORCE, ALGORITHM=COPY; connection default; SET DEBUG_SYNC='now WAIT_FOR blocked'; @@ -124,6 +124,17 @@ connection con1; connection default; COMMIT; SET DEBUG_SYNC=RESET; +RENAME TABLE mysql.innodb_table_stats TO mysql.innodb_table_stats_hidden; +connection con1; +SET DEBUG_SYNC='innodb_commit_inplace_before_lock SIGNAL blocked WAIT_FOR go'; +ALTER TABLE t1 DROP INDEX i; +connection default; +SET DEBUG_SYNC='now WAIT_FOR blocked'; +RENAME TABLE mysql.innodb_table_stats_hidden TO mysql.innodb_table_stats; +SET DEBUG_SYNC='now SIGNAL go'; +connection con1; +connection default; +SET DEBUG_SYNC=RESET; connection con1; ALTER TABLE t1 RENAME KEY idx TO idx1, ALGORITHM=COPY; disconnect con1; diff --git a/mysql-test/suite/innodb/t/innodb-alter-debug.test b/mysql-test/suite/innodb/t/innodb-alter-debug.test index c4a68ac71b7..6b94bfd214f 100644 --- a/mysql-test/suite/innodb/t/innodb-alter-debug.test +++ b/mysql-test/suite/innodb/t/innodb-alter-debug.test @@ -144,15 +144,14 @@ DROP TABLE t; --echo # create table t1(f1 int not null primary key, - - f2 int not null, index idx(f2))engine=innodb; + f2 int not null, index idx(f2), index i(f2,f1))engine=innodb; insert into t1 values(1, 1); connect(con1,localhost,root,,,); SET DEBUG_SYNC='before_delete_table_stats SIGNAL blocked WAIT_FOR go'; -SET innodb_lock_wait_timeout=0; -send ALTER TABLE t1 FORCE, ALGORITHM=COPY; +send SET STATEMENT innodb_lock_wait_timeout=0 FOR +ALTER TABLE t1 FORCE, ALGORITHM=COPY; connection default; SET DEBUG_SYNC='now WAIT_FOR blocked'; @@ -167,6 +166,21 @@ connection default; COMMIT; SET DEBUG_SYNC=RESET; +RENAME TABLE mysql.innodb_table_stats TO mysql.innodb_table_stats_hidden; +connection con1; +SET DEBUG_SYNC='innodb_commit_inplace_before_lock SIGNAL blocked WAIT_FOR go'; +send ALTER TABLE t1 DROP INDEX i; + +connection default; +SET DEBUG_SYNC='now WAIT_FOR blocked'; +RENAME TABLE mysql.innodb_table_stats_hidden TO mysql.innodb_table_stats; +SET DEBUG_SYNC='now SIGNAL go'; + +connection con1; +reap; +connection default; +SET DEBUG_SYNC=RESET; + connection con1; ALTER TABLE t1 RENAME KEY idx TO idx1, ALGORITHM=COPY; disconnect con1; diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc index 087a0c32319..63a5e56e666 100644 --- a/storage/innobase/handler/handler0alter.cc +++ b/storage/innobase/handler/handler0alter.cc @@ -10228,6 +10228,7 @@ when rebuilding the table. @param ctx In-place ALTER TABLE context @param altered_table MySQL table that is being altered @param old_table MySQL table as it is before the ALTER operation +@param statistics_exist whether to update InnoDB persistent statistics @param trx Data dictionary transaction @param table_name Table name in MySQL @retval true Failure @@ -10501,6 +10502,7 @@ when not rebuilding the table. @param ha_alter_info Data used during in-place alter @param ctx In-place ALTER TABLE context @param old_table MySQL table as it is before the ALTER operation +@param statistics_exist whether to update InnoDB persistent statistics @param trx Data dictionary transaction @param table_name Table name in MySQL @retval true Failure @@ -10514,6 +10516,7 @@ commit_try_norebuild( ha_innobase_inplace_ctx*ctx, TABLE* altered_table, const TABLE* old_table, + bool statistics_exist, trx_t* trx, const char* table_name) { @@ -10628,6 +10631,10 @@ commit_try_norebuild( goto handle_error; } + if (!statistics_exist) { + continue; + } + error = dict_stats_delete_from_index_stats(db, table, index->name, trx); switch (error) { @@ -10639,7 +10646,8 @@ commit_try_norebuild( } } - if (const size_t size = ha_alter_info->rename_keys.size()) { + if (!statistics_exist) { + } else if (const size_t size = ha_alter_info->rename_keys.size()) { char tmp_name[5]; char db[MAX_DB_UTF8_LEN], table[MAX_TABLE_UTF8_LEN]; @@ -11386,6 +11394,8 @@ err_index: } } + DEBUG_SYNC(m_user_thd, "innodb_commit_inplace_before_lock"); + DBUG_EXECUTE_IF("stats_lock_fail", error = DB_LOCK_WAIT_TIMEOUT; trx_rollback_for_mysql(trx);); @@ -11469,7 +11479,9 @@ fail: goto fail; } } else if (commit_try_norebuild(ha_alter_info, ctx, - altered_table, table, trx, + altered_table, table, + table_stats && index_stats, + trx, table_share->table_name.str)) { goto fail; }