mirror of
https://github.com/MariaDB/server.git
synced 2025-11-06 13:10:12 +03:00
This also fixes MDEV-20198: Instant ALTER TABLE is not crash safe InnoDB dictionary recovery wrongly used the READ UNCOMMITTED isolation level, causing some mismatch. For example, if a table was renamed or replaced in a transaction, according to READ UNCOMMITTED the table might not exist at all. We implement READ COMMITTED isolation level for accessing the dictionary tables SYS_TABLES, SYS_COLUMNS, SYS_INDEXES, SYS_FIELDS, SYS_VIRTUAL, SYS_FOREIGN, SYS_FOREIGN_COLS. For most of these tables, no secondary index exists. For the secondary indexes (on SYS_TABLES.ID, SYS_FOREIGN.FOR_NAME, SYS_FOREIGN.REF_NAME), we will always look up the primary key in the clustered index and check if the record actually is a committed version. dict_check_sys_tables(): Recover tablespaces also from delete-marked committed records, so that if a matching .ibd file exists, it will be removed by fil_delete_tablespace() when the committed delete-marked SYS_INDEXES record of the clustered index is purged in row_purge_remove_clust_if_poss_low(). fil_ibd_open(): Change the Boolean parameter "validate" to a ternary one, to suppress error messages when the file might not exist. It is possible that a .ibd file was deleted and the server shut down before the SYS_INDEXES and SYS_TABLES records were purged. Hence, if dict_check_sys_tables() finds a committed delete-marked record, we must not complain if the tablespace file is not found. On Windows, we msut treat ERROR_PATH_NOT_FOUND (directory not found) in the same way as ERROR_FILE_NOT_FOUND. This fixes a few failures where a previous test successfully executed DROP DATABASE (and deleted all files and the directory), but a committed delete-marked SYS_TABLES record had not been purged before server restart. dict_getnext_system_low(): Do not filter out delete-marked records. dict_startscan_system(), dict_getnext_system(): Do filter out delete-marked records, for accessing the INFORMATION_SCHEMA tables. dict_sys_tables_rec_read(): Return the DB_TRX_ID of the committed version of the record. This is needed in dict_load_table_low(). dict_load_foreign_cols(), dict_load_foreign(): Add a parameter for the current transaction identifier. In some DDL operations, the FOREIGN KEY constraints are being loaded from the data dictionary before the DDL transaction has been committed. For SYS_FOREIGN and SYS_FOREIGN_COLS, we must implement the special case of READ COMMITTED that the changes of the uncommitted current transaction are visible. dict_load_foreign(): Validate the table name. We could find a SYS_FOREIGN.ID via a committed delete-marked secondary index record that does not match the REF_NAME or FOR_NAME of the secondary index record. dict_load_index_low(): Optionally take the table as a parameter, so that table->def_trx_id can be updated in case of a committed delete-marked SYS_INDEXES record corresponding to DROP INDEX, but not corresponding to an index stub of ADD INDEX. dict_load_indexes(): Do not update table->def_trx_id in case of delete-marked records. rec_is_metadata(), rec_offs_make_valid(), rec_get_offsets_func(), row_build_low(): Relax some assertions. We may now have !index->is_instant() even if a metadata record is present in the index. Previously, the recovery of instant ADD/DROP COLUMN assumed that READ UNCOMMITTED of the data dictionary will be performed. Now, we will have a READ COMMITTED copy of the data dictionary cache, and a READ UNCOMMITTED copy of the metadata record. btr_page_reorganize_low(): Correctly update the FIL_PAGE_TYPE when rolling back an instant ADD/DROP COLUMN operation. row_rec_to_index_entry_impl(): Relax some assertions, and disallow accessing "extra" fields. This fixes the recovery of a crash during an instant ADD COLUMN after a successful instant DROP COLUMN, in the test innodb.instant_alter_crash. Tested by: Matthias Leich
167 lines
5.8 KiB
Plaintext
167 lines
5.8 KiB
Plaintext
--source include/have_innodb.inc
|
|
# Embedded mode doesn't allow restarting
|
|
--source include/not_embedded.inc
|
|
--source include/have_sequence.inc
|
|
|
|
--disable_query_log
|
|
call mtr.add_suppression("InnoDB: Table `mysql`\\.`innodb_table_stats` not found");
|
|
call mtr.add_suppression("InnoDB: Table test/t1 in InnoDB data dictionary contains invalid flags. SYS_TABLES\\.TYPE=1 SYS_TABLES\\.MIX_LEN=511\\r?$");
|
|
call mtr.add_suppression("InnoDB: Parent table of FTS auxiliary table test/FTS_.* not found");
|
|
call mtr.add_suppression("InnoDB: Cannot open table test/t1 from the internal data dictionary");
|
|
call mtr.add_suppression("InnoDB: Table `test`.`t1` does not exist in the InnoDB internal data dictionary though MariaDB is trying to (rename|drop)");
|
|
FLUSH TABLES;
|
|
--enable_query_log
|
|
|
|
let INNODB_PAGE_SIZE=`select @@innodb_page_size`;
|
|
|
|
let bugdir= $MYSQLTEST_VARDIR/tmp/row_format_redundant;
|
|
--mkdir $bugdir
|
|
--let SEARCH_FILE = $MYSQLTEST_VARDIR/log/mysqld.1.err
|
|
|
|
--let $d=--innodb-data-home-dir=$bugdir --innodb-log-group-home-dir=$bugdir
|
|
--let $d=$d --innodb-data-file-path=ibdata1:1M:autoextend
|
|
--let $d=$d --innodb-undo-tablespaces=0 --innodb-stats-persistent=0
|
|
--let $restart_parameters= $d
|
|
# Ensure that any DDL records from previous tests have been purged.
|
|
SET GLOBAL innodb_fast_shutdown=0;
|
|
--source include/restart_mysqld.inc
|
|
|
|
SET GLOBAL innodb_file_per_table=1;
|
|
|
|
--echo #
|
|
--echo # Bug#21644827 - FTS, ASSERT !SRV_READ_ONLY_MODE || M_IMPL.M_LOG_MODE ==
|
|
--echo # MTR_LOG_NO_REDO
|
|
--echo #
|
|
|
|
SET GLOBAL innodb_file_per_table=ON;
|
|
create table t1 (a int not null, d varchar(15) not null, b
|
|
varchar(198) not null, c char(156)) engine=InnoDB
|
|
row_format=redundant;
|
|
|
|
create temporary table t like t1;
|
|
|
|
insert into t values(123, 'abcdef', 'jghikl', 'mnop');
|
|
insert into t values(456, 'abcdef', 'jghikl', 'mnop');
|
|
insert into t values(789, 'abcdef', 'jghikl', 'mnop');
|
|
insert into t values(134, 'kasdfsdsadf', 'adfjlasdkfjasd', 'adfsadflkasdasdfljasdf');
|
|
|
|
insert into t1 select a,d,b,c from t, seq_1_to_1024;
|
|
|
|
SET GLOBAL innodb_file_per_table=OFF;
|
|
create table t2 (a int not null, d varchar(15) not null, b
|
|
varchar(198) not null, c char(156), fulltext ftsic(c)) engine=InnoDB
|
|
row_format=redundant;
|
|
|
|
insert into t2 select a,d,b,c from t, seq_1_to_1024;
|
|
|
|
create table t3 (a int not null, d varchar(15) not null, b varchar(198),
|
|
c varchar(150), index k1(c(99), b(56)), index k2(b(5), c(10))) engine=InnoDB
|
|
row_format=redundant;
|
|
|
|
insert into t3 values(444, 'dddd', 'bbbbb', 'aaaaa');
|
|
insert into t3 values(555, 'eeee', 'ccccc', 'aaaaa');
|
|
|
|
# read-only restart requires the change buffer to be empty; therefore we
|
|
# do a slow shutdown.
|
|
SET GLOBAL innodb_fast_shutdown=0;
|
|
--let $restart_parameters= $d --innodb-read-only
|
|
--source include/restart_mysqld.inc
|
|
|
|
SELECT COUNT(*) FROM t1;
|
|
SELECT COUNT(*) FROM t2;
|
|
SELECT COUNT(*) FROM t3;
|
|
|
|
--error ER_OPEN_AS_READONLY
|
|
TRUNCATE TABLE t1;
|
|
--error ER_OPEN_AS_READONLY
|
|
TRUNCATE TABLE t2;
|
|
--error ER_OPEN_AS_READONLY
|
|
TRUNCATE TABLE t3;
|
|
|
|
--let $restart_parameters= $d
|
|
--source include/restart_mysqld.inc
|
|
|
|
TRUNCATE TABLE t1;
|
|
TRUNCATE TABLE t2;
|
|
TRUNCATE TABLE t3;
|
|
|
|
--source include/shutdown_mysqld.inc
|
|
--perl
|
|
use strict;
|
|
do "$ENV{MTR_SUITE_DIR}/include/crc32.pl";
|
|
my $ps= $ENV{INNODB_PAGE_SIZE};
|
|
my $file= "$ENV{bugdir}/ibdata1";
|
|
open(FILE, "+<", $file) || die "Unable to open $file\n";
|
|
die "Unable to read $file" unless sysread(FILE, $_, $ps) == $ps;
|
|
my $full_crc32 = unpack("N",substr($_,54,4)) & 0x10; # FIL_SPACE_FLAGS;
|
|
sysseek(FILE, 0, 0) || die "Unable to seek $file";
|
|
# Read DICT_HDR_TABLES, the root page number of CLUST_IND (SYS_TABLES.NAME).
|
|
sysseek(FILE, 7*$ps+38+32, 0) || die "Unable to seek $file";
|
|
die "Unable to read $file" unless sysread(FILE, $_, 4) == 4;
|
|
my $sys_tables_root = unpack("N", $_);
|
|
my $page;
|
|
sysseek(FILE, $sys_tables_root*$ps, 0) || die "Unable to seek $file";
|
|
die "Unable to read $file" unless sysread(FILE, $page, $ps) == $ps;
|
|
for (my $offset= 0x65; $offset;
|
|
$offset= unpack("n", substr($page,$offset-2,2)))
|
|
{
|
|
my $n_fields= unpack("n", substr($page,$offset-4,2)) >> 1 & 0x3ff;
|
|
my $start= 0;
|
|
my $end= unpack("C", substr($page, $offset-7, 1));
|
|
my $name= substr($page,$offset+$start,$end-$start);
|
|
for (my $i= 0; $i < $n_fields; $i++) {
|
|
my $end= unpack("C", substr($page, $offset-7-$i, 1));
|
|
# Corrupt SYS_TABLES.MIX_LEN (ignored for ROW_FORMAT=REDUNDANT)
|
|
if ($i == 7 && $name =~ '^test/t[123]')
|
|
{
|
|
print "corrupted SYS_TABLES.MIX_LEN for $name\n";
|
|
substr($page,$offset+$start,$end-$start)= pack("N", 511);
|
|
}
|
|
$start= $end & 0x7f;
|
|
}
|
|
}
|
|
my $polynomial = 0x82f63b78; # CRC-32C
|
|
if ($full_crc32) {
|
|
my $ck = mycrc32(substr($page, 0, $ps-4), 0, $polynomial);
|
|
substr($page, $ps-4, 4) = pack("N", $ck);
|
|
} else {
|
|
my $ck= pack("N",mycrc32(substr($page, 4, 22), 0, $polynomial) ^
|
|
mycrc32(substr($page, 38, $ps - 38 - 8), 0, $polynomial));
|
|
substr($page,0,4)=$ck;
|
|
substr($page,$ps-8,4)=$ck;
|
|
}
|
|
|
|
sysseek(FILE, $sys_tables_root*$ps, 0) || die "Unable to seek $file";
|
|
syswrite(FILE, $page, $ps)==$ps || die "Unable to write $file\n";
|
|
close(FILE) || die "Unable to close $file\n";
|
|
EOF
|
|
|
|
--source include/start_mysqld.inc
|
|
--error ER_NO_SUCH_TABLE_IN_ENGINE
|
|
TRUNCATE TABLE t1;
|
|
TRUNCATE TABLE t2;
|
|
TRUNCATE TABLE t3;
|
|
--error ER_NO_SUCH_TABLE_IN_ENGINE
|
|
SELECT COUNT(*) FROM t1;
|
|
SELECT COUNT(*) FROM t2;
|
|
SELECT COUNT(*) FROM t3;
|
|
--error ER_ERROR_ON_RENAME
|
|
RENAME TABLE t1 TO tee_one;
|
|
DROP TABLE t1;
|
|
DROP TABLE t2,t3;
|
|
|
|
--let SEARCH_PATTERN= \[ERROR\] InnoDB: Table test/t1 in InnoDB data dictionary contains invalid flags\. SYS_TABLES\.TYPE=1 SYS_TABLES\.MIX_LEN=511\b
|
|
--source include/search_pattern_in_file.inc
|
|
|
|
--let $restart_parameters=
|
|
--source include/restart_mysqld.inc
|
|
|
|
--list_files $bugdir
|
|
--remove_files_wildcard $bugdir
|
|
--rmdir $bugdir
|
|
|
|
# Remove the data file, because DROP TABLE skipped it for the "corrupted" table
|
|
--let MYSQLD_DATADIR=`select @@datadir`
|
|
--remove_file $MYSQLD_DATADIR/test/t1.ibd
|
|
--list_files $MYSQLD_DATADIR/test
|