Commit 754727bb authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-14378 In ALGORITHM=INPLACE, use a common name for the intermediate tables or partitions

This is a backport of commit 07e9ff1f.

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.
parent cf2a4426
...@@ -4,17 +4,10 @@ ...@@ -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` ( ...@@ -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); ...@@ -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'; ...@@ -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`; ...@@ -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
--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;
--echo # Consecutive Alter table does not create same temporary file name
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.
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 $temp_table_name=', $frm_file[0], ';';
close OUT or die;
EOF EOF
--echo # Manual recovery end source $TABLENAME_INC;
--echo # Startup the server after manual recovery remove_file $TABLENAME_INC;
--source include/start_mysqld.inc --source include/start_mysqld.inc
--echo # Drop the orphaned rebuilt table. show create table t1;
--echo # Consecutive Alter table does not create same temporary file name
ALTER TABLE t1 ADD PRIMARY KEY (f2, f1);
--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[] = { ...@@ -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;
......
...@@ -294,11 +294,7 @@ is_partition( ...@@ -294,11 +294,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
......
...@@ -4520,11 +4520,18 @@ prepare_inplace_alter_table_dict( ...@@ -4520,11 +4520,18 @@ prepare_inplace_alter_table_dict(
to rebuild the table with a temporary name. */ to rebuild the table with a temporary name. */
if (new_clustered) { if (new_clustered) {
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;
......
...@@ -568,6 +568,29 @@ struct table_name_t ...@@ -568,6 +568,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 */
......
...@@ -3629,8 +3629,7 @@ row_drop_table_for_mysql( ...@@ -3629,8 +3629,7 @@ 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_") ut_ad(strstr(table->name.m_name, "/FTS_")
|| strstr(table->name.m_name, || strstr(table->name.m_name, TEMP_TABLE_PATH_PREFIX));
"/" TEMP_FILE_PREFIX_INNODB));
} }
/* Mark all indexes unavailable in the data dictionary cache /* Mark all indexes unavailable in the data dictionary cache
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment