From c99072c7484fad1a942ae175e1c942178fb41295 Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Wed, 1 Apr 2026 16:42:10 +0530 Subject: [PATCH] MDEV-38412 System tablespace fails to shrink due to legacy tables Problem: ======= - InnoDB system tablespace fails to autoshrink when it contains legacy internal tables. These are non-user tables and internal table exist from older version. Because the current shrink logic does recognize these entries as user table, they block the defragmentation process required to reduce the tablespace size. Solution: ========= To enable successful shrinking, InnoDB has been updated to identify and remove these legacy entries during the startup: fsp_drop_legacy_tables(): A new function that scans the InnoDB system tables for entries lacking the / naming convention. It triggers the removal of these table objects from InnoDb system tables and ensures the purge thread subsequently drops any associated index trees in SYS_INDEXES. delete_from_sys_table_entries(): Function is to remove specific table IDs from internal system catalogs. fsp_system_tablespace_truncate(): If legacy tables are detected, InnoDB prioritizes their removal. To ensure data integrity and complete the shrink, a two-restart sequence may be required: 1) purge the legacy table 2) Defragment the system tablespace and shrink the system tablespace further --- .../suite/innodb/r/sys_truncate_debug.result | 9 ++ .../suite/innodb/t/sys_truncate_debug.test | 30 +++++++ storage/innobase/dict/dict0crea.cc | 17 ++++ storage/innobase/dict/drop.cc | 26 +----- storage/innobase/fsp/fsp0fsp.cc | 83 ++++++++++++++++++- storage/innobase/include/srv0srv.h | 8 ++ storage/innobase/srv/srv0start.cc | 29 +++++++ 7 files changed, 176 insertions(+), 26 deletions(-) diff --git a/mysql-test/suite/innodb/r/sys_truncate_debug.result b/mysql-test/suite/innodb/r/sys_truncate_debug.result index f51693d849c71..08b25fe72db93 100644 --- a/mysql-test/suite/innodb/r/sys_truncate_debug.result +++ b/mysql-test/suite/innodb/r/sys_truncate_debug.result @@ -54,3 +54,12 @@ InnoDB YES Supports transactions, row-level locking, foreign keys and encryption SELECT NAME, FILE_SIZE FROM INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES WHERE SPACE=0; NAME FILE_SIZE innodb_system 3145728 +# +# MDEV-38412 System tablespace fails to shrink due to legacy tables +# +# restart: --innodb-data-home-dir=MYSQLTEST_VARDIR/tmp/old_datadir --innodb-log-group-home-dir=MYSQLTEST_VARDIR/tmp/old_datadir --innodb_undo_directory=MYSQLTEST_VARDIR/tmp/old_datadir --debug_dbug=d,create_dummy_sys_tables +# restart: --innodb-data-home-dir=MYSQLTEST_VARDIR/tmp/old_datadir --innodb-log-group-home-dir=MYSQLTEST_VARDIR/tmp/old_datadir --innodb_undo_directory=MYSQLTEST_VARDIR/tmp/old_datadir --innodb_data_file_path=ibdata1:10M:autoextend:autoshrink --innodb_fast_shutdown=0 +FOUND 1 /InnoDB: Dropping the unknown table/ in mysqld.1.err +FOUND 1 /InnoDB: Found the unknown table/ in mysqld.1.err +# restart: --innodb-data-home-dir=MYSQLTEST_VARDIR/tmp/old_datadir --innodb-log-group-home-dir=MYSQLTEST_VARDIR/tmp/old_datadir --innodb_undo_directory=MYSQLTEST_VARDIR/tmp/old_datadir --innodb_data_file_path=ibdata1:10M:autoextend:autoshrink +# restart diff --git a/mysql-test/suite/innodb/t/sys_truncate_debug.test b/mysql-test/suite/innodb/t/sys_truncate_debug.test index 7dcb5ffde2aa9..a9bcc943da40b 100644 --- a/mysql-test/suite/innodb/t/sys_truncate_debug.test +++ b/mysql-test/suite/innodb/t/sys_truncate_debug.test @@ -74,3 +74,33 @@ SELECT * FROM INFORMATION_SCHEMA.ENGINES WHERE engine = 'innodb' AND support IN ('YES', 'DEFAULT', 'ENABLED'); SELECT NAME, FILE_SIZE FROM INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES WHERE SPACE=0; + +--echo # +--echo # MDEV-38412 System tablespace fails to shrink due to legacy tables +--echo # +let bugdir= $MYSQLTEST_VARDIR/tmp/old_datadir; +--mkdir $bugdir +let $dirs= --innodb-data-home-dir=$bugdir --innodb-log-group-home-dir=$bugdir --innodb_undo_directory=$bugdir; +# Create a new data directory +let $restart_parameters=$dirs --debug_dbug=d,create_dummy_sys_tables; +--source include/restart_mysqld.inc + +let SEARCH_FILE = $MYSQLTEST_VARDIR/log/mysqld.1.err; + +# Drop the unknown table during slow shutdown +let $restart_parameters=$dirs --innodb_data_file_path=ibdata1:10M:autoextend:autoshrink --innodb_fast_shutdown=0; +--source include/restart_mysqld.inc +let SEARCH_PATTERN=InnoDB: Dropping the unknown table; +--source include/search_pattern_in_file.inc + +let SEARCH_PATTERN=InnoDB: Found the unknown table; +--source include/search_pattern_in_file.inc + +# Shrink the system tablespace further +let $restart_parameters=$dirs --innodb_data_file_path=ibdata1:10M:autoextend:autoshrink; +--source include/restart_mysqld.inc + +# Cleanup +let $restart_parameters=; +--source include/restart_mysqld.inc +rmdir $bugdir; diff --git a/storage/innobase/dict/dict0crea.cc b/storage/innobase/dict/dict0crea.cc index 8ee7c46739540..24af4db76790b 100644 --- a/storage/innobase/dict/dict0crea.cc +++ b/storage/innobase/dict/dict0crea.cc @@ -1459,6 +1459,23 @@ dberr_t dict_sys_t::create_or_check_sys_tables() noexcept } } + DBUG_EXECUTE_IF("create_dummy_sys_tables", + { + error= que_eval_sql( + nullptr, "PROCEDURE CREATE_DUMMY_1() IS\n" + "BEGIN\n" + "CREATE TABLE\n" + "DUMMY_1(TABLE_ID BIGINT, POS INT);\n" + "CREATE UNIQUE CLUSTERED INDEX CLUST_IND" + " ON DUMMY_1(TABLE_ID, POS);\n" + "END;\n", trx); + if (error) + { + tablename = "DUMMY"; + goto err_exit; + } + }); + trx->commit(); row_mysql_unlock_data_dictionary(trx); trx->clear_and_free(); diff --git a/storage/innobase/dict/drop.cc b/storage/innobase/dict/drop.cc index 15f6c29dfdce9..91a2199c95e5c 100644 --- a/storage/innobase/dict/drop.cc +++ b/storage/innobase/dict/drop.cc @@ -201,31 +201,7 @@ dberr_t trx_t::drop_table(const dict_table_t &table) mod_tables.emplace(const_cast(&table), undo_no). first->second.set_dropped(); - pars_info_t *info= pars_info_create(); - pars_info_add_ull_literal(info, "id", table.id); - return que_eval_sql(info, - "PROCEDURE DROP_TABLE() IS\n" - "iid CHAR;\n" - - "DECLARE CURSOR idx IS\n" - "SELECT ID FROM SYS_INDEXES\n" - "WHERE TABLE_ID=:id FOR UPDATE;\n" - - "BEGIN\n" - - "DELETE FROM SYS_TABLES WHERE ID=:id;\n" - "DELETE FROM SYS_COLUMNS WHERE TABLE_ID=:id;\n" - - "OPEN idx;\n" - "WHILE 1 = 1 LOOP\n" - " FETCH idx INTO iid;\n" - " IF (SQL % NOTFOUND) THEN EXIT; END IF;\n" - " DELETE FROM SYS_INDEXES WHERE CURRENT OF idx;\n" - " DELETE FROM SYS_FIELDS WHERE INDEX_ID=iid;\n" - "END LOOP;\n" - "CLOSE idx;\n" - - "END;\n", this); + return delete_from_sys_table_entries(table.id, this); } /** Commit the transaction, possibly after drop_table(). diff --git a/storage/innobase/fsp/fsp0fsp.cc b/storage/innobase/fsp/fsp0fsp.cc index 1fa03e2099894..0f4b920f8664e 100644 --- a/storage/innobase/fsp/fsp0fsp.cc +++ b/storage/innobase/fsp/fsp0fsp.cc @@ -5690,6 +5690,84 @@ dberr_t IndexDefragmenter::defragment(SpaceDefragmenter *space_defrag) noexcept return err; } +/** Determine if a table should be dropped during system tablespace +autoshrinking. This function identifies legacy tables in the +system tablespace that should be removed. A table is considered a +legacy/unknown table if the table name does not contain '/' +(indicating it's not a proper database/table name) +@return error code or DB_SUCCESS */ +static dberr_t fsp_drop_legacy_tables(bool &drop_unknown) noexcept +{ + mtr_t mtr{nullptr}; + btr_pcur_t pcur; + dberr_t err= DB_SUCCESS; + std::vector unknown_tables; + mtr.start(); + for (const rec_t *rec= dict_startscan_system(&pcur, &mtr, + dict_sys.sys_tables); + rec; rec= dict_getnext_system(&pcur, &mtr)) + { + const byte *field= nullptr; + ulint len= 0; + if (rec_get_deleted_flag(rec, 0)) + continue; + field= rec_get_nth_field_old(rec, DICT_FLD__SYS_TABLES__SPACE, &len); + if (len != 4) + { + err= DB_CORRUPTION; + break; + } + if (mach_read_from_4(field) != 0) + continue; + field= rec_get_nth_field_old(rec, DICT_FLD__SYS_TABLES__ID, &len); + if (len != 8) + { + err= DB_CORRUPTION; + break; + } + + table_id_t table_id= mach_read_from_8(field); + if (dict_sys.is_sys_table(table_id)) + continue; + field = rec_get_nth_field_old( + rec, DICT_FLD__SYS_TABLES__NAME, &len); + + if (len == UNIV_SQL_NULL || len == 0) + { + err= DB_CORRUPTION; + break; + } + if (memchr(rec, '/', len)) + continue; + sql_print_information("InnoDB: Found the unknown table %.*s", + (int) len, rec); + unknown_tables.emplace_back(table_id); + } + mtr.commit(); + if (err || unknown_tables.size() == 0) + return err; + + drop_unknown= true; + sql_print_information("InnoDB: Dropping the unknown tables"); + trx_t *trx= trx_create(); + trx_start_for_ddl(trx); + dict_sys.lock(SRW_LOCK_CALL); + + for (table_id_t table_id : unknown_tables) + { + err= delete_from_sys_table_entries(table_id, trx); + if (err) + break; + } + + if (err == DB_SUCCESS) + trx->commit(); + else + trx->rollback(); + dict_sys.unlock(); + trx->clear_and_free(); + return err; +} /** check whether any user table exist in system tablespace @retval DB_SUCCESS_LOCKED_REC if user table exist @retval DB_SUCCESS if no user table exist @@ -5781,7 +5859,10 @@ void fsp_system_tablespace_truncate(bool shutdown) if (!shutdown) { - err= space->defragment(); + bool drop_unknown= false; + err= fsp_drop_legacy_tables(drop_unknown); + if (err == DB_SUCCESS || drop_unknown == false) + err= space->defragment(); if (err) { srv_sys_space.set_shrink_fail(); diff --git a/storage/innobase/include/srv0srv.h b/storage/innobase/include/srv0srv.h index d61d3ef4c7f05..dd8ca689ef238 100644 --- a/storage/innobase/include/srv0srv.h +++ b/storage/innobase/include/srv0srv.h @@ -683,3 +683,11 @@ static inline void srv_start_periodic_timer(std::unique_ptr& t, void srv_thread_pool_init(); void srv_thread_pool_end(); + +/** Delete the given table id entries from InnoDB system tables +(like SYS_TABLES, SYS_COLUMNS, SYS_FIELDS, SYS_INDEXES) +@param table_id table identifier to be removed +@param trx transaction +@return DB_SUCCESS or error code */ +dberr_t delete_from_sys_table_entries(table_id_t table_id, + trx_t *trx) noexcept; diff --git a/storage/innobase/srv/srv0start.cc b/storage/innobase/srv/srv0start.cc index ca5e080ec0960..7513e2b073ae9 100644 --- a/storage/innobase/srv/srv0start.cc +++ b/storage/innobase/srv/srv0start.cc @@ -2152,3 +2152,32 @@ srv_get_meta_data_filename( ut_free(path); } + +dberr_t delete_from_sys_table_entries(table_id_t table_id, trx_t *trx) noexcept +{ + pars_info_t *info= pars_info_create(); + pars_info_add_ull_literal(info, "id", table_id); + return que_eval_sql(info, + "PROCEDURE DROP_TABLE() IS\n" + "iid CHAR;\n" + + "DECLARE CURSOR idx IS\n" + "SELECT ID FROM SYS_INDEXES\n" + "WHERE TABLE_ID=:id FOR UPDATE;\n" + + "BEGIN\n" + + "DELETE FROM SYS_TABLES WHERE ID=:id;\n" + "DELETE FROM SYS_COLUMNS WHERE TABLE_ID=:id;\n" + + "OPEN idx;\n" + "WHILE 1 = 1 LOOP\n" + " FETCH idx INTO iid;\n" + " IF (SQL % NOTFOUND) THEN EXIT; END IF;\n" + " DELETE FROM SYS_INDEXES WHERE CURRENT OF idx;\n" + " DELETE FROM SYS_FIELDS WHERE INDEX_ID=iid;\n" + "END LOOP;\n" + "CLOSE idx;\n" + + "END;\n", trx); +}