Skip to content

Commit a7e0c41

Browse files
Andrzej Jarzabekzmur
authored andcommitted
Bug#34929814 Inconsistent FTS state in concurrent scenarios
Bug#36347647 Contribution by Tencent: Resiliency issue in fts_sync_commit Symptoms: During various operations on tables containing FTS indexes the state of FTS as comitted to database may become inconsistent, affecting the following scenarios: - the server terminates when synchronizing the FTS cache, - synchronization of FTS cache occurs concurrently with another FTS operation This inconsistency may lead to various negative effects including incorrect query results. An example operation which forces the synchronization of FTS cach is OPTIMIZE TABLE with innodb_optimize_fulltext_only set to ON. Root Cause: Function 'fts_cmp_set_sync_doc_id' and 'fts_sql_commit' use different trx_t objects in function 'fts_sync_commit'. This causes a scenario where 'synced_doc_id' in the config table is already committed, but remaining FTS data isn't yet, leading to issues in the scenarios described above - the server terminating between the commits, or concurrent access getting the intermediate state. Fix: When 'fts_cmp_set_sync_doc_id' is called from 'fts_sync_commit' it will use the transaction provided by the caller. Patch based on contribution by Tencent. Change-Id: I65fa5702db5e7b6b2004a7311a6b0aa97449034f
1 parent 2f67801 commit a7e0c41

File tree

3 files changed

+142
-9
lines changed

3 files changed

+142
-9
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
CREATE TABLE opening_lines (
2+
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
3+
opening_line TEXT(500),
4+
author VARCHAR(200),
5+
title VARCHAR(200)
6+
) ENGINE=InnoDB;
7+
CREATE FULLTEXT INDEX idx ON opening_lines(opening_line);
8+
Warnings:
9+
Warning 124 InnoDB rebuilding table to add column FTS_DOC_ID
10+
CREATE FULLTEXT INDEX ft_idx1 ON opening_lines(title);
11+
INSERT INTO opening_lines(opening_line,author,title) VALUES
12+
('Call me Ishmael.','Herman Melville','Moby Dick'),
13+
('A screaming comes across the sky.','Thomas Pynchon','Gravity\'s Rainbow'),
14+
('I am an invisible man.','Ralph Ellison','Invisible Man'),
15+
('Where now? Who now? When now?','Samuel Beckett','The Unnamable'),
16+
('It was love at first sight.','Joseph Heller','Catch-22'),
17+
('All this happened, more or less.','Kurt Vonnegut','Slaughterhouse-Five'),
18+
('Mrs. Dalloway said she would buy the flowers herself.','Virginia Woolf','Mrs. Dalloway'),
19+
('It was a pleasure to burn.','Ray Bradbury','Fahrenheit 451');
20+
SET GLOBAL innodb_ft_aux_table='test/opening_lines';
21+
SELECT * FROM information_schema.innodb_ft_config;
22+
KEY VALUE
23+
optimize_checkpoint_limit 180
24+
synced_doc_id 0
25+
stopword_table_name
26+
use_stopword 1
27+
SELECT * FROM opening_lines WHERE MATCH(opening_line) AGAINST('Ishmael');
28+
id opening_line author title
29+
1 Call me Ishmael. Herman Melville Moby Dick
30+
SELECT * FROM opening_lines WHERE MATCH(opening_line) AGAINST('invisible');
31+
id opening_line author title
32+
3 I am an invisible man. Ralph Ellison Invisible Man
33+
SELECT * FROM opening_lines;
34+
id opening_line author title
35+
1 Call me Ishmael. Herman Melville Moby Dick
36+
2 A screaming comes across the sky. Thomas Pynchon Gravity's Rainbow
37+
3 I am an invisible man. Ralph Ellison Invisible Man
38+
4 Where now? Who now? When now? Samuel Beckett The Unnamable
39+
5 It was love at first sight. Joseph Heller Catch-22
40+
6 All this happened, more or less. Kurt Vonnegut Slaughterhouse-Five
41+
7 Mrs. Dalloway said she would buy the flowers herself. Virginia Woolf Mrs. Dalloway
42+
8 It was a pleasure to burn. Ray Bradbury Fahrenheit 451
43+
SET GLOBAL innodb_optimize_fulltext_only=ON;
44+
SET GLOBAL debug='+d,fts_crash_before_commit_sync';
45+
OPTIMIZE TABLE opening_lines;
46+
ERROR HY000: Lost connection to MySQL server during query
47+
SELECT * FROM opening_lines WHERE MATCH(opening_line) AGAINST('Ishmael');
48+
id opening_line author title
49+
1 Call me Ishmael. Herman Melville Moby Dick
50+
SELECT * FROM opening_lines WHERE MATCH(opening_line) AGAINST('invisible');
51+
id opening_line author title
52+
3 I am an invisible man. Ralph Ellison Invisible Man
53+
SELECT * FROM opening_lines;
54+
id opening_line author title
55+
1 Call me Ishmael. Herman Melville Moby Dick
56+
2 A screaming comes across the sky. Thomas Pynchon Gravity's Rainbow
57+
3 I am an invisible man. Ralph Ellison Invisible Man
58+
4 Where now? Who now? When now? Samuel Beckett The Unnamable
59+
5 It was love at first sight. Joseph Heller Catch-22
60+
6 All this happened, more or less. Kurt Vonnegut Slaughterhouse-Five
61+
7 Mrs. Dalloway said she would buy the flowers herself. Virginia Woolf Mrs. Dalloway
62+
8 It was a pleasure to burn. Ray Bradbury Fahrenheit 451
63+
DROP TABLE opening_lines;
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Test database resiliency against scenario where the server crashes
2+
# right before fts_sync_commit commits its transaction
3+
4+
source include/have_debug.inc;
5+
source include/not_valgrind.inc;
6+
7+
CREATE TABLE opening_lines (
8+
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
9+
opening_line TEXT(500),
10+
author VARCHAR(200),
11+
title VARCHAR(200)
12+
) ENGINE=InnoDB;
13+
14+
CREATE FULLTEXT INDEX idx ON opening_lines(opening_line);
15+
CREATE FULLTEXT INDEX ft_idx1 ON opening_lines(title);
16+
17+
INSERT INTO opening_lines(opening_line,author,title) VALUES
18+
('Call me Ishmael.','Herman Melville','Moby Dick'),
19+
('A screaming comes across the sky.','Thomas Pynchon','Gravity\'s Rainbow'),
20+
('I am an invisible man.','Ralph Ellison','Invisible Man'),
21+
('Where now? Who now? When now?','Samuel Beckett','The Unnamable'),
22+
('It was love at first sight.','Joseph Heller','Catch-22'),
23+
('All this happened, more or less.','Kurt Vonnegut','Slaughterhouse-Five'),
24+
('Mrs. Dalloway said she would buy the flowers herself.','Virginia Woolf','Mrs. Dalloway'),
25+
('It was a pleasure to burn.','Ray Bradbury','Fahrenheit 451');
26+
27+
SET GLOBAL innodb_ft_aux_table='test/opening_lines';
28+
SELECT * FROM information_schema.innodb_ft_config;
29+
30+
SELECT * FROM opening_lines WHERE MATCH(opening_line) AGAINST('Ishmael');
31+
SELECT * FROM opening_lines WHERE MATCH(opening_line) AGAINST('invisible');
32+
SELECT * FROM opening_lines;
33+
34+
SET GLOBAL innodb_optimize_fulltext_only=ON;
35+
SET GLOBAL debug='+d,fts_crash_before_commit_sync';
36+
--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
37+
--error 2013
38+
OPTIMIZE TABLE opening_lines;
39+
40+
--enable_reconnect
41+
--source include/wait_until_connected_again.inc
42+
43+
SELECT * FROM opening_lines WHERE MATCH(opening_line) AGAINST('Ishmael');
44+
SELECT * FROM opening_lines WHERE MATCH(opening_line) AGAINST('invisible');
45+
SELECT * FROM opening_lines;
46+
47+
DROP TABLE opening_lines;

storage/innobase/fts/fts0fts.cc

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*****************************************************************************
22
3-
Copyright (c) 2011, 2023, Oracle and/or its affiliates.
3+
Copyright (c) 2011, 2024, Oracle and/or its affiliates.
44
55
This program is free software; you can redistribute it and/or modify
66
it under the terms of the GNU General Public License, version 2.0,
@@ -2778,26 +2778,28 @@ fts_get_next_doc_id(
27782778
This function fetch the Doc ID from CONFIG table, and compare with
27792779
the Doc ID supplied. And store the larger one to the CONFIG table.
27802780
@return DB_SUCCESS if OK */
2781-
static MY_ATTRIBUTE((nonnull))
2781+
static
27822782
dberr_t
27832783
fts_cmp_set_sync_doc_id(
27842784
/*====================*/
27852785
const dict_table_t* table, /*!< in: table */
27862786
doc_id_t doc_id_cmp, /*!< in: Doc ID to compare */
27872787
ibool read_only, /*!< in: TRUE if read the
27882788
synced_doc_id only */
2789-
doc_id_t* doc_id) /*!< out: larger document id
2789+
doc_id_t* doc_id, /*!< out: larger document id
27902790
after comparing "doc_id_cmp"
27912791
to the one stored in CONFIG
27922792
table */
2793+
trx_t* trx = NULL)
27932794
{
2794-
trx_t* trx;
27952795
pars_info_t* info;
27962796
dberr_t error;
27972797
fts_table_t fts_table;
27982798
que_t* graph = NULL;
27992799
fts_cache_t* cache = table->fts->cache;
28002800
char table_name[MAX_FULL_NAME_LEN];
2801+
bool trx_allocated;
2802+
trx_savept_t savept;
28012803
retry:
28022804
ut_a(table->fts->doc_col != ULINT_UNDEFINED);
28032805

@@ -2808,7 +2810,13 @@ fts_cmp_set_sync_doc_id(
28082810

28092811
fts_table.parent = table->name.m_name;
28102812

2811-
trx = trx_allocate_for_background();
2813+
trx_allocated = false;
2814+
if (trx == NULL) {
2815+
trx = trx_allocate_for_background();
2816+
trx_allocated = true;
2817+
} else {
2818+
savept = trx_savept_take(trx);
2819+
}
28122820

28132821
trx->op_info = "update the next FTS document id";
28142822

@@ -2875,21 +2883,34 @@ fts_cmp_set_sync_doc_id(
28752883
func_exit:
28762884

28772885
if (error == DB_SUCCESS) {
2878-
fts_sql_commit(trx);
2886+
if (trx_allocated) {
2887+
fts_sql_commit(trx);
2888+
}
28792889
} else {
28802890
*doc_id = 0;
28812891

28822892
ib::error() << "(" << ut_strerr(error) << ") while getting"
28832893
" next doc id.";
2884-
fts_sql_rollback(trx);
2894+
if (trx_allocated) {
2895+
fts_sql_rollback(trx);
2896+
} else {
2897+
trx_rollback_to_savepoint(trx, &savept);
2898+
}
28852899

28862900
if (error == DB_DEADLOCK) {
28872901
os_thread_sleep(FTS_DEADLOCK_RETRY_WAIT);
2902+
if (trx_allocated) {
2903+
/* free trx before retry */
2904+
trx_free_for_background(trx);
2905+
trx = NULL;
2906+
}
28882907
goto retry;
28892908
}
28902909
}
28912910

2892-
trx_free_for_background(trx);
2911+
if (trx_allocated) {
2912+
trx_free_for_background(trx);
2913+
}
28932914

28942915
return(error);
28952916
}
@@ -4618,7 +4639,7 @@ fts_sync_commit(
46184639
/* After each Sync, update the CONFIG table about the max doc id
46194640
we just sync-ed to index table */
46204641
error = fts_cmp_set_sync_doc_id(sync->table, sync->max_doc_id, FALSE,
4621-
&last_doc_id);
4642+
&last_doc_id, trx);
46224643

46234644
/* Get the list of deleted documents that are either in the
46244645
cache or were headed there but were deleted before the add
@@ -4639,6 +4660,8 @@ fts_sync_commit(
46394660

46404661
if (error == DB_SUCCESS) {
46414662

4663+
DBUG_EXECUTE_IF("fts_crash_before_commit_sync", { DBUG_SUICIDE(); });
4664+
46424665
fts_sql_commit(trx);
46434666

46444667
} else if (error != DB_SUCCESS) {

0 commit comments

Comments
 (0)