Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions mysql-test/suite/innodb/r/sys_truncate_debug.result
Original file line number Diff line number Diff line change
Expand Up @@ -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
30 changes: 30 additions & 0 deletions mysql-test/suite/innodb/t/sys_truncate_debug.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +92 to +97
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we also look for the specific names of the garbage tables here? We could be outputting some garbage, and the test would not catch that.

Can we also check the contents of INFORMATION_SCHEMA.INNODB_SYS_TABLES and friends to show that the dummy tables appear and disappear as intended?


# 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;
17 changes: 17 additions & 0 deletions storage/innobase/dict/dict0crea.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
26 changes: 1 addition & 25 deletions storage/innobase/dict/drop.cc
Original file line number Diff line number Diff line change
Expand Up @@ -201,31 +201,7 @@ dberr_t trx_t::drop_table(const dict_table_t &table)
mod_tables.emplace(const_cast<dict_table_t*>(&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().
Expand Down
83 changes: 82 additions & 1 deletion storage/innobase/fsp/fsp0fsp.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<table_id_t> 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;
Comment on lines +5746 to +5748
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not need mtr or pcur after this point. Could this be split into two functions?


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);
Comment on lines +5751 to +5758
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we only need to lock dict_sys for invoking the SQL parser. There is no need to hold the latch across the transaction commit. What we are really missing here is a call to lock_sys_tables(trx) after the creation of the transaction.

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
Expand Down Expand Up @@ -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();
Expand Down
8 changes: 8 additions & 0 deletions storage/innobase/include/srv0srv.h
Original file line number Diff line number Diff line change
Expand Up @@ -683,3 +683,11 @@ static inline void srv_start_periodic_timer(std::unique_ptr<tpool::timer>& 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;
29 changes: 29 additions & 0 deletions storage/innobase/srv/srv0start.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Loading