MDEV-36487 Fix ha_innobase::check() for sequences

InnoDB does the following check for sequence table during check
table command:
- There should be only one index should exist on sequence table
- There should be only one row should exist on sequence table
- The leaf page must be the root page for the sequence table
- Delete marked record should not exist
- DB_TRX_ID and DB_ROLL_PTR of the record should be 0 and 1U << 55
This commit is contained in:
Thirunarayanan Balathandayuthapani 2025-06-06 18:42:30 +05:30
parent 37274ae01f
commit 6a2afb42ba
6 changed files with 395 additions and 3 deletions

View File

@ -0,0 +1,17 @@
--- check_sequence.result
+++ check_sequence,debug.result
@@ -112,3 +112,14 @@
3
disconnect prevent_purge;
DROP SEQUENCE s1;
+CREATE SEQUENCE s ENGINE=InnoDB;
+ALTER TABLE s SEQUENCE=0;
+FLUSH TABLES;
+SET STATEMENT DEBUG_DBUG="+d,fail_root_page" FOR
+CHECK TABLE s;
+Table Op Msg_type Msg_text
+test.s check Warning InnoDB: Sequence table test/s is corrupted.
+test.s check error Corrupt
+ALTER TABLE s SEQUENCE=1;
+ERROR HY000: InnoDB: Table `test`.`s` is corrupted.
+DROP SEQUENCE s;

View File

@ -0,0 +1,114 @@
#
# MDEV-36487 Fix ha_innobase::check() for sequences
#
call mtr.add_suppression("InnoDB: Table test/s2 contains 1 indexes .*");
call mtr.add_suppression("Table test/s2 has a primary key in InnoDB .*");
CREATE SEQUENCE s ENGINE=InnoDB;
ALTER TABLE s SEQUENCE=0, ALGORITHM=INPLACE;
ERROR 0A000: ALGORITHM=INPLACE is not supported. Reason: SEQUENCE. Try ALGORITHM=COPY
ALTER TABLE s SEQUENCE=0, ALGORITHM=COPY;
FLUSH TABLES;
CHECK TABLE s;
Table Op Msg_type Msg_text
test.s check Warning InnoDB: Sequence table test/s has ROLLBACK enabled.
test.s check error Corrupt
ALTER TABLE s SEQUENCE=1;
ERROR HY000: InnoDB: Table `test`.`s` is corrupted.
DROP SEQUENCE s;
CREATE SEQUENCE s ENGINE=InnoDB;
CREATE TABLE s2 LIKE s;
ALTER TABLE s2 sequence=0;
INSERT INTO s2 VALUES (3,1,9223372036854775806,1,1,1000,0,0);
ALTER TABLE s2 ADD INDEX idx(start_value);
FLUSH TABLES;
CHECK TABLE s2;
Table Op Msg_type Msg_text
test.s2 check Warning InnoDB: Table test/s2 contains 1 indexes inside InnoDB, which is different from the number of indexes 0 defined in the MariaDB
test.s2 check Warning InnoDB: Sequence table test/s2 does have more than one indexes.
test.s2 check error Corrupt
ALTER TABLE s2 SEQUENCE=1;
ERROR HY000: InnoDB: Table `test`.`s2` is corrupted.
DROP SEQUENCE s;
DROP SEQUENCE s2;
CREATE SEQUENCE s ENGINE=InnoDB;
CREATE TABLE s2 LIKE s;
ALTER TABLE s2 sequence=0;
INSERT INTO s2 VALUES (3,2,9223372036854775806,2,2,1000,0,0);
ALTER TABLE s2 ADD PRIMARY KEY(start_value);
FLUSH TABLES;
CHECK TABLE s2;
Table Op Msg_type Msg_text
test.s2 check Warning InnoDB: Table test/s2 has a primary key in InnoDB data dictionary, but not in MariaDB!
test.s2 check Warning InnoDB: Table test/s2 contains 1 indexes inside InnoDB, which is different from the number of indexes 0 defined in the MariaDB
test.s2 check Warning InnoDB: Sequence table test/s2 does not have generated clustered index.
test.s2 check error Corrupt
ALTER TABLE s2 SEQUENCE=1;
ERROR HY000: InnoDB: Table `test`.`s2` is corrupted.
DROP SEQUENCE s;
DROP SEQUENCE s2;
CREATE SEQUENCE s ENGINE=InnoDB;
CREATE TABLE s2 LIKE s;
ALTER TABLE s2 sequence=0;
INSERT INTO s2 VALUES (3,1,9223372036854775806,1,1,1000,0,0);
DELETE FROM s2;
InnoDB 0 transactions not purged
FLUSH TABLES;
CHECK TABLE s2;
Table Op Msg_type Msg_text
test.s2 check Warning InnoDB: Should have only one record in sequence table test/s2. But it has 0 records.
test.s2 check error Corrupt
ALTER TABLE s2 SEQUENCE=1;
ERROR HY000: InnoDB: Table `test`.`s2` is corrupted.
DROP SEQUENCE s;
DROP SEQUENCE s2;
CREATE SEQUENCE s ENGINE=InnoDB;
CREATE TABLE s2 LIKE s;
ALTER TABLE s2 sequence=0;
INSERT INTO s2 select seq, seq, seq, seq, seq, seq, 1, seq from
seq_1_to_200;
FLUSH TABLES;
CHECK TABLE s2;
Table Op Msg_type Msg_text
test.s2 check Warning InnoDB: Non leaf page exists for sequence table test/s2.
test.s2 check error Corrupt
ALTER TABLE s2 SEQUENCE=1;
ERROR HY000: InnoDB: Table `test`.`s2` is corrupted.
DROP SEQUENCE s;
DROP SEQUENCE s2;
CREATE SEQUENCE s ENGINE=InnoDB;
CREATE TABLE s2 LIKE s;
ALTER TABLE s2 sequence=0;
DELETE FROM s2;
InnoDB 0 transactions not purged
connect prevent_purge,localhost,root;
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection default;
INSERT INTO s2 VALUES (3,1,9223372036854775806,1,1,1000,0,0);
FLUSH TABLES;
CHECK TABLE s2;
Table Op Msg_type Msg_text
test.s2 check Warning InnoDB: Record in sequence table test/s2 is corrupted.
test.s2 check error Corrupt
ALTER TABLE s2 SEQUENCE=1;
ERROR HY000: InnoDB: Table `test`.`s2` is corrupted.
DROP SEQUENCE s;
DROP SEQUENCE s2;
CREATE SEQUENCE s1 ENGINE=InnoDB;
CHECK TABLE s1;
Table Op Msg_type Msg_text
test.s1 check status OK
connection prevent_purge;
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection default;
INSERT INTO s1 VALUES (3,1,9223372036854775806,1,1,1000,0,0);
SELECT * FROM s1;
next_not_cached_value minimum_value maximum_value start_value increment cache_size cycle_option cycle_count
3 1 9223372036854775806 1 1 1000 0 0
CHECK TABLE s1;
Table Op Msg_type Msg_text
test.s1 check status OK
select nextval(s1);
nextval(s1)
3
disconnect prevent_purge;
DROP SEQUENCE s1;

View File

@ -0,0 +1,136 @@
--source include/have_innodb.inc
--source include/have_sequence.inc
--source include/maybe_debug.inc
--echo #
--echo # MDEV-36487 Fix ha_innobase::check() for sequences
--echo #
call mtr.add_suppression("InnoDB: Table test/s2 contains 1 indexes .*");
call mtr.add_suppression("Table test/s2 has a primary key in InnoDB .*");
# Sequence table which has NO_ROLLBACK flag set
let $datadir=`select @@datadir`;
CREATE SEQUENCE s ENGINE=InnoDB;
copy_file $datadir/test/s.frm $datadir/test/s1.frm;
--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
ALTER TABLE s SEQUENCE=0, ALGORITHM=INPLACE;
ALTER TABLE s SEQUENCE=0, ALGORITHM=COPY;
FLUSH TABLES;
remove_file $datadir/test/s.frm;
move_file $datadir/test/s1.frm $datadir/test/s.frm;
CHECK TABLE s;
--error ER_TABLE_CORRUPT
ALTER TABLE s SEQUENCE=1;
DROP SEQUENCE s;
# Checks for more than one index
CREATE SEQUENCE s ENGINE=InnoDB;
copy_file $datadir/test/s.frm $datadir/test/orig.frm;
CREATE TABLE s2 LIKE s;
ALTER TABLE s2 sequence=0;
INSERT INTO s2 VALUES (3,1,9223372036854775806,1,1,1000,0,0);
ALTER TABLE s2 ADD INDEX idx(start_value);
FLUSH TABLES;
move_file $datadir/test/orig.frm $datadir/test/s2.frm;
CHECK TABLE s2;
--error ER_TABLE_CORRUPT
ALTER TABLE s2 SEQUENCE=1;
DROP SEQUENCE s;
DROP SEQUENCE s2;
# Checks for generated clustered index
CREATE SEQUENCE s ENGINE=InnoDB;
copy_file $datadir/test/s.frm $datadir/test/orig.frm;
CREATE TABLE s2 LIKE s;
ALTER TABLE s2 sequence=0;
INSERT INTO s2 VALUES (3,2,9223372036854775806,2,2,1000,0,0);
ALTER TABLE s2 ADD PRIMARY KEY(start_value);
FLUSH TABLES;
move_file $datadir/test/orig.frm $datadir/test/s2.frm;
CHECK TABLE s2;
--error ER_TABLE_CORRUPT
ALTER TABLE s2 SEQUENCE=1;
DROP SEQUENCE s;
DROP SEQUENCE s2;
# Should contain only one record
CREATE SEQUENCE s ENGINE=InnoDB;
copy_file $datadir/test/s.frm $datadir/test/orig.frm;
CREATE TABLE s2 LIKE s;
ALTER TABLE s2 sequence=0;
INSERT INTO s2 VALUES (3,1,9223372036854775806,1,1,1000,0,0);
DELETE FROM s2;
--source include/wait_all_purged.inc
FLUSH TABLES;
move_file $datadir/test/orig.frm $datadir/test/s2.frm;
CHECK TABLE s2;
--error ER_TABLE_CORRUPT
ALTER TABLE s2 SEQUENCE=1;
DROP SEQUENCE s;
DROP SEQUENCE s2;
# More than one page
CREATE SEQUENCE s ENGINE=InnoDB;
copy_file $datadir/test/s.frm $datadir/test/orig.frm;
CREATE TABLE s2 LIKE s;
ALTER TABLE s2 sequence=0;
INSERT INTO s2 select seq, seq, seq, seq, seq, seq, 1, seq from
seq_1_to_200;
FLUSH TABLES;
move_file $datadir/test/orig.frm $datadir/test/s2.frm;
CHECK TABLE s2;
--error ER_TABLE_CORRUPT
ALTER TABLE s2 SEQUENCE=1;
DROP SEQUENCE s;
DROP SEQUENCE s2;
# Checks for DB_TRX_ID & DB_ROLL_PTR in the record
CREATE SEQUENCE s ENGINE=InnoDB;
copy_file $datadir/test/s.frm $datadir/test/orig.frm;
CREATE TABLE s2 LIKE s;
ALTER TABLE s2 sequence=0;
DELETE FROM s2;
--source include/wait_all_purged.inc
--connect (prevent_purge,localhost,root)
START TRANSACTION WITH CONSISTENT SNAPSHOT;
--connection default
INSERT INTO s2 VALUES (3,1,9223372036854775806,1,1,1000,0,0);
FLUSH TABLES;
move_file $datadir/test/orig.frm $datadir/test/s2.frm;
CHECK TABLE s2;
--error ER_TABLE_CORRUPT
ALTER TABLE s2 SEQUENCE=1;
DROP SEQUENCE s;
DROP SEQUENCE s2;
# Insert a row into a sequence table updates that row
CREATE SEQUENCE s1 ENGINE=InnoDB;
CHECK TABLE s1;
--connection prevent_purge
START TRANSACTION WITH CONSISTENT SNAPSHOT;
--connection default
INSERT INTO s1 VALUES (3,1,9223372036854775806,1,1,1000,0,0);
SELECT * FROM s1;
CHECK TABLE s1;
--disable_ps2_protocol
select nextval(s1);
--enable_ps2_protocol
--disconnect prevent_purge
DROP SEQUENCE s1;
if ($have_debug)
{
# Root page is corrupted
CREATE SEQUENCE s ENGINE=InnoDB;
copy_file $datadir/test/s.frm $datadir/test/s1.frm;
ALTER TABLE s SEQUENCE=0;
FLUSH TABLES;
remove_file $datadir/test/s.frm;
move_file $datadir/test/s1.frm $datadir/test/s.frm;
SET STATEMENT DEBUG_DBUG="+d,fail_root_page" FOR
CHECK TABLE s;
--error ER_TABLE_CORRUPT
ALTER TABLE s SEQUENCE=1;
DROP SEQUENCE s;
}

View File

@ -120,12 +120,16 @@ drop sequence s;
#
CREATE SEQUENCE s engine=innodb;
ALTER TABLE s sequence=0;
connect prevent_purge,localhost,root;
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection default;
delete from s;
FLUSH TABLES;
CHECK TABLE s;
Table Op Msg_type Msg_text
test.s check Error Fewer than one row in the table
test.s check Warning InnoDB: Encountered delete marked record in sequence table test/s.
test.s check error Corrupt
disconnect prevent_purge;
DROP SEQUENCE s;
CREATE SEQUENCE s engine=innodb;
CHECK TABLE s;
@ -138,6 +142,6 @@ insert into s values (2,1,9223372036854775806,1,1,1000,0,0);
FLUSH TABLES;
CHECK TABLE s;
Table Op Msg_type Msg_text
test.s check Warning More than one row in the table
test.s check status OK
test.s check Warning InnoDB: Should have only one record in sequence table test/s. But it has 2 records.
test.s check error Corrupt
DROP SEQUENCE s;

View File

@ -147,11 +147,16 @@ let $datadir=`select @@datadir`;
CREATE SEQUENCE s engine=innodb;
copy_file $datadir/test/s.frm $datadir/test/s1.frm;
ALTER TABLE s sequence=0;
--connect (prevent_purge,localhost,root)
START TRANSACTION WITH CONSISTENT SNAPSHOT;
--connection default
delete from s;
FLUSH TABLES;
remove_file $datadir/test/s.frm;
move_file $datadir/test/s1.frm $datadir/test/s.frm;
CHECK TABLE s;
--disconnect prevent_purge
DROP SEQUENCE s;
# Just one row, check ok

View File

@ -15197,6 +15197,117 @@ ha_innobase::optimize(
return try_alter ? HA_ADMIN_TRY_ALTER : HA_ADMIN_OK;
}
/** Does the following validation check for the sequence table
1) Check whether the InnoDB table has no_rollback flags
2) Should have only one primary index
3) Root index page must be leaf page
4) There should be only one record in leaf page
5) There shouldn't be delete marked record in leaf page
6) DB_TRX_ID, DB_ROLL_PTR in the record should be 0 and 1U << 55
@param thd Thread
@param table InnoDB table
@retval true if validation succeeds or false if validation fails */
static bool innobase_sequence_table_check(THD *thd, dict_table_t *table)
{
fil_space_t *space= table->space;
dict_index_t *clust_index= dict_table_get_first_index(table);
mtr_t mtr;
bool corruption= false;
const rec_t *rec= nullptr;
buf_block_t *root_block= nullptr;
if (UT_LIST_GET_LEN(table->indexes) != 1)
{
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NOT_KEYFILE,
"InnoDB: Sequence table %s does have more than one "
"indexes.", table->name.m_name);
corruption= true;
goto func_exit;
}
if (!clust_index->is_gen_clust())
{
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NOT_KEYFILE,
"InnoDB: Sequence table %s does not have generated "
"clustered index.", table->name.m_name);
corruption= true;
goto func_exit;
}
mtr.start();
mtr.set_named_space(space);
root_block= buf_page_get_gen(page_id_t(space->id, clust_index->page),
space->zip_size(), RW_S_LATCH, nullptr, BUF_GET,
&mtr);
DBUG_EXECUTE_IF("fail_root_page", root_block= nullptr;);
if (!root_block)
{
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NOT_KEYFILE,
"InnoDB: Sequence table %s is corrupted.",
table->name.m_name);
corruption= true;
goto err_exit;
}
if (!page_is_leaf(root_block->page.frame))
{
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NOT_KEYFILE,
"InnoDB: Non leaf page exists for sequence table %s.",
table->name.m_name);
corruption= true;
goto err_exit;
}
if (page_get_n_recs(root_block->page.frame) != 1)
{
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NOT_KEYFILE,
"InnoDB: Should have only one record in sequence "
"table %s. But it has %u records.", table->name.m_name,
page_get_n_recs(root_block->page.frame));
corruption= true;
goto err_exit;
}
rec= page_rec_get_next(page_get_infimum_rec(root_block->page.frame));
if (rec_get_deleted_flag(rec, dict_table_is_comp(table)))
{
corruption= true;
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NOT_KEYFILE,
"InnoDB: Encountered delete marked record in sequence "
"table %s.", table->name.m_name);
goto err_exit;
}
if (trx_read_trx_id(rec + clust_index->trx_id_offset) != 0 ||
trx_read_roll_ptr(rec + clust_index->trx_id_offset + DATA_TRX_ID_LEN) !=
roll_ptr_t{1} << ROLL_PTR_INSERT_FLAG_POS)
{
corruption= true;
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NOT_KEYFILE,
"InnoDB: Record in sequence table %s is corrupted.",
table->name.m_name);
goto err_exit;
}
if (!table->no_rollback())
{
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NOT_KEYFILE,
"InnoDB: Sequence table %s has ROLLBACK enabled.",
table->name.m_name);
corruption= true;
}
err_exit:
mtr.commit();
func_exit:
if (corruption)
{
dict_set_corrupted(clust_index, "Table corruption");
return false;
}
return true;
}
/*******************************************************************//**
Tries to check that an InnoDB table is not corrupted. If corruption is
noticed, prints to stderr information about it. In case of corruption
@ -15265,6 +15376,11 @@ ha_innobase::check(
table->s->table_name.str);
DBUG_RETURN(HA_ADMIN_CORRUPT);
} else if (table->s->table_type == TABLE_TYPE_SEQUENCE) {
DBUG_RETURN(
innobase_sequence_table_check(thd, m_prebuilt->table)
? HA_ADMIN_OK
: HA_ADMIN_CORRUPT);
}
m_prebuilt->trx->op_info = "checking table";