Skip to content
/ server Public

Commit accb4af

Browse files
MDEV-31892 Server crash upon moving InnoDB table with
fulltext index between databases Problem: ======== - When renaming/moving an InnoDB table with fulltext indexes between databases, the server would crash because InnoDB attempted to open auxiliary tables using the old database name instead of the new one. When creating auxiliary index, InnoDB does create temporary sort fulltext index using old table reference which creates the auxiliary table name with the prefix of old database name. FTS Document ID size optimization in row_merge_create_fts_sort_index() using dict_table_get_n_rows() to decide between 4-byte and 8-byte Doc IDs for memory optimization. But dict_table_get_n_rows() returns estimated statistics that may be stale or inaccurate, potentially leading to wrong size decisions and data corruption if 4-byte Doc IDs are chosen when 8-byte are actually needed. Solution: ========= fts_rename_aux_tables(): Iterate through all table indexes and ensure all fulltext indexes are properly renamed row_merge_create_fts_sort_index() : Use new_table instead of old_table when creating temporary FTS sort indexes. row_merge_build_indexes(): Refactored the logic to do memory optimization to determine the doc id size for temporary fts sort index. - When adding FTS index for the first time (DICT_TF2_FTS_ADD_DOC_ID), always use 8-byte Doc IDs - For existing FTS tables or user-supplied Doc ID columns, use fts_get_max_doc_id() approach to check actual maximum Doc ID
1 parent 08ba31f commit accb4af

File tree

6 files changed

+54
-37
lines changed

6 files changed

+54
-37
lines changed

mysql-test/suite/innodb_fts/r/innodb-fts-ddl.result

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,3 +332,13 @@ ALTER TABLE t ADD FULLTEXT INDEX ft1(f1);
332332
ALTER TABLE t ADD FULLTEXT INDEX ft2(f2);
333333
INSERT INTO t (f1,f2,f3) VALUES ('bar','baz','qux');
334334
DROP TABLE t;
335+
#
336+
# MDEV-31892 Server crash upon moving InnoDB table
337+
# with fulltext index between databases
338+
#
339+
CREATE DATABASE db;
340+
CREATE TABLE db.t (pk INT PRIMARY KEY, c CHAR(120), FULLTEXT KEY(c)) ENGINE=InnoDB;
341+
INSERT INTO db.t VALUES (1,'foo'),(2,'bar');
342+
ALTER TABLE db.t FORCE, RENAME to test.t;
343+
DROP DATABASE db;
344+
DROP TABLE IF EXISTS test.t;

mysql-test/suite/innodb_fts/t/innodb-fts-ddl.test

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,3 +441,15 @@ ALTER TABLE t ADD FULLTEXT INDEX ft1(f1);
441441
ALTER TABLE t ADD FULLTEXT INDEX ft2(f2);
442442
INSERT INTO t (f1,f2,f3) VALUES ('bar','baz','qux');
443443
DROP TABLE t;
444+
445+
--echo #
446+
--echo # MDEV-31892 Server crash upon moving InnoDB table
447+
--echo # with fulltext index between databases
448+
--echo #
449+
CREATE DATABASE db;
450+
CREATE TABLE db.t (pk INT PRIMARY KEY, c CHAR(120), FULLTEXT KEY(c)) ENGINE=InnoDB;
451+
INSERT INTO db.t VALUES (1,'foo'),(2,'bar');
452+
ALTER TABLE db.t FORCE, RENAME to test.t;
453+
# Cleanup
454+
DROP DATABASE db;
455+
DROP TABLE IF EXISTS test.t;

storage/innobase/fts/fts0fts.cc

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1485,17 +1485,15 @@ fts_rename_aux_tables(
14851485
}
14861486
}
14871487

1488-
fts_t* fts = table->fts;
1489-
1490-
/* Rename index specific auxiliary tables */
1491-
for (i = 0; fts->indexes != 0 && i < ib_vector_size(fts->indexes);
1492-
++i) {
1493-
dict_index_t* index;
1488+
for (dict_index_t *index = dict_table_get_first_index(table);
1489+
index; index = dict_table_get_next_index(index)) {
1490+
if (!(index->type & DICT_FTS)) {
1491+
continue;
1492+
}
14941493

1495-
index = static_cast<dict_index_t*>(
1496-
ib_vector_getp(fts->indexes, i));
1494+
FTS_INIT_INDEX_TABLE(&fts_table, nullptr,
1495+
FTS_INDEX_TABLE, index);
14971496

1498-
FTS_INIT_INDEX_TABLE(&fts_table, NULL, FTS_INDEX_TABLE, index);
14991497

15001498
for (ulint j = 0; j < FTS_NUM_AUX_INDEX; ++j) {
15011499
fts_table.suffix = fts_get_suffix(j);

storage/innobase/include/row0ftsort.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,8 @@ row_merge_create_fts_sort_index(
187187
is created */
188188
dict_table_t* table, /*!< in,out: table that FTS index
189189
is being created on */
190-
ibool* opt_doc_id_size);
191-
/*!< out: whether to use 4 bytes
190+
bool opt_doc_id_size);
191+
/*!< in: whether to use 4 bytes
192192
instead of 8 bytes integer to
193193
store Doc ID during sort */
194194

storage/innobase/row/row0ftsort.cc

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ row_merge_create_fts_sort_index(
7070
is created */
7171
dict_table_t* table, /*!< in,out: table that FTS index
7272
is being created on */
73-
ibool* opt_doc_id_size)
74-
/*!< out: whether to use 4 bytes
73+
bool opt_doc_id_size)
74+
/*!< in: whether to use 4 bytes
7575
instead of 8 bytes integer to
7676
store Doc ID during sort */
7777
{
@@ -115,29 +115,8 @@ row_merge_create_fts_sort_index(
115115
field->col = static_cast<dict_col_t*>(
116116
mem_heap_zalloc(new_index->heap, sizeof(dict_col_t)));
117117
field->col->mtype = DATA_INT;
118-
*opt_doc_id_size = FALSE;
119-
120-
/* Check whether we can use 4 bytes instead of 8 bytes integer
121-
field to hold the Doc ID, thus reduce the overall sort size */
122-
if (DICT_TF2_FLAG_IS_SET(table, DICT_TF2_FTS_ADD_DOC_ID)) {
123-
/* If Doc ID column is being added by this create
124-
index, then just check the number of rows in the table */
125-
if (dict_table_get_n_rows(table) < MAX_DOC_ID_OPT_VAL) {
126-
*opt_doc_id_size = TRUE;
127-
}
128-
} else {
129-
doc_id_t max_doc_id;
130-
131-
/* If the Doc ID column is supplied by user, then
132-
check the maximum Doc ID in the table */
133-
max_doc_id = fts_get_max_doc_id((dict_table_t*) table);
134-
135-
if (max_doc_id && max_doc_id < MAX_DOC_ID_OPT_VAL) {
136-
*opt_doc_id_size = TRUE;
137-
}
138-
}
139118

140-
if (*opt_doc_id_size) {
119+
if (opt_doc_id_size) {
141120
field->col->len = sizeof(ib_uint32_t);
142121
field->fixed_len = sizeof(ib_uint32_t);
143122
} else {

storage/innobase/row/row0merge.cc

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4476,14 +4476,32 @@ row_merge_build_indexes(
44764476
* static_cast<double>(n_indexes);
44774477
for (i = 0; i < n_indexes; i++) {
44784478
if (indexes[i]->type & DICT_FTS) {
4479-
ibool opt_doc_id_size = FALSE;
4479+
bool opt_doc_id_size = false;
44804480

44814481
/* To build FTS index, we would need to extract
44824482
doc's word, Doc ID, and word's position, so
44834483
we need to build a "fts sort index" indexing
44844484
on above three 'fields' */
4485+
4486+
/* Check whether we can use 4 bytes instead of 8 bytes
4487+
integer field to hold the Doc ID, thus reduce
4488+
the overall sort size. If the fulltext index is being
4489+
added for the first time then we should use 8 bytes Doc ID
4490+
size because table->stat_n_rows is an estimation and
4491+
not reliable to determine the Doc ID size. */
4492+
if (old_table->fts || !DICT_TF2_FLAG_IS_SET(
4493+
new_table, DICT_TF2_FTS_ADD_DOC_ID)) {
4494+
/* If the Doc ID column is supplied by user
4495+
or rebuilding the existing FTS table, then
4496+
check the maximum Doc ID in the old table */
4497+
doc_id_t max_doc_id =
4498+
fts_get_max_doc_id((dict_table_t*) old_table);
4499+
opt_doc_id_size =
4500+
(max_doc_id < MAX_DOC_ID_OPT_VAL);
4501+
}
4502+
44854503
fts_sort_idx = row_merge_create_fts_sort_index(
4486-
indexes[i], old_table, &opt_doc_id_size);
4504+
indexes[i], new_table, opt_doc_id_size);
44874505

44884506
row_merge_dup_t* dup
44894507
= static_cast<row_merge_dup_t*>(

0 commit comments

Comments
 (0)