Skip to content

Commit 6e390a6

Browse files
committed
MDEV-26772 InnoDB DDL fails with DUPLICATE KEY error
ha_innobase::delete_table(): When the table that is being dropped has a name starting with #sql, temporarily set innodb_lock_wait_timeout=0 while attempting to lock the persistent statistics tables. If the statistics tables cannot be locked, pretend that statistics did not exist and carry on with dropping the table. The SQL layer is not really prepared for failures of this operation. This is what fixes the test case. ha_innobase::rename_table(): When renaming a table from a name that starts with #sql, try to lock the statistics tables with an immediate timeout, and ignore the statistics if the locks were not available. In fact, during any rename from a #sql name, dict_stats_rename_table() should have no effect, because already when an earlier rename to a #sql name took place we should have deleted the statistics for the table using the non-#sql name. This change is just analogous to the ha_innobase::delete_table().
1 parent f7684f0 commit 6e390a6

File tree

3 files changed

+110
-4
lines changed

3 files changed

+110
-4
lines changed

mysql-test/suite/innodb/r/innodb-alter-debug.result

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,29 @@ ALTER TABLE t RENAME INDEX i2 to x, ALGORITHM=INPLACE;
107107
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
108108
SET DEBUG_DBUG = @saved_debug_dbug;
109109
DROP TABLE t;
110+
#
111+
# MDEV-26772 InnoDB DDL fails with DUPLICATE KEY error
112+
#
113+
create table t1(f1 int not null primary key,
114+
f2 int not null, index idx(f2))engine=innodb;
115+
insert into t1 values(1, 1);
116+
connect con1,localhost,root,,,;
117+
SET DEBUG_SYNC='before_delete_table_stats SIGNAL blocked WAIT_FOR go';
118+
SET innodb_lock_wait_timeout=0;
119+
ALTER TABLE t1 FORCE, ALGORITHM=COPY;
120+
connection default;
121+
SET DEBUG_SYNC='now WAIT_FOR blocked';
122+
BEGIN;
123+
SELECT * FROM mysql.innodb_table_stats FOR UPDATE;
124+
database_name table_name last_update n_rows clustered_index_size sum_of_other_index_sizes
125+
SET DEBUG_SYNC='now SIGNAL go';
126+
connection con1;
127+
connection default;
128+
COMMIT;
129+
SET DEBUG_SYNC=RESET;
130+
connection con1;
131+
ALTER TABLE t1 RENAME KEY idx TO idx1, ALGORITHM=COPY;
132+
disconnect con1;
133+
connection default;
134+
DROP TABLE t1;
135+
# End of 10.6 tests

mysql-test/suite/innodb/t/innodb-alter-debug.test

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,5 +142,42 @@ SET DEBUG_DBUG = @saved_debug_dbug;
142142

143143
DROP TABLE t;
144144

145+
--echo #
146+
--echo # MDEV-26772 InnoDB DDL fails with DUPLICATE KEY error
147+
--echo #
148+
149+
create table t1(f1 int not null primary key,
150+
151+
f2 int not null, index idx(f2))engine=innodb;
152+
153+
insert into t1 values(1, 1);
154+
155+
connect(con1,localhost,root,,,);
156+
SET DEBUG_SYNC='before_delete_table_stats SIGNAL blocked WAIT_FOR go';
157+
SET innodb_lock_wait_timeout=0;
158+
send ALTER TABLE t1 FORCE, ALGORITHM=COPY;
159+
160+
connection default;
161+
SET DEBUG_SYNC='now WAIT_FOR blocked';
162+
BEGIN;
163+
SELECT * FROM mysql.innodb_table_stats FOR UPDATE;
164+
SET DEBUG_SYNC='now SIGNAL go';
165+
166+
connection con1;
167+
reap;
168+
169+
connection default;
170+
COMMIT;
171+
SET DEBUG_SYNC=RESET;
172+
173+
connection con1;
174+
ALTER TABLE t1 RENAME KEY idx TO idx1, ALGORITHM=COPY;
175+
disconnect con1;
176+
177+
connection default;
178+
DROP TABLE t1;
179+
180+
--echo # End of 10.6 tests
181+
145182
# Wait till all disconnects are completed
146183
--source include/wait_until_count_sessions.inc

storage/innobase/handler/ha_innodb.cc

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13473,6 +13473,8 @@ int ha_innobase::delete_table(const char *name)
1347313473
}
1347413474
#endif
1347513475

13476+
DEBUG_SYNC(thd, "before_delete_table_stats");
13477+
1347613478
if (err == DB_SUCCESS && dict_stats_is_persistent_enabled(table) &&
1347713479
!table->is_stats_table())
1347813480
{
@@ -13496,11 +13498,29 @@ int ha_innobase::delete_table(const char *name)
1349613498
dict_sys.unfreeze();
1349713499
}
1349813500

13501+
auto &timeout= THDVAR(thd, lock_wait_timeout);
13502+
const auto save_timeout= timeout;
13503+
if (table->name.is_temporary())
13504+
timeout= 0;
13505+
1349913506
if (table_stats && index_stats &&
1350013507
!strcmp(table_stats->name.m_name, TABLE_STATS_NAME) &&
1350113508
!strcmp(index_stats->name.m_name, INDEX_STATS_NAME) &&
1350213509
!(err= lock_table_for_trx(table_stats, trx, LOCK_X)))
1350313510
err= lock_table_for_trx(index_stats, trx, LOCK_X);
13511+
13512+
if (err != DB_SUCCESS && !timeout)
13513+
{
13514+
/* We may skip deleting statistics if we cannot lock the tables,
13515+
when the table carries a temporary name. */
13516+
err= DB_SUCCESS;
13517+
dict_table_close(table_stats, false, thd, mdl_table);
13518+
dict_table_close(index_stats, false, thd, mdl_index);
13519+
table_stats= nullptr;
13520+
index_stats= nullptr;
13521+
}
13522+
13523+
timeout= save_timeout;
1350413524
}
1350513525

1350613526
if (err == DB_SUCCESS)
@@ -13959,8 +13979,9 @@ ha_innobase::rename_table(
1395913979
normalize_table_name(norm_to, to);
1396013980

1396113981
dberr_t error = DB_SUCCESS;
13982+
const bool from_temp = dict_table_t::is_temporary_name(norm_from);
1396213983

13963-
if (dict_table_t::is_temporary_name(norm_from)) {
13984+
if (from_temp) {
1396413985
/* There is no need to lock any FOREIGN KEY child tables. */
1396513986
} else if (dict_table_t *table = dict_table_open_on_name(
1396613987
norm_from, false, DICT_ERR_IGNORE_FK_NOKEY)) {
@@ -14003,9 +14024,31 @@ ha_innobase::rename_table(
1400314024

1400414025
if (error == DB_SUCCESS && table_stats && index_stats
1400514026
&& !strcmp(table_stats->name.m_name, TABLE_STATS_NAME)
14006-
&& !strcmp(index_stats->name.m_name, INDEX_STATS_NAME) &&
14007-
!(error = lock_table_for_trx(table_stats, trx, LOCK_X))) {
14008-
error = lock_table_for_trx(index_stats, trx, LOCK_X);
14027+
&& !strcmp(index_stats->name.m_name, INDEX_STATS_NAME)) {
14028+
auto &timeout = THDVAR(thd, lock_wait_timeout);
14029+
const auto save_timeout = timeout;
14030+
if (from_temp) {
14031+
timeout = 0;
14032+
}
14033+
error = lock_table_for_trx(table_stats, trx, LOCK_X);
14034+
if (error == DB_SUCCESS) {
14035+
error = lock_table_for_trx(index_stats, trx,
14036+
LOCK_X);
14037+
}
14038+
if (error != DB_SUCCESS && from_temp) {
14039+
error = DB_SUCCESS;
14040+
/* We may skip renaming statistics if
14041+
we cannot lock the tables, when the
14042+
table is being renamed from from a
14043+
temporary name. */
14044+
dict_table_close(table_stats, false, thd,
14045+
mdl_table);
14046+
dict_table_close(index_stats, false, thd,
14047+
mdl_index);
14048+
table_stats = nullptr;
14049+
index_stats = nullptr;
14050+
}
14051+
timeout = save_timeout;
1400914052
}
1401014053
}
1401114054

0 commit comments

Comments
 (0)