diff --git a/mysql-test/suite/innodb/r/recovery_shutdown.result b/mysql-test/suite/innodb/r/recovery_shutdown.result index 6f7ca6e0d1d..85b6ae51a42 100644 --- a/mysql-test/suite/innodb/r/recovery_shutdown.result +++ b/mysql-test/suite/innodb/r/recovery_shutdown.result @@ -1,5 +1,6 @@ FLUSH TABLES; call mtr.add_suppression("Found 1 prepared XA transactions"); +call mtr.add_suppression("InnoDB: Cannot free the unused segments in system tablespace because a previous shutdown was not with innodb_fast_shutdown=0 or XA PREPARE transactions exist"); # # MDEV-13797 InnoDB may hang if shutdown is initiated soon after startup # while rolling back recovered incomplete transactions diff --git a/mysql-test/suite/innodb/r/sys_truncate_debug.result b/mysql-test/suite/innodb/r/sys_truncate_debug.result index f122e9446e8..b198d449a76 100644 --- a/mysql-test/suite/innodb/r/sys_truncate_debug.result +++ b/mysql-test/suite/innodb/r/sys_truncate_debug.result @@ -19,13 +19,20 @@ InnoDB 0 transactions not purged SELECT NAME, FILE_SIZE FROM INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES WHERE SPACE = 0; NAME FILE_SIZE innodb_system 540016640 -# restart: --debug_dbug=+d,shrink_buffer_pool_full +# restart: --debug_dbug=+d,traversal_extent_fail FOUND 1 /\[Warning\] InnoDB: Cannot shrink the system tablespace/ in mysqld.1.err SELECT * FROM INFORMATION_SCHEMA.ENGINES WHERE engine = 'innodb' AND support IN ('YES', 'DEFAULT', 'ENABLED'); ENGINE SUPPORT COMMENT TRANSACTIONS XA SAVEPOINTS InnoDB YES Supports transactions, row-level locking, foreign keys and encryption for tables YES YES YES +# restart: --debug_dbug=+d,shrink_buffer_pool_full +FOUND 2 /\[Warning\] InnoDB: Cannot shrink the system tablespace/ in mysqld.1.err +SELECT * FROM INFORMATION_SCHEMA.ENGINES +WHERE engine = 'innodb' +AND support IN ('YES', 'DEFAULT', 'ENABLED'); +ENGINE SUPPORT COMMENT TRANSACTIONS XA SAVEPOINTS +InnoDB YES Supports transactions, row-level locking, foreign keys and encryption for tables YES YES YES # restart: --debug_dbug=+d,mtr_log_max_size FOUND 1 /\[ERROR\] InnoDB: Cannot shrink the system tablespace/ in mysqld.1.err SELECT * FROM INFORMATION_SCHEMA.ENGINES diff --git a/mysql-test/suite/innodb/r/undo_leak.result b/mysql-test/suite/innodb/r/undo_leak.result new file mode 100644 index 00000000000..491ec7c9279 --- /dev/null +++ b/mysql-test/suite/innodb/r/undo_leak.result @@ -0,0 +1,25 @@ +# restart: --debug_dbug=d,undo_segment_leak +SET GLOBAL INNODB_FILE_PER_TABLE=0; +Warnings: +Warning 1287 '@@innodb_file_per_table' is deprecated and will be removed in a future release +CREATE TABLE t1(f1 INT NOT NULL, f2 INT NOT NULL)ENGINE=InnoDB; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +UPDATE t1 SET f1 = f1 + 1 WHERE f1 > 1000; +UPDATE t1 SET f2 = f2 + 1 WHERE f1 > 1000; +UPDATE t1 SET f1 = f2 + 1 WHERE f1 > 1000; +UPDATE t1 SET f2 = f1 + 1 WHERE f1 > 1000; +DELETE FROM t1; +DROP TABLE t1; +set GLOBAL innodb_fast_shutdown=0; +SELECT NAME, FILE_SIZE FROM information_schema.innodb_sys_tablespaces WHERE SPACE = 0; +NAME FILE_SIZE +innodb_system 79691776 +# restart +SELECT NAME, FILE_SIZE FROM information_schema.innodb_sys_tablespaces WHERE SPACE = 0; +NAME FILE_SIZE +innodb_system 12582912 diff --git a/mysql-test/suite/innodb/r/undo_leak_fail.result b/mysql-test/suite/innodb/r/undo_leak_fail.result new file mode 100644 index 00000000000..6aa89ec7d3d --- /dev/null +++ b/mysql-test/suite/innodb/r/undo_leak_fail.result @@ -0,0 +1,94 @@ +call mtr.add_suppression("InnoDB: Cannot free the unused segments in system tablespace because a previous shutdown was not with innodb_fast_shutdown=0"); +call mtr.add_suppression("InnoDB: :autoshrink failed to read the used segment"); +call mtr.add_suppression("InnoDB: :autoshrink failed due to .* in FSP_SEG_INODES_FULL list"); +call mtr.add_suppression("InnoDB: :autoshrink failed due to .* in FSP_SEG_INODES_FREE list"); +call mtr.add_suppression("InnoDB: :autoshrink failed to free the segment"); +call mtr.add_suppression("Found .* prepared XA transactions"); +SELECT NAME, FILE_SIZE FROM information_schema.innodb_sys_tablespaces WHERE SPACE = 0; +NAME FILE_SIZE +innodb_system 10485760 +SET GLOBAL INNODB_FILE_PER_TABLE=0; +Warnings: +Warning 1287 '@@innodb_file_per_table' is deprecated and will be removed in a future release +CREATE TABLE t1(f1 INT NOT NULL, f2 INT NOT NULL)STATS_PERSISTENT=0, ENGINE=InnoDB; +XA START 'x'; +insert into t1 values (1, 1); +XA END 'x'; +XA PREPARE 'x'; +set GLOBAL innodb_fast_shutdown=0; +# restart +# Fail to free the segment due to XA PREPARE transaction +FOUND 2 /InnoDB: Cannot free the unused segments in system tablespace because a previous shutdown was not with innodb_fast_shutdown=0/ in mysqld.1.err +XA COMMIT 'x'; +DROP TABLE t1; +# restart: --debug_dbug=d,undo_segment_leak +SET GLOBAL INNODB_FILE_PER_TABLE=0; +Warnings: +Warning 1287 '@@innodb_file_per_table' is deprecated and will be removed in a future release +CREATE TABLE t1(f1 INT NOT NULL, f2 INT NOT NULL)STATS_PERSISTENT=0, ENGINE=InnoDB; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +UPDATE t1 SET f1 = f1 + 1 WHERE f1 > 1000; +UPDATE t1 SET f2 = f2 + 1 WHERE f1 > 1000; +UPDATE t1 SET f1 = f2 + 1 WHERE f1 > 1000; +UPDATE t1 SET f2 = f1 + 1 WHERE f1 > 1000; +DELETE FROM t1; +DROP TABLE t1; +SELECT NAME, FILE_SIZE FROM information_schema.innodb_sys_tablespaces WHERE SPACE = 0; +NAME FILE_SIZE +innodb_system 77594624 +# restart: --debug_dbug=d,unused_undo_free_fail_1 +# Fail to free the segment due to previous shutdown +FOUND 4 /InnoDB: Cannot free the unused segments in system tablespace because a previous shutdown was not with innodb_fast_shutdown=0/ in mysqld.1.err +SELECT NAME, FILE_SIZE FROM information_schema.innodb_sys_tablespaces WHERE SPACE = 0; +NAME FILE_SIZE +innodb_system 15728640 +SET GLOBAL innodb_fast_shutdown= 0; +# Fail to free the segment while finding the used segments +# restart: --debug_dbug=d,unused_undo_free_fail_2 +SELECT IF(file_size>10485760,'ok',file_size) FROM information_schema.innodb_sys_tablespaces WHERE space=0; +IF(file_size>10485760,'ok',file_size) +ok +FOUND 1 /InnoDB: :autoshrink failed to read the used segment/ in mysqld.1.err +FOUND 1 /InnoDB: :autoshrink failed due to .* in FSP_SEG_INODES_FULL list/ in mysqld.1.err +SET GLOBAL innodb_fast_shutdown= 0; +# Fail to free the segment while finding the used segments +# restart: --debug_dbug=d,unused_undo_free_fail_3 +SELECT IF(file_size>10485760,'ok',file_size) FROM information_schema.innodb_sys_tablespaces WHERE space=0; +IF(file_size>10485760,'ok',file_size) +ok +FOUND 1 /InnoDB: :autoshrink failed due to .* in FSP_SEG_INODES_FREE list/ in mysqld.1.err +SET GLOBAL innodb_fast_shutdown= 0; +# Fail to free the segment while freeing the unused segments +# restart: --debug_dbug=d,unused_undo_free_fail_4 +SELECT IF(file_size>10485760,'ok',file_size) FROM information_schema.innodb_sys_tablespaces WHERE space=0; +IF(file_size>10485760,'ok',file_size) +ok +FOUND 1 /InnoDB: :autoshrink failed to free the segment .* in page .*/ in mysqld.1.err +SET GLOBAL innodb_fast_shutdown= 0; +# Fail to free the segment while freeing the used segments +# restart: --debug_dbug=d,unused_undo_free_fail_5 +SELECT IF(file_size>10485760,'ok',file_size) FROM information_schema.innodb_sys_tablespaces WHERE space=0; +IF(file_size>10485760,'ok',file_size) +ok +FOUND 1 /InnoDB: :autoshrink failed to free the segment .* in page .*/ in mysqld.1.err +SET GLOBAL innodb_fast_shutdown= 0; +# restart +SELECT NAME, FILE_SIZE FROM information_schema.innodb_sys_tablespaces WHERE SPACE = 0; +NAME FILE_SIZE +innodb_system 10485760 +# Fail to reset the TRX_SYS_FSEG_HEADER during undo tablespace +# reinitialization. garbage_collect() shouldn't free the +# TRX_SYS_FSEG_HEADER index node +set global innodb_fast_shutdown=0; +# restart: --innodb_undo_tablespaces=2 --debug_dbug=d,sys_fseg_header_fail +FOUND 1 /InnoDB: :autoshrink freed the segment .* in page .*/ in mysqld.1.err +set global innodb_fast_shutdown=0; +# restart +SELECT NAME, FILE_SIZE FROM information_schema.innodb_sys_tablespaces WHERE SPACE = 0; +NAME FILE_SIZE +innodb_system 10485760 diff --git a/mysql-test/suite/innodb/t/recovery_shutdown.test b/mysql-test/suite/innodb/t/recovery_shutdown.test index 968ea62833a..bdf0bb090b3 100644 --- a/mysql-test/suite/innodb/t/recovery_shutdown.test +++ b/mysql-test/suite/innodb/t/recovery_shutdown.test @@ -6,6 +6,7 @@ # Flush any open myisam tables from previous tests FLUSH TABLES; call mtr.add_suppression("Found 1 prepared XA transactions"); +call mtr.add_suppression("InnoDB: Cannot free the unused segments in system tablespace because a previous shutdown was not with innodb_fast_shutdown=0 or XA PREPARE transactions exist"); --echo # --echo # MDEV-13797 InnoDB may hang if shutdown is initiated soon after startup diff --git a/mysql-test/suite/innodb/t/sys_truncate_debug.test b/mysql-test/suite/innodb/t/sys_truncate_debug.test index b3363f105e3..20fa981685d 100644 --- a/mysql-test/suite/innodb/t/sys_truncate_debug.test +++ b/mysql-test/suite/innodb/t/sys_truncate_debug.test @@ -23,6 +23,18 @@ DROP TABLE t1; --source include/wait_all_purged.inc SELECT NAME, FILE_SIZE FROM INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES WHERE SPACE = 0; +# Corruption during traversal of extent +let $restart_parameters=--debug_dbug=+d,traversal_extent_fail; +--source include/restart_mysqld.inc + +--let SEARCH_PATTERN= \[Warning\] InnoDB: Cannot shrink the system tablespace +let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err; +--source include/search_pattern_in_file.inc + +SELECT * FROM INFORMATION_SCHEMA.ENGINES +WHERE engine = 'innodb' +AND support IN ('YES', 'DEFAULT', 'ENABLED'); + # Ran out of buffer pool let $restart_parameters=--debug_dbug=+d,shrink_buffer_pool_full; --source include/restart_mysqld.inc diff --git a/mysql-test/suite/innodb/t/undo_leak.opt b/mysql-test/suite/innodb/t/undo_leak.opt new file mode 100644 index 00000000000..e8459f850f8 --- /dev/null +++ b/mysql-test/suite/innodb/t/undo_leak.opt @@ -0,0 +1,2 @@ +--innodb_undo_tablespaces=0 +--innodb_sys_tablespaces diff --git a/mysql-test/suite/innodb/t/undo_leak.test b/mysql-test/suite/innodb/t/undo_leak.test new file mode 100644 index 00000000000..760ad7e3ec7 --- /dev/null +++ b/mysql-test/suite/innodb/t/undo_leak.test @@ -0,0 +1,26 @@ +--source include/have_innodb.inc +--source include/have_sequence.inc +--source include/have_debug.inc +--source include/not_embedded.inc + +let $restart_parameters=--debug_dbug=d,undo_segment_leak; +--source include/restart_mysqld.inc +SET GLOBAL INNODB_FILE_PER_TABLE=0; +CREATE TABLE t1(f1 INT NOT NULL, f2 INT NOT NULL)ENGINE=InnoDB; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +UPDATE t1 SET f1 = f1 + 1 WHERE f1 > 1000; +UPDATE t1 SET f2 = f2 + 1 WHERE f1 > 1000; +UPDATE t1 SET f1 = f2 + 1 WHERE f1 > 1000; +UPDATE t1 SET f2 = f1 + 1 WHERE f1 > 1000; +DELETE FROM t1; +DROP TABLE t1; +set GLOBAL innodb_fast_shutdown=0; +SELECT NAME, FILE_SIZE FROM information_schema.innodb_sys_tablespaces WHERE SPACE = 0; +let $restart_parameters=; +--source include/restart_mysqld.inc +SELECT NAME, FILE_SIZE FROM information_schema.innodb_sys_tablespaces WHERE SPACE = 0; diff --git a/mysql-test/suite/innodb/t/undo_leak_fail.opt b/mysql-test/suite/innodb/t/undo_leak_fail.opt new file mode 100644 index 00000000000..79a79a9cd1d --- /dev/null +++ b/mysql-test/suite/innodb/t/undo_leak_fail.opt @@ -0,0 +1,3 @@ +--innodb_data_file_path=ibdata1:10M:autoextend:autoshrink +--innodb_undo_tablespaces=0 +--innodb_sys_tablespaces diff --git a/mysql-test/suite/innodb/t/undo_leak_fail.test b/mysql-test/suite/innodb/t/undo_leak_fail.test new file mode 100644 index 00000000000..0346293eda2 --- /dev/null +++ b/mysql-test/suite/innodb/t/undo_leak_fail.test @@ -0,0 +1,119 @@ +--source include/have_innodb.inc +--source include/have_sequence.inc +--source include/have_debug.inc +--source include/not_embedded.inc + +call mtr.add_suppression("InnoDB: Cannot free the unused segments in system tablespace because a previous shutdown was not with innodb_fast_shutdown=0"); +call mtr.add_suppression("InnoDB: :autoshrink failed to read the used segment"); +call mtr.add_suppression("InnoDB: :autoshrink failed due to .* in FSP_SEG_INODES_FULL list"); +call mtr.add_suppression("InnoDB: :autoshrink failed due to .* in FSP_SEG_INODES_FREE list"); +call mtr.add_suppression("InnoDB: :autoshrink failed to free the segment"); +call mtr.add_suppression("Found .* prepared XA transactions"); +SELECT NAME, FILE_SIZE FROM information_schema.innodb_sys_tablespaces WHERE SPACE = 0; + +SET GLOBAL INNODB_FILE_PER_TABLE=0; +CREATE TABLE t1(f1 INT NOT NULL, f2 INT NOT NULL)STATS_PERSISTENT=0, ENGINE=InnoDB; +XA START 'x'; +insert into t1 values (1, 1); +XA END 'x'; +XA PREPARE 'x'; +set GLOBAL innodb_fast_shutdown=0; +--source include/restart_mysqld.inc +--echo # Fail to free the segment due to XA PREPARE transaction +let SEARCH_PATTERN= InnoDB: Cannot free the unused segments in system tablespace because a previous shutdown was not with innodb_fast_shutdown=0; +let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err; +--source include/search_pattern_in_file.inc +XA COMMIT 'x'; +DROP TABLE t1; + +let $restart_parameters=--debug_dbug=d,undo_segment_leak; +--source include/restart_mysqld.inc +SET GLOBAL INNODB_FILE_PER_TABLE=0; +CREATE TABLE t1(f1 INT NOT NULL, f2 INT NOT NULL)STATS_PERSISTENT=0, ENGINE=InnoDB; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +INSERT INTO t1 SELECT seq, seq FROM seq_1_to_4096; +UPDATE t1 SET f1 = f1 + 1 WHERE f1 > 1000; +UPDATE t1 SET f2 = f2 + 1 WHERE f1 > 1000; +UPDATE t1 SET f1 = f2 + 1 WHERE f1 > 1000; +UPDATE t1 SET f2 = f1 + 1 WHERE f1 > 1000; +DELETE FROM t1; +DROP TABLE t1; +SELECT NAME, FILE_SIZE FROM information_schema.innodb_sys_tablespaces WHERE SPACE = 0; +let $shutdown_timeout=0; +let $restart_parameters=--debug_dbug=d,unused_undo_free_fail_1; +--source include/restart_mysqld.inc + +let $shutdown_timeout=; +--echo # Fail to free the segment due to previous shutdown +--let SEARCH_PATTERN= InnoDB: Cannot free the unused segments in system tablespace because a previous shutdown was not with innodb_fast_shutdown=0 + +let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err; +--source include/search_pattern_in_file.inc + +SELECT NAME, FILE_SIZE FROM information_schema.innodb_sys_tablespaces WHERE SPACE = 0; +SET GLOBAL innodb_fast_shutdown= 0; + +--echo # Fail to free the segment while finding the used segments +let $restart_parameters=--debug_dbug=d,unused_undo_free_fail_2; +--source include/restart_mysqld.inc +SELECT IF(file_size>10485760,'ok',file_size) FROM information_schema.innodb_sys_tablespaces WHERE space=0; +let SEARCH_PATTERN= InnoDB: :autoshrink failed to read the used segment; +let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err; +--source include/search_pattern_in_file.inc + +let SEARCH_PATTERN= InnoDB: :autoshrink failed due to .* in FSP_SEG_INODES_FULL list; +--source include/search_pattern_in_file.inc +SET GLOBAL innodb_fast_shutdown= 0; + +--echo # Fail to free the segment while finding the used segments +let $restart_parameters=--debug_dbug=d,unused_undo_free_fail_3; +--source include/restart_mysqld.inc +SELECT IF(file_size>10485760,'ok',file_size) FROM information_schema.innodb_sys_tablespaces WHERE space=0; + +let SEARCH_PATTERN= InnoDB: :autoshrink failed due to .* in FSP_SEG_INODES_FREE list; +let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err; +--source include/search_pattern_in_file.inc +SET GLOBAL innodb_fast_shutdown= 0; + +--echo # Fail to free the segment while freeing the unused segments +let $restart_parameters=--debug_dbug=d,unused_undo_free_fail_4; +--source include/restart_mysqld.inc +SELECT IF(file_size>10485760,'ok',file_size) FROM information_schema.innodb_sys_tablespaces WHERE space=0; + +let SEARCH_PATTERN= InnoDB: :autoshrink failed to free the segment .* in page .*; +let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err; +--source include/search_pattern_in_file.inc + +SET GLOBAL innodb_fast_shutdown= 0; + +--echo # Fail to free the segment while freeing the used segments +let $restart_parameters=--debug_dbug=d,unused_undo_free_fail_5; +--source include/restart_mysqld.inc +SELECT IF(file_size>10485760,'ok',file_size) FROM information_schema.innodb_sys_tablespaces WHERE space=0; + +let SEARCH_PATTERN= InnoDB: :autoshrink failed to free the segment .* in page .*; +let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err; +--source include/search_pattern_in_file.inc + +SET GLOBAL innodb_fast_shutdown= 0; +let $restart_parameters=; +--source include/restart_mysqld.inc +SELECT NAME, FILE_SIZE FROM information_schema.innodb_sys_tablespaces WHERE SPACE = 0; + +--echo # Fail to reset the TRX_SYS_FSEG_HEADER during undo tablespace +--echo # reinitialization. garbage_collect() shouldn't free the +--echo # TRX_SYS_FSEG_HEADER index node +set global innodb_fast_shutdown=0; +let $restart_parameters=--innodb_undo_tablespaces=2 --debug_dbug=d,sys_fseg_header_fail; +--source include/restart_mysqld.inc +let SEARCH_PATTERN= InnoDB: :autoshrink freed the segment .* in page .*; +let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err; +--source include/search_pattern_in_file.inc +let $restart_parameters=; +set global innodb_fast_shutdown=0; +--source include/restart_mysqld.inc +SELECT NAME, FILE_SIZE FROM information_schema.innodb_sys_tablespaces WHERE SPACE = 0; diff --git a/storage/innobase/dict/dict0load.cc b/storage/innobase/dict/dict0load.cc index 645e0c79490..d336a007d44 100644 --- a/storage/innobase/dict/dict0load.cc +++ b/storage/innobase/dict/dict0load.cc @@ -172,36 +172,24 @@ name_of_col_is( } #endif /* UNIV_DEBUG */ -/********************************************************************//** -This function gets the next system table record as it scans the table. -@return the next record if found, NULL if end of scan */ -static const rec_t* -dict_getnext_system_low( -/*====================*/ - btr_pcur_t* pcur, /*!< in/out: persistent cursor to the - record*/ - mtr_t* mtr) /*!< in: the mini-transaction */ +dict_getnext_system_low(btr_pcur_t *pcur, mtr_t *mtr) { - rec_t* rec = NULL; - - while (!rec) { - btr_pcur_move_to_next_user_rec(pcur, mtr); - - rec = btr_pcur_get_rec(pcur); - - if (!btr_pcur_is_on_user_rec(pcur)) { - /* end of index */ - btr_pcur_close(pcur); - - return(NULL); - } - } - - /* Get a record, let's save the position */ - btr_pcur_store_position(pcur, mtr); - - return(rec); + rec_t *rec = nullptr; + while (!rec) + { + btr_pcur_move_to_next_user_rec(pcur, mtr); + rec = btr_pcur_get_rec(pcur); + if (!btr_pcur_is_on_user_rec(pcur)) + { + /* end of index */ + btr_pcur_close(pcur); + return nullptr; + } + } + /* Get a record, let's save the position */ + btr_pcur_store_position(pcur, mtr); + return rec; } /********************************************************************//** diff --git a/storage/innobase/fsp/fsp0fsp.cc b/storage/innobase/fsp/fsp0fsp.cc index 8cb2793e7b4..05de5823f25 100644 --- a/storage/innobase/fsp/fsp0fsp.cc +++ b/storage/innobase/fsp/fsp0fsp.cc @@ -25,11 +25,8 @@ Created 11/29/1995 Heikki Tuuri ***********************************************************************/ #include "fsp0fsp.h" -#include "buf0buf.h" -#include "fil0fil.h" #include "fil0crypt.h" #include "mtr0log.h" -#include "ut0byte.h" #include "page0page.h" #include "srv0srv.h" #include "srv0start.h" @@ -37,12 +34,16 @@ Created 11/29/1995 Heikki Tuuri #include "btr0sea.h" #include "dict0boot.h" #include "log0log.h" +#include "dict0load.h" #include "dict0mem.h" -#include "fsp0types.h" +#include "btr0pcur.h" +#include "trx0sys.h" #include "log.h" #ifndef DBUG_OFF # include "trx0purge.h" #endif +#include +#include "trx0undo.h" /** Returns the first extent descriptor for a segment. We think of the extent lists of the segment catenated in the order @@ -1477,19 +1478,19 @@ MY_ATTRIBUTE((nonnull)) @param[in,out] inode segment inode @param[in,out] iblock segment inode page @param[in,out] mtr mini-transaction */ -static void fsp_free_seg_inode(fil_space_t *space, fseg_inode_t *inode, - buf_block_t *iblock, mtr_t *mtr) +static dberr_t fsp_free_seg_inode(fil_space_t *space, fseg_inode_t *inode, + buf_block_t *iblock, mtr_t *mtr) { ut_d(space->modify_check(*mtr)); dberr_t err; buf_block_t *header= fsp_get_header(space, mtr, &err); if (!header) - return; + return err; if (UNIV_UNLIKELY(memcmp(FSEG_MAGIC_N_BYTES, FSEG_MAGIC_N + inode, 4))) { space->set_corrupted(); - return; + return DB_CORRUPTION; } const ulint physical_size= space->physical_size(); @@ -1499,24 +1500,27 @@ static void fsp_free_seg_inode(fil_space_t *space, fseg_inode_t *inode, physical_size)) { /* Move the page to another list */ - if (flst_remove(header, FSP_HEADER_OFFSET + FSP_SEG_INODES_FULL, - iblock, FSEG_INODE_PAGE_NODE, limit, mtr) != DB_SUCCESS) - return; - if (flst_add_last(header, FSP_HEADER_OFFSET + FSP_SEG_INODES_FREE, - iblock, FSEG_INODE_PAGE_NODE, limit, mtr) != DB_SUCCESS) - return; + err= flst_remove(header, FSP_HEADER_OFFSET + FSP_SEG_INODES_FULL, + iblock, FSEG_INODE_PAGE_NODE, limit, mtr); + if (err == DB_SUCCESS) + err= flst_add_last(header, FSP_HEADER_OFFSET + FSP_SEG_INODES_FREE, + iblock, FSEG_INODE_PAGE_NODE, limit, mtr); + if (err) + return err; } mtr->memset(iblock, page_offset(inode) + FSEG_ID, FSEG_INODE_SIZE, 0); if (ULINT_UNDEFINED != fsp_seg_inode_page_find_used(iblock->page.frame, physical_size)) - return; + return DB_SUCCESS; /* There are no other used headers left on the page: free it */ - if (flst_remove(header, FSP_HEADER_OFFSET + FSP_SEG_INODES_FREE, - iblock, FSEG_INODE_PAGE_NODE, limit, mtr) == DB_SUCCESS) - fsp_free_page(space, iblock->page.id().page_no(), mtr); + err= flst_remove(header, FSP_HEADER_OFFSET + FSP_SEG_INODES_FREE, + iblock, FSEG_INODE_PAGE_NODE, limit, mtr); + if (err != DB_SUCCESS) + return err; + return fsp_free_page(space, iblock->page.id().page_no(), mtr); } MY_ATTRIBUTE((nonnull(1,4,5), warn_unused_result)) @@ -2510,20 +2514,21 @@ try_to_extend: MY_ATTRIBUTE((nonnull, warn_unused_result)) /** Frees a single page of a segment. -@param[in] seg_inode segment inode @param[in,out] space tablespace -@param[in] offset page number +@param[in] seg_inode segment inode +@param[in,out] iblock block where segment inode are kept @param[in,out] mtr mini-transaction +@param[in] offset page number @param[in] ahi Drop adaptive hash index @return error code */ static dberr_t fseg_free_page_low( + fil_space_t* space, fseg_inode_t* seg_inode, buf_block_t* iblock, - fil_space_t* space, - uint32_t offset, - mtr_t* mtr + mtr_t* mtr, + uint32_t offset #ifdef BTR_CUR_HASH_ADAPT ,bool ahi=false #endif /* BTR_CUR_HASH_ADAPT */ @@ -2656,7 +2661,7 @@ dberr_t fseg_free_page(fseg_header_t *seg_header, fil_space_t *space, { if (!space->full_crc32()) fil_block_check_type(*iblock, FIL_PAGE_INODE, mtr); - return fseg_free_page_low(seg_inode, iblock, space, offset, mtr); + return fseg_free_page_low(space, seg_inode, iblock, mtr, offset); } return err; @@ -2812,6 +2817,71 @@ remove: return DB_SUCCESS; } +/** Free the extent and fragment page associated with +the segment. +@param space tablespace where segment resides +@param inode index node information +@param iblock page where segment header are placed +@param mtr mini-transaction +@param hdr_page_no segment header page number field +@param ahi adaptive hash index +@return DB_SUCCESS_LOCKED_REC when freeing wasn't completed +@return DB_SUCCESS or other error code when freeing was completed */ +static +dberr_t fseg_free_step_low(fil_space_t *space, fseg_inode_t *inode, + buf_block_t *iblock, mtr_t *mtr, + const byte *hdr_page_no +#ifdef BTR_CUR_HASH_ADAPT + , bool ahi=false +#endif /* BTR_CUR_HASH_ADAPT */ + ) +{ + dberr_t err= DB_SUCCESS; + if (xdes_t *descr= fseg_get_first_extent(inode, space, mtr, &err)) + { + err= fseg_free_extent(inode, iblock, space, + xdes_get_offset(descr), mtr +#ifdef BTR_CUR_HASH_ADAPT + , ahi +#endif /* BTR_CUR_HASH_ADAPT */ + ); + return err == DB_SUCCESS ? DB_SUCCESS_LOCKED_REC : err; + } + + if (err != DB_SUCCESS) + return err; + + /* Free a fragment page. If there are no fragment pages + exist in the array then free the file segment inode */ + ulint n = fseg_find_last_used_frag_page_slot(inode); + if (UNIV_UNLIKELY(n == ULINT_UNDEFINED)) + return hdr_page_no + ? DB_SUCCESS + : fsp_free_seg_inode(space, inode, iblock, mtr); + + if (hdr_page_no && !memcmp_aligned<2>(hdr_page_no + FIL_PAGE_OFFSET, + inode + FSEG_FRAG_ARR + + n * FSEG_FRAG_SLOT_SIZE, 4)) + /* header_page_no is only passed by fseg_free_step_not_header(). + In that case, the header page must be preserved, to be freed + when we're finally called by fseg_free_step(). */ + return DB_SUCCESS; + + uint32_t page_no= fseg_get_nth_frag_page_no(inode, n); + err= fseg_free_page_low(space, inode, iblock, mtr, page_no +#ifdef BTR_CUR_HASH_ADAPT + , ahi +#endif /* BTR_CUR_HASH_ADAPT */ + ); + if (err != DB_SUCCESS) + return err; + buf_page_free(space, page_no, mtr); + if (!hdr_page_no && + fseg_find_last_used_frag_page_slot(inode) == ULINT_UNDEFINED) + return fsp_free_seg_inode(space, inode, iblock, mtr); + return DB_SUCCESS_LOCKED_REC; +} + /** Frees part of a segment. This function can be used to free a segment by repeatedly calling this function in different mini-transactions. Doing the freeing in a single mini-transaction @@ -2831,7 +2901,6 @@ fseg_free_step( #endif /* BTR_CUR_HASH_ADAPT */ ) { - ulint n; fseg_inode_t* inode; const uint32_t space_id = page_get_space_id(page_align(header)); @@ -2864,54 +2933,11 @@ fseg_free_step( fil_block_check_type(*iblock, FIL_PAGE_INODE, mtr); } - dberr_t err; - descr = fseg_get_first_extent(inode, space, mtr, &err); - - if (descr) { - /* Free the extent held by the segment */ - return fseg_free_extent(inode, iblock, space, - xdes_get_offset(descr), mtr + return fseg_free_step_low(space, inode, iblock, mtr, nullptr #ifdef BTR_CUR_HASH_ADAPT - , ahi + , ahi #endif /* BTR_CUR_HASH_ADAPT */ - ) != DB_SUCCESS; - } - - if (err != DB_SUCCESS || space->is_stopping()) { - return true; - } - - /* Free a frag page */ - n = fseg_find_last_used_frag_page_slot(inode); - - if (n == ULINT_UNDEFINED) { - /* Freeing completed: free the segment inode */ - fsp_free_seg_inode(space, inode, iblock, mtr); - return true; - } - - uint32_t page_no = fseg_get_nth_frag_page_no(inode, n); - - if (fseg_free_page_low(inode, iblock, space, page_no, mtr -#ifdef BTR_CUR_HASH_ADAPT - , ahi -#endif /* BTR_CUR_HASH_ADAPT */ - ) != DB_SUCCESS) { - return true; - } - - buf_page_free(space, page_no, mtr); - - n = fseg_find_last_used_frag_page_slot(inode); - - if (n == ULINT_UNDEFINED) { - /* Freeing completed: free the segment inode */ - fsp_free_seg_inode(space, inode, iblock, mtr); - - return true; - } - - return false; + ) != DB_SUCCESS_LOCKED_REC; } bool @@ -2923,16 +2949,16 @@ fseg_free_step_not_header( #endif /* BTR_CUR_HASH_ADAPT */ ) { - fseg_inode_t* inode; - - const uint32_t space_id = page_get_space_id(page_align(header)); + const page_t *page = page_align(header); + const uint32_t space_id = page_get_space_id(page); ut_ad(mtr->is_named_space(space_id)); fil_space_t* space = mtr->x_lock_space(space_id); buf_block_t* iblock; - inode = fseg_inode_try_get(header, space_id, space->zip_size(), - mtr, &iblock); + fseg_inode_t *inode = fseg_inode_try_get(header, space_id, + space->zip_size(), + mtr, &iblock); if (space->is_stopping()) { return true; } @@ -2940,7 +2966,7 @@ fseg_free_step_not_header( if (!inode) { ib::warn() << "Double free of " << page_id_t(space_id, - page_get_page_no(page_align(header))); + page_get_page_no(page)); return true; } @@ -2948,43 +2974,11 @@ fseg_free_step_not_header( fil_block_check_type(*iblock, FIL_PAGE_INODE, mtr); } - dberr_t err; - if (xdes_t* descr = fseg_get_first_extent(inode, space, mtr, &err)) { - /* Free the extent held by the segment */ - return fseg_free_extent(inode, iblock, space, - xdes_get_offset(descr), - mtr + return fseg_free_step_low(space, inode, iblock, mtr, page #ifdef BTR_CUR_HASH_ADAPT - , ahi + , ahi #endif /* BTR_CUR_HASH_ADAPT */ - ) != DB_SUCCESS; - } else if (err != DB_SUCCESS) { - return true; - } - - /* Free a frag page */ - - ulint n = fseg_find_last_used_frag_page_slot(inode); - - if (UNIV_UNLIKELY(n == ULINT_UNDEFINED)) { - return true; - } - - uint32_t page_no = fseg_get_nth_frag_page_no(inode, n); - - if (page_no == page_get_page_no(page_align(header))) { - return true; - } - - if (fseg_free_page_low(inode, iblock, space, page_no, mtr -#ifdef BTR_CUR_HASH_ADAPT - , ahi -#endif /* BTR_CUR_HASH_ADAPT */ - ) != DB_SUCCESS) { - return true; - } - buf_page_free(space, page_no, mtr); - return false; + ) != DB_SUCCESS_LOCKED_REC; } /** Returns the first extent descriptor for a segment. @@ -3577,22 +3571,463 @@ dberr_t fsp_sys_tablespace_validate() } #endif /* UNIV_DEBUG */ -void fsp_system_tablespace_truncate() +/** Store the inode information which basically stores +the page and offset */ +struct inode_info : private std::unordered_set +{ +public: + /** Register an inode + @param page index node page + @param offset index node offset within the page + @retval true in case of successful registeration + @retval false in case of invalid entry or already inserted inode */ + __attribute__((warn_unused_result)) + bool insert_inode(uint32_t page, uint16_t offset) + { + return page < fil_system.sys_space->free_limit && + offset >= FIL_PAGE_DATA && offset < srv_page_size - FIL_PAGE_DATA_END && + emplace(uint64_t{page} << 32 | offset).second; + } + + /** Register an inode + @param inode index node information + @retval true in case of successful registeration + @retval false in case of invalid entry or already inserted inode */ + __attribute__((warn_unused_result)) + bool insert_seg(const byte *inode) + { + return insert_inode(mach_read_from_4(inode + 4), + mach_read_from_2(inode + 8)); + } + + __attribute__((warn_unused_result)) + bool find(uint32_t page, uint16_t offset) const + { + return std::unordered_set::find(uint64_t{page} << 32 | + offset) != end(); + } + + /** Get the unused inode segment header from the list of index + node pages. + @param boffset offset for the FSP_SEG_INODES_FULL + or FSP_SEG_INODES_FREE list in fsp header page + @param unused store the unused information + @return error code */ + dberr_t get_unused(uint16_t boffset, inode_info *unused) const + { + dberr_t err= DB_SUCCESS; + buf_block_t *block= buf_pool.page_fix(page_id_t{0, 0}, &err, + buf_pool_t::FIX_WAIT_READ); + if (!block) + return err; + buf_block_t *header= block; + const uint32_t len= flst_get_len(block->page.frame + boffset); + fil_addr_t addr= flst_get_first(block->page.frame + boffset); + ulint n_inode_per_page= + FSP_SEG_INODES_PER_PAGE(fil_system.sys_space->physical_size()); + for (uint32_t i= len; i--; ) + { + if (addr.boffset < FIL_PAGE_DATA || + addr.boffset >= block->physical_size() - FIL_PAGE_DATA_END) + { + err= DB_CORRUPTION; + break; + } + + block= buf_pool.page_fix(page_id_t{0, addr.page}, &err, + buf_pool_t::FIX_WAIT_READ); + if (!block) + break; + + fil_addr_t next_addr= flst_get_next_addr(block->page.frame + + addr.boffset); + for (uint32_t i= 0; i < n_inode_per_page; i++) + { + const fseg_inode_t *inode= + fsp_seg_inode_page_get_nth_inode(block->page.frame, i); + ulint seg_id= mach_read_from_8(FSEG_ID + inode); + /* Consider TRX_SYS_FSEG_HEADER as used segment. + While reinitializing the undo tablespace, InnoDB + fail to reset the value of TRX_SYS_FSEG_HEADER + in TRX_SYS page. so InnoDB shouldn't consider + this segment as unused one */ + if (seg_id == 0 || seg_id == 2) + continue; + uint16_t offset= uint16_t(inode - block->page.frame); + if (offset < FIL_PAGE_DATA || + offset >= block->physical_size() - FIL_PAGE_DATA_END) + { + err= DB_CORRUPTION; + break; + } + + if (!find(addr.page, offset) && + !unused->insert_inode(addr.page, offset)) + { + err= DB_DUPLICATE_KEY; + break; + } + } + addr= next_addr; + block->page.unfix(); + if (err) + break; + } + ut_ad(addr.page == FIL_NULL || err != DB_SUCCESS); + header->page.unfix(); + return err; + } + + /** Free the segment information present in the set + @return error code */ + dberr_t free_segs(); +}; + +/** Get the file segments from root page +@param inodes store the index nodes information +@param root root page +@return error code */ +static dberr_t fsp_table_inodes_root(inode_info *inodes, uint32_t root) +{ + if (root == FIL_NULL) + return DB_SUCCESS; + + dberr_t err= DB_SUCCESS; + buf_block_t *block= buf_pool.page_fix(page_id_t{0, root}, &err, + buf_pool_t::FIX_WAIT_READ); + if (!block) + return err; + + if (!inodes->insert_seg(block->page.frame + PAGE_HEADER + PAGE_BTR_SEG_TOP)) + err= DB_CORRUPTION; + + if (!inodes->insert_seg(block->page.frame + PAGE_HEADER + PAGE_BTR_SEG_LEAF)) + err= DB_CORRUPTION; + + block->page.unfix(); + return err; +} + +/** Add the file segment of all root pages in table +@param inodes store the index nodes information +@param table table to be read +@return error code */ +static dberr_t add_index_root_pages(inode_info *inodes, dict_table_t *table) +{ + dberr_t err= DB_SUCCESS; + for (auto i= UT_LIST_GET_FIRST(table->indexes); + i != nullptr && err == DB_SUCCESS; i= UT_LIST_GET_NEXT(indexes, i)) + err= fsp_table_inodes_root(inodes, i->page); + return err; +} + +/** Determine the inodes used by tables in the system tablespace. +@param inodes store the index nodes information +@param mtr mini-transaction +@return error code */ +static dberr_t fsp_table_inodes(inode_info *inodes, mtr_t *mtr) +{ + btr_pcur_t pcur; + ulint len; + const auto savepoint= mtr->get_savepoint(); + dberr_t err= DB_SUCCESS; + dict_sys.freeze(SRW_LOCK_CALL); + for (const rec_t *rec= dict_startscan_system(&pcur, mtr, + dict_sys.sys_indexes); + rec; rec= dict_getnext_system_low(&pcur, mtr)) + { + const byte *field= + rec_get_nth_field_old(rec, DICT_FLD__SYS_INDEXES__SPACE, &len); + if (len != 4) + { + err= DB_CORRUPTION; + break; + } + uint32_t space= mach_read_from_4(field); + if (space > 0) continue; + + field= rec_get_nth_field_old(rec, DICT_FLD__SYS_INDEXES__PAGE_NO, &len); + if (len != 4) + { + err= DB_CORRUPTION; + break; + } + err= fsp_table_inodes_root(inodes, mach_read_from_4(field)); + if (err) + break; + } + mtr->rollback_to_savepoint(savepoint); + dict_sys.unfreeze(); + + if (err == DB_SUCCESS) + err= add_index_root_pages(inodes, dict_sys.sys_tables); + if (err == DB_SUCCESS) + err= add_index_root_pages(inodes, dict_sys.sys_indexes); + if (err == DB_SUCCESS) + err= add_index_root_pages(inodes, dict_sys.sys_columns); + if (err == DB_SUCCESS) + err= add_index_root_pages(inodes, dict_sys.sys_fields); + return err; +} + +/* Get the used inode from the system tablespace +@param inodes inode information used found in system tablespace +@param mtr mini-transaction +@return error code */ +static dberr_t fsp_get_sys_used_segment(inode_info *inodes, mtr_t *mtr) +{ + dberr_t err= DB_SUCCESS; + buf_block_t *block= nullptr; + /* Get TRX_SYS_FSEG_HEADER, TRX_SYS_DOUBLEWRITE_FSEG from + TRX_SYS_PAGE */ + block= buf_pool.page_fix(page_id_t{0, TRX_SYS_PAGE_NO}, &err, + buf_pool_t::FIX_WAIT_READ); + if (!block) + return err; + + fil_addr_t sys_fseg_addr= flst_read_addr(block->page.frame + + TRX_SYS + TRX_SYS_FSEG_HEADER + 4); + if (sys_fseg_addr.page == 0 && sys_fseg_addr.boffset == 0) + { + /* While reinitializing the undo tablespace, InnoDB fail + to reset the TRX_SYS_FSEG_HEADER offset in TRX_SYS page */ + } + else if (!inodes->insert_inode(sys_fseg_addr.page, sys_fseg_addr.boffset)) + err= DB_CORRUPTION; + + if (!inodes->insert_seg(block->page.frame + TRX_SYS_DOUBLEWRITE + + TRX_SYS_DOUBLEWRITE_FSEG)) + err= DB_CORRUPTION; + + block->page.unfix(); + + if (err) + return err; + + block= buf_pool.page_fix(page_id_t{0, DICT_HDR_PAGE_NO}, &err, + buf_pool_t::FIX_WAIT_READ); + if (!block) + return err; + + if (!inodes->insert_seg(block->page.frame + DICT_HDR + DICT_HDR_FSEG_HEADER)) + err= DB_CORRUPTION; + + block->page.unfix(); + + if (err) + return err; + + block= buf_pool.page_fix(page_id_t{0, FSP_IBUF_HEADER_PAGE_NO}, + &err, buf_pool_t::FIX_WAIT_READ); + if (!block) + return err; + if (!inodes->insert_seg(block->page.frame + PAGE_DATA)) + err= DB_CORRUPTION; + + block->page.unfix(); + + /* Get rollback segment header page */ + for (ulint rseg_id= 0; rseg_id < TRX_SYS_N_RSEGS && err == DB_SUCCESS; + rseg_id++) + { + trx_rseg_t *rseg= &trx_sys.rseg_array[rseg_id]; + if (rseg->space->id == 0) + { + block= buf_pool.page_fix(rseg->page_id(), &err, + buf_pool_t::FIX_WAIT_READ); + if (!block) + break; + if (!inodes->insert_seg(block->page.frame + TRX_RSEG + + TRX_RSEG_FSEG_HEADER)) + err= DB_CORRUPTION; + block->page.unfix(); + + /* Even after slow shutdown, there is a possiblity that + cached undo log can exist. So store the segment as used one */ + for (trx_undo_t *undo= UT_LIST_GET_FIRST(rseg->undo_cached); + undo && err == DB_SUCCESS; + undo= UT_LIST_GET_NEXT(undo_list, undo)) + { + block= buf_pool.page_fix(page_id_t{0, undo->hdr_page_no}, &err, + buf_pool_t::FIX_WAIT_READ); + if (!block) + return err; + if (!inodes->insert_seg(block->page.frame + TRX_UNDO_SEG_HDR + + TRX_UNDO_FSEG_HEADER)) + err= DB_CORRUPTION; + block->page.unfix(); + } + } + } + + if (err == DB_SUCCESS) + err= fsp_table_inodes(inodes, mtr); + return err; +} + +/** Free the extents, fragment page from the given inode +@param page_no index node page number +@param offset index node offset within page +@return error code */ +static dberr_t fseg_inode_free(uint32_t page_no, uint16_t offset) +{ + fil_space_t *space= fil_system.sys_space; + dberr_t err= DB_SUCCESS; + mtr_t mtr; + mtr.start(); + mtr.x_lock_space(space); + buf_block_t *iblock= buf_page_get_gen(page_id_t{0, page_no}, 0, + RW_X_LATCH, nullptr, BUF_GET, + &mtr, &err); + + fseg_inode_t *inode= nullptr; + DBUG_EXECUTE_IF("unused_undo_free_fail_4", + iblock= nullptr; err= DB_CORRUPTION;); + if (!iblock) + goto func_exit; + + inode= iblock->page.frame + offset; + while ((err= fseg_free_step_low(space, inode, iblock, + &mtr, nullptr)) == DB_SUCCESS_LOCKED_REC) + { + DBUG_EXECUTE_IF("unused_undo_free_fail_5", + err= DB_CORRUPTION; + goto func_exit;); + iblock->fix(); + mtr.commit(); + + mtr.start(); + mtr.x_lock_space(space); + iblock->page.lock.x_lock(); + mtr.memo_push(iblock, MTR_MEMO_PAGE_X_FIX); + } + /* These are all leaked undo log segments. That means there is no + way to access these undo log segments other than traversing + the index node page. Above fseg_free_step_low() clears + the undo segment header page as well. */ +func_exit: + mtr.commit(); + return err; +} + +/** Free the unused segment +@return error code */ +dberr_t inode_info::free_segs() +{ + for (auto i : *this) + { + uint32_t page= uint32_t(i >> 32); + uint16_t offset= uint16_t(i); + if (dberr_t err= fseg_inode_free(page, offset)) + { + sql_print_error("InnoDB: :autoshrink failed to free the " + "segment %u in page " UINT32PF, unsigned{offset}, + page); + return err; + } + sql_print_information("InnoDB: :autoshrink freed the segment " + "%u in page " UINT32PF, unsigned{offset}, page); + } + return DB_SUCCESS; +} + +bool trx_sys_t::is_xa_exist() noexcept +{ + for (const trx_rseg_t &rseg : trx_sys.rseg_array) + { + if (rseg.page_no == FIL_NULL) + continue; + const trx_undo_t *undo= UT_LIST_GET_FIRST(rseg.undo_list); + while (undo) + { + if (undo->state == TRX_UNDO_PREPARED) + return true; + undo= UT_LIST_GET_NEXT(undo_list, undo); + } + } + return false; +} + +/** Remove the unused segment in tablespace. This function +used only during shrinking of system tablespace +@param shutdown called during slow shutdown +@return error code */ +dberr_t fil_space_t::garbage_collect(bool shutdown) +{ + if ((shutdown && trx_sys_t::is_xa_exist()) || + (!shutdown && !trx_sys.is_undo_empty())) + { + sql_print_warning("InnoDB: Cannot free the unused segments" + " in system tablespace because a previous" + " shutdown was not with innodb_fast_shutdown=0" + " or XA PREPARE transactions exist"); + return DB_SUCCESS; + } + + ut_a(id == 0); + /* Collect all the used segment inode entries */ + mtr_t mtr; + mtr.start(); + inode_info used_inodes, unused_inodes; + dberr_t err= fsp_get_sys_used_segment(&used_inodes, &mtr); + DBUG_EXECUTE_IF("unused_undo_free_fail_1", err= DB_CORRUPTION;); + if (err) + { + sql_print_error("InnoDB: :autoshrink failed to read the " + "used segment due to %s", ut_strerr(err)); + mtr.commit(); + return err; + } + + const char *ctx= "in FSP_SEG_INODES_FULL list"; + err= used_inodes.get_unused(FSP_HEADER_OFFSET + FSP_SEG_INODES_FULL, + &unused_inodes); + DBUG_EXECUTE_IF("unused_undo_free_fail_2", err= DB_CORRUPTION;); + + if (err == DB_SUCCESS) + { + ctx= "in FSP_SEG_INODES_FREE list"; + err= used_inodes.get_unused(FSP_HEADER_OFFSET + FSP_SEG_INODES_FREE, + &unused_inodes); + DBUG_EXECUTE_IF("unused_undo_free_fail_3", err= DB_CORRUPTION;); + } + + mtr.commit(); + if (err) + { + sql_print_error("InnoDB: :autoshrink failed due to " + "%s %s ", ut_strerr(err), ctx); + return err; + } + + return unused_inodes.free_segs(); +} + +void fsp_system_tablespace_truncate(bool shutdown) { ut_ad(!purge_sys.enabled()); ut_ad(!srv_undo_sources); uint32_t last_used_extent= 0; fil_space_t *space= fil_system.sys_space; + dberr_t err= space->garbage_collect(shutdown); + if (err) + { + srv_sys_space.set_shrink_fail(); + return; + } + mtr_t mtr; mtr.start(); mtr.x_lock_space(space); - dberr_t err= fsp_traverse_extents(space, &last_used_extent, &mtr); + err= fsp_traverse_extents(space, &last_used_extent, &mtr); + DBUG_EXECUTE_IF("traversal_extent_fail", err= DB_CORRUPTION;); if (err != DB_SUCCESS) { -func_exit: +err_exit: + mtr.commit(); sql_print_warning("InnoDB: Cannot shrink the system tablespace " "due to %s", ut_strerr(err)); - mtr.commit(); + srv_sys_space.set_shrink_fail(); return; } uint32_t fixed_size= srv_sys_space.get_min_size(), @@ -3643,7 +4078,7 @@ func_exit: header= fsp_get_latched_xdes_page(0, &mtr, &err); if (!header) - goto func_exit; + goto err_exit; mtr.write<4, mtr_t::FORCED>( *header, FSP_HEADER_OFFSET + FSP_SIZE + header->page.frame, @@ -3656,16 +4091,16 @@ func_exit: err= fsp_shrink_list( header, FSP_HEADER_OFFSET + FSP_FREE, last_used_extent, &mtr); if (err != DB_SUCCESS) - goto func_exit; + goto err_exit; err= fsp_shrink_list( header, FSP_HEADER_OFFSET + FSP_FREE_FRAG, last_used_extent, &mtr); if (err != DB_SUCCESS) - goto func_exit; + goto err_exit; err= fsp_xdes_reset(space, last_used_extent, &mtr); if (err != DB_SUCCESS) - goto func_exit; + goto err_exit; mtr.trim_pages(page_id_t(0, last_used_extent)); size_t shrink_redo_size= mtr.get_log_size(); diff --git a/storage/innobase/include/dict0load.h b/storage/innobase/include/dict0load.h index c774a792f0b..69ccd3f816c 100644 --- a/storage/innobase/include/dict0load.h +++ b/storage/innobase/include/dict0load.h @@ -213,4 +213,12 @@ dict_process_sys_foreign_col_rec( in referenced table */ ulint* pos); /*!< out: column position */ +/** This function gets the next system table record as it scans +the table. +@param pcur persistent cursor +@param mtr mini-transaction +@return the next record if found +@retval nullptr at the end of the table */ +const rec_t* +dict_getnext_system_low(btr_pcur_t *pcur, mtr_t *mtr); #endif diff --git a/storage/innobase/include/fil0fil.h b/storage/innobase/include/fil0fil.h index 950aa3c5256..358297f3f23 100644 --- a/storage/innobase/include/fil0fil.h +++ b/storage/innobase/include/fil0fil.h @@ -988,6 +988,10 @@ public: /** Update the data structures on write completion */ void complete_write(); + /** Free the unused segment for the tablespace + @param shutdown called during slow shutdown + @return error code */ + dberr_t garbage_collect(bool shutdown); private: /** @return whether the file is usable for io() */ ATTRIBUTE_COLD bool prepare_acquired(); diff --git a/storage/innobase/include/fsp0fsp.h b/storage/innobase/include/fsp0fsp.h index 4e7b8b8c8de..5a088a1685f 100644 --- a/storage/innobase/include/fsp0fsp.h +++ b/storage/innobase/include/fsp0fsp.h @@ -555,8 +555,9 @@ inline void fsp_init_file_page( mtr->init(block); } -/** Truncate the system tablespace */ -void fsp_system_tablespace_truncate(); +/** Truncate the system tablespace +@param shutdown Called during shutdown */ +void fsp_system_tablespace_truncate(bool shutdown); #ifndef UNIV_DEBUG # define fsp_init_file_page(space, block, mtr) fsp_init_file_page(block, mtr) diff --git a/storage/innobase/include/fsp0sysspace.h b/storage/innobase/include/fsp0sysspace.h index 3ff0e864182..cace0c087c3 100644 --- a/storage/innobase/include/fsp0sysspace.h +++ b/storage/innobase/include/fsp0sysspace.h @@ -174,6 +174,11 @@ public: ulint* sum_new_sizes) MY_ATTRIBUTE((warn_unused_result)); + /** @return whether shrinking failed during + previous attempt of system tablespace shrinking */ + bool is_shrink_fail() noexcept { return m_auto_shrink_fail; } + + void set_shrink_fail() noexcept { m_auto_shrink_fail= true; } private: /** Check the tablespace header for this tablespace. @return DB_SUCCESS or error code */ @@ -271,6 +276,10 @@ private: /** Shrink the system tablespace if the value is enabled */ bool m_auto_shrink; + + /** Set to true only when InnoDB system tablespace + shrink fails during startup */ + bool m_auto_shrink_fail; }; /* GLOBAL OBJECTS */ diff --git a/storage/innobase/include/trx0sys.h b/storage/innobase/include/trx0sys.h index b4a3decd1c5..81a8583b07e 100644 --- a/storage/innobase/include/trx0sys.h +++ b/storage/innobase/include/trx0sys.h @@ -1202,6 +1202,9 @@ public: /** Get the undo log empty value */ bool is_undo_empty() const { return !undo_log_nonempty; } + /** @return whether XA transaction is in PREPARED state */ + static bool is_xa_exist() noexcept; + /* Reset the trx_sys page and retain the dblwr information, system rollback segment header page @return error code */ diff --git a/storage/innobase/srv/srv0srv.cc b/storage/innobase/srv/srv0srv.cc index db5dc5a4a3d..08f87ff29db 100644 --- a/storage/innobase/srv/srv0srv.cc +++ b/storage/innobase/srv/srv0srv.cc @@ -1578,7 +1578,8 @@ void srv_purge_shutdown() purge_sys.coordinator_shutdown(); srv_shutdown_purge_tasks(); if (!srv_fast_shutdown && !high_level_read_only && srv_was_started && - !opt_bootstrap && srv_operation == SRV_OPERATION_NORMAL) - fsp_system_tablespace_truncate(); + !opt_bootstrap && srv_operation == SRV_OPERATION_NORMAL && + !srv_sys_space.is_shrink_fail()) + fsp_system_tablespace_truncate(true); } } diff --git a/storage/innobase/srv/srv0start.cc b/storage/innobase/srv/srv0start.cc index 08c3dc9f30b..fe89cdb7b55 100644 --- a/storage/innobase/srv/srv0start.cc +++ b/storage/innobase/srv/srv0start.cc @@ -333,6 +333,15 @@ inline dberr_t trx_sys_t::reset_page(mtr_t *mtr) if (!sys_header) return err; + if (mach_read_from_4(sys_header->page.frame + TRX_SYS + + TRX_SYS_FSEG_HEADER) != TRX_SYS_SPACE) + return DB_CORRUPTION; + + /* Store the TRX_SYS_FSEG_HEADER page, offset */ + char fseg_addr[6]; + memcpy(fseg_addr, + sys_header->page.frame + TRX_SYS + TRX_SYS_FSEG_HEADER + 4, 6); + const bool dblwr_enabled= mach_read_from_4(TRX_SYS_DOUBLEWRITE_MAGIC + TRX_SYS_DOUBLEWRITE + sys_header->page.frame) @@ -347,6 +356,17 @@ inline dberr_t trx_sys_t::reset_page(mtr_t *mtr) mtr->write<2>(*sys_header, FIL_PAGE_TYPE + sys_header->page.frame, FIL_PAGE_TYPE_TRX_SYS); + DBUG_EXECUTE_IF("sys_fseg_header_fail", + { + fseg_addr[4]= 0; + fseg_addr[5]= 0; + }); + + /** Write the TRX_SYS_FSEG_HEADER only if it is not zero-filled */ + if (!memcmp(fseg_addr + 4, "00", 2)) + mtr->memcpy(*sys_header, + sys_header->page.frame + TRX_SYS + TRX_SYS_FSEG_HEADER + 4, + fseg_addr, 6); mtr->write<4>(*sys_header, TRX_SYS + TRX_SYS_RSEGS + TRX_SYS_RSEG_PAGE_NO + sys_header->page.frame, FSP_FIRST_RSEG_PAGE_NO); @@ -1781,7 +1801,7 @@ dberr_t srv_start(bool create_new_db) if (!high_level_read_only && srv_sys_space.can_auto_shrink()) { - fsp_system_tablespace_truncate(); + fsp_system_tablespace_truncate(false); DBUG_EXECUTE_IF("crash_after_sys_truncate", return srv_init_abort(DB_ERROR);); } diff --git a/storage/innobase/trx/trx0purge.cc b/storage/innobase/trx/trx0purge.cc index f53e76f9f72..d543468d465 100644 --- a/storage/innobase/trx/trx0purge.cc +++ b/storage/innobase/trx/trx0purge.cc @@ -482,6 +482,7 @@ loop: free_segment: ut_ad(rseg.curr_size >= seg_size); rseg.curr_size-= seg_size; + DBUG_EXECUTE_IF("undo_segment_leak", goto skip_purge_free;); trx_purge_free_segment(rseg_hdr, b, mtr); break; case TRX_UNDO_CACHED: @@ -509,7 +510,9 @@ loop: goto free_segment; } } - +#ifndef DBUG_OFF +skip_purge_free: +#endif /* !DBUG_OFF */ hdr_addr= prev_hdr_addr; mtr.commit();