mirror of
https://github.com/MariaDB/server.git
synced 2025-07-29 05:21:33 +03:00
MDEV-14378 In ALGORITHM=INPLACE, use a common name for the intermediate tables or partitions
Allow DROP TABLE `#mysql50##sql-...._.` to drop tables that were being rebuilt by ALGORITHM=INPLACE NOTE: If the server is killed after the table-rebuilding ALGORITHM=INPLACE commits inside InnoDB but before the .frm file has been replaced, then the recovery will involve something else than DROP TABLE. NOTE: If the server is killed in a true inplace ALTER TABLE commits inside InnoDB but before the .frm file has been replaced, then we are really out of luck. To properly handle that situation, we would need a transactional mysql.ddl_fixup table that directs recovery to rename or remove files. prepare_inplace_alter_table_dict(): Use the altered_table->s->table_name for generating the new_table_name. table_name_t::part_suffix: The start of the partition name suffix. table_name_t::dbend(): Return the end of the schema name. table_name_t::dblen(): Return the length of the schema name, in bytes. table_name_t::basename(): Return the name without the schema name. table_name_t::part(): Return the partition name, or NULL if none. row_drop_table_for_mysql(): Assert for #sql, not #sql-ib.
This commit is contained in:
@ -4,17 +4,10 @@
|
|||||||
# Temporary tablename will be unique. This makes sure that future
|
# Temporary tablename will be unique. This makes sure that future
|
||||||
# in-place ALTERs of the same table will not be blocked due to
|
# in-place ALTERs of the same table will not be blocked due to
|
||||||
# temporary tablename.
|
# temporary tablename.
|
||||||
# Crash the server in ha_innobase::commit_inplace_alter_table()
|
|
||||||
CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL) ENGINE=innodb;
|
CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL) ENGINE=innodb;
|
||||||
SET debug='d,innodb_alter_commit_crash_before_commit';
|
SET debug_dbug='+d,innodb_alter_commit_crash_before_commit';
|
||||||
Warnings:
|
|
||||||
Warning 1287 '@@debug' is deprecated and will be removed in a future release. Please use '@@debug_dbug' instead
|
|
||||||
# Write file to make mysql-test-run.pl expect crash
|
|
||||||
# Execute the statement that causes the crash
|
|
||||||
ALTER TABLE t1 ADD PRIMARY KEY (f2, f1);
|
ALTER TABLE t1 ADD PRIMARY KEY (f2, f1);
|
||||||
ERROR HY000: Lost connection to MySQL server during query
|
ERROR HY000: Lost connection to MySQL server during query
|
||||||
# Startup the server after the crash
|
|
||||||
# Read and remember the temporary table name
|
|
||||||
show create table t1;
|
show create table t1;
|
||||||
Table Create Table
|
Table Create Table
|
||||||
t1 CREATE TABLE `t1` (
|
t1 CREATE TABLE `t1` (
|
||||||
@ -23,13 +16,6 @@ t1 CREATE TABLE `t1` (
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1
|
) ENGINE=InnoDB DEFAULT CHARSET=latin1
|
||||||
# Consecutive Alter table does not create same temporary file name
|
# Consecutive Alter table does not create same temporary file name
|
||||||
ALTER TABLE t1 ADD PRIMARY KEY (f2, f1);
|
ALTER TABLE t1 ADD PRIMARY KEY (f2, f1);
|
||||||
# Shutdown the server to allow manual recovery
|
|
||||||
# Manual recovery begin. The dictionary was not updated
|
|
||||||
# and the files were not renamed. The rebuilt table
|
|
||||||
# was left behind on purpose, to faciliate data recovery.
|
|
||||||
# Manual recovery end
|
|
||||||
# Startup the server after manual recovery
|
|
||||||
# Drop the orphaned rebuilt table.
|
|
||||||
show create table t1;
|
show create table t1;
|
||||||
Table Create Table
|
Table Create Table
|
||||||
t1 CREATE TABLE `t1` (
|
t1 CREATE TABLE `t1` (
|
||||||
|
@ -138,24 +138,27 @@ ALTER TABLE t2 ADD PRIMARY KEY (f2, f1);
|
|||||||
let $temp_table_name = `SELECT SUBSTRING(name,6)
|
let $temp_table_name = `SELECT SUBSTRING(name,6)
|
||||||
FROM information_schema.innodb_sys_tables
|
FROM information_schema.innodb_sys_tables
|
||||||
WHERE name LIKE "test/#sql-ib$orig_table_id%"`;
|
WHERE name LIKE "test/#sql-ib$orig_table_id%"`;
|
||||||
# This second copy is an environment variable for the perl script below.
|
|
||||||
let temp_table_name = $temp_table_name;
|
|
||||||
|
|
||||||
--echo # Manual *.frm recovery begin. The dictionary was not updated
|
--echo # Manual *.frm recovery begin. The dictionary was not updated
|
||||||
--echo # and the files were not renamed. The rebuilt table
|
--echo # and the files were not renamed. The rebuilt table
|
||||||
--echo # was left behind on purpose, to faciliate data recovery.
|
--echo # was left behind on purpose, to faciliate data recovery.
|
||||||
|
|
||||||
|
let TABLENAME_INC= $MYSQLTEST_VARDIR/tmp/tablename.inc;
|
||||||
perl;
|
perl;
|
||||||
my @frm_file = glob "$ENV{'datadir'}/test/#sql-*.frm";
|
die unless open OUT, ">$ENV{TABLENAME_INC}";
|
||||||
my $target_frm = "$ENV{'datadir'}/test/$ENV{'temp_table_name'}.frm";
|
chdir "$ENV{'datadir'}/test";
|
||||||
rename($frm_file[0], $target_frm);
|
my @frm_file = map { substr($_, 0, -4) } glob "#sql-*.frm";
|
||||||
|
print OUT 'let $tablename=', $frm_file[0], ';';
|
||||||
|
close OUT or die;
|
||||||
EOF
|
EOF
|
||||||
|
source $TABLENAME_INC;
|
||||||
|
remove_file $TABLENAME_INC;
|
||||||
|
|
||||||
--echo # Manual recovery end
|
--echo # Manual recovery end
|
||||||
|
|
||||||
--echo # Drop the orphaned rebuilt table.
|
--echo # Drop the orphaned rebuilt table.
|
||||||
--disable_query_log
|
--disable_query_log
|
||||||
eval DROP TABLE `#mysql50#$temp_table_name`;
|
eval DROP TABLE `#mysql50#$tablename`;
|
||||||
--enable_query_log
|
--enable_query_log
|
||||||
|
|
||||||
SHOW TABLES;
|
SHOW TABLES;
|
||||||
@ -186,7 +189,6 @@ SET DEBUG_DBUG='+d,innodb_alter_commit_crash_after_commit';
|
|||||||
let $orig_table_id = `select table_id from
|
let $orig_table_id = `select table_id from
|
||||||
information_schema.innodb_sys_tables where name = 'test/t1'`;
|
information_schema.innodb_sys_tables where name = 'test/t1'`;
|
||||||
|
|
||||||
# FIXME: MDEV-9469 'Incorrect key file' on ALTER TABLE
|
|
||||||
# Write file to make mysql-test-run.pl expect crash
|
# Write file to make mysql-test-run.pl expect crash
|
||||||
--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
|
--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
|
||||||
#
|
#
|
||||||
|
@ -24,49 +24,30 @@ let datadir= `select @@datadir`;
|
|||||||
--let $_server_id= `SELECT @@server_id`
|
--let $_server_id= `SELECT @@server_id`
|
||||||
--let $_expect_file_name=$MYSQLTEST_VARDIR/tmp/mysqld.$_server_id.expect
|
--let $_expect_file_name=$MYSQLTEST_VARDIR/tmp/mysqld.$_server_id.expect
|
||||||
|
|
||||||
--echo # Crash the server in ha_innobase::commit_inplace_alter_table()
|
|
||||||
CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL) ENGINE=innodb;
|
CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL) ENGINE=innodb;
|
||||||
SET debug='d,innodb_alter_commit_crash_before_commit';
|
SET debug_dbug='+d,innodb_alter_commit_crash_before_commit';
|
||||||
|
|
||||||
let $orig_table_id = `SELECT table_id
|
--exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
|
||||||
FROM information_schema.innodb_sys_tables
|
|
||||||
WHERE name = 'test/t1'`;
|
|
||||||
|
|
||||||
--echo # Write file to make mysql-test-run.pl expect crash
|
|
||||||
--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
|
|
||||||
|
|
||||||
--echo # Execute the statement that causes the crash
|
|
||||||
--error 2013
|
--error 2013
|
||||||
ALTER TABLE t1 ADD PRIMARY KEY (f2, f1);
|
ALTER TABLE t1 ADD PRIMARY KEY (f2, f1);
|
||||||
--echo # Startup the server after the crash
|
|
||||||
|
let TABLENAME_INC= $MYSQLTEST_VARDIR/tmp/tablename.inc;
|
||||||
|
perl;
|
||||||
|
die unless open OUT, ">$ENV{TABLENAME_INC}";
|
||||||
|
chdir "$ENV{'datadir'}/test";
|
||||||
|
my @frm_file = map { substr($_, 0, -4) } glob "#sql-*.frm";
|
||||||
|
print OUT 'let $temp_table_name=', $frm_file[0], ';';
|
||||||
|
close OUT or die;
|
||||||
|
EOF
|
||||||
|
source $TABLENAME_INC;
|
||||||
|
remove_file $TABLENAME_INC;
|
||||||
|
|
||||||
--source include/start_mysqld.inc
|
--source include/start_mysqld.inc
|
||||||
|
|
||||||
--echo # Read and remember the temporary table name
|
|
||||||
let $temp_table_name = `SELECT SUBSTRING(name,6)
|
|
||||||
FROM information_schema.innodb_sys_tables
|
|
||||||
WHERE name LIKE "test/#sql-ib$orig_table_id%"`;
|
|
||||||
# This second copy is an environment variable for the perl script below.
|
|
||||||
let temp_table_name = $temp_table_name;
|
|
||||||
show create table t1;
|
show create table t1;
|
||||||
--echo # Consecutive Alter table does not create same temporary file name
|
--echo # Consecutive Alter table does not create same temporary file name
|
||||||
ALTER TABLE t1 ADD PRIMARY KEY (f2, f1);
|
ALTER TABLE t1 ADD PRIMARY KEY (f2, f1);
|
||||||
--echo # Shutdown the server to allow manual recovery
|
|
||||||
--source include/shutdown_mysqld.inc
|
|
||||||
|
|
||||||
--echo # Manual recovery begin. The dictionary was not updated
|
|
||||||
--echo # and the files were not renamed. The rebuilt table
|
|
||||||
--echo # was left behind on purpose, to faciliate data recovery.
|
|
||||||
|
|
||||||
perl;
|
|
||||||
my @frm_file = glob "$ENV{'datadir'}/test/#sql-*.frm";
|
|
||||||
my $target_frm = "$ENV{'datadir'}/test/$ENV{'temp_table_name'}.frm";
|
|
||||||
rename($frm_file[0], $target_frm);
|
|
||||||
EOF
|
|
||||||
--echo # Manual recovery end
|
|
||||||
--echo # Startup the server after manual recovery
|
|
||||||
--source include/start_mysqld.inc
|
|
||||||
|
|
||||||
--echo # Drop the orphaned rebuilt table.
|
|
||||||
--disable_query_log
|
--disable_query_log
|
||||||
eval DROP TABLE `#mysql50#$temp_table_name`;
|
eval DROP TABLE `#mysql50#$temp_table_name`;
|
||||||
--enable_query_log
|
--enable_query_log
|
||||||
|
@ -50,6 +50,14 @@ static const char* innobase_system_databases[] = {
|
|||||||
NullS
|
NullS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** The start of the table basename suffix for partitioned tables */
|
||||||
|
const char table_name_t::part_suffix[4]
|
||||||
|
#ifdef _WIN32
|
||||||
|
= "#p#";
|
||||||
|
#else
|
||||||
|
= "#P#";
|
||||||
|
#endif
|
||||||
|
|
||||||
/** An interger randomly initialized at startup used to make a temporary
|
/** An interger randomly initialized at startup used to make a temporary
|
||||||
table name as unuique as possible. */
|
table name as unuique as possible. */
|
||||||
static ib_uint32_t dict_temp_file_num;
|
static ib_uint32_t dict_temp_file_num;
|
||||||
|
@ -279,11 +279,7 @@ is_partition(
|
|||||||
{
|
{
|
||||||
/* We look for pattern #P# to see if the table is partitioned
|
/* We look for pattern #P# to see if the table is partitioned
|
||||||
MariaDB table. */
|
MariaDB table. */
|
||||||
#ifdef _WIN32
|
return strstr(file_name, table_name_t::part_suffix);
|
||||||
return strstr(file_name, "#p#");
|
|
||||||
#else
|
|
||||||
return strstr(file_name, "#P#");
|
|
||||||
#endif /* _WIN32 */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Signal to shut down InnoDB (NULL if shutdown was signaled, or if
|
/** Signal to shut down InnoDB (NULL if shutdown was signaled, or if
|
||||||
|
@ -4887,11 +4887,18 @@ new_clustered_failed:
|
|||||||
goto err_exit;
|
goto err_exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* new_table_name
|
size_t dblen = ctx->old_table->name.dblen() + 1;
|
||||||
= dict_mem_create_temporary_tablename(
|
size_t tablen = altered_table->s->table_name.length;
|
||||||
ctx->heap,
|
const char* part = ctx->old_table->name.part();
|
||||||
ctx->new_table->name.m_name,
|
size_t partlen = part ? strlen(part) : 0;
|
||||||
ctx->new_table->id);
|
char* new_table_name = static_cast<char*>(
|
||||||
|
mem_heap_alloc(ctx->heap,
|
||||||
|
dblen + tablen + partlen + 1));
|
||||||
|
memcpy(new_table_name, ctx->old_table->name.m_name, dblen);
|
||||||
|
memcpy(new_table_name + dblen,
|
||||||
|
altered_table->s->table_name.str, tablen);
|
||||||
|
memcpy(new_table_name + dblen + tablen,
|
||||||
|
part ? part : "", partlen + 1);
|
||||||
ulint n_cols = 0;
|
ulint n_cols = 0;
|
||||||
ulint n_v_cols = 0;
|
ulint n_v_cols = 0;
|
||||||
dtuple_t* add_cols;
|
dtuple_t* add_cols;
|
||||||
|
@ -566,6 +566,29 @@ struct table_name_t
|
|||||||
{
|
{
|
||||||
/** The name in internal representation */
|
/** The name in internal representation */
|
||||||
char* m_name;
|
char* m_name;
|
||||||
|
|
||||||
|
/** @return the end of the schema name */
|
||||||
|
const char* dbend() const
|
||||||
|
{
|
||||||
|
const char* sep = strchr(m_name, '/');
|
||||||
|
ut_ad(sep);
|
||||||
|
return sep;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return the length of the schema name, in bytes */
|
||||||
|
size_t dblen() const { return dbend() - m_name; }
|
||||||
|
|
||||||
|
/** Determine the filename-safe encoded table name.
|
||||||
|
@return the filename-safe encoded table name */
|
||||||
|
const char* basename() const { return dbend() + 1; }
|
||||||
|
|
||||||
|
/** The start of the table basename suffix for partitioned tables */
|
||||||
|
static const char part_suffix[4];
|
||||||
|
|
||||||
|
/** Determine the partition or subpartition name suffix.
|
||||||
|
@return the partition name
|
||||||
|
@retval NULL if the table is not partitioned */
|
||||||
|
const char* part() const { return strstr(basename(), part_suffix); }
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Data structure for a column in a table */
|
/** Data structure for a column in a table */
|
||||||
|
@ -3850,8 +3850,8 @@ row_drop_table_for_mysql(
|
|||||||
TRX_DICT_OP_INDEX, we should be dropping auxiliary
|
TRX_DICT_OP_INDEX, we should be dropping auxiliary
|
||||||
tables for full-text indexes or temp tables. */
|
tables for full-text indexes or temp tables. */
|
||||||
ut_ad(strstr(table->name.m_name, "/FTS_") != NULL
|
ut_ad(strstr(table->name.m_name, "/FTS_") != NULL
|
||||||
|| strstr(table->name.m_name, TEMP_FILE_PREFIX_INNODB)
|
|| strstr(table->name.m_name,
|
||||||
!= NULL);
|
TEMP_TABLE_PATH_PREFIX) != NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mark all indexes unavailable in the data dictionary cache
|
/* Mark all indexes unavailable in the data dictionary cache
|
||||||
|
Reference in New Issue
Block a user