全文索引是在基于文本的列(CHAR
、VARCHAR
或 TEXT
列)上创建的,以加速查询和 DML 操作在这些列中的数据。
全文索引可以作为 CREATE TABLE
语句的一部分定义,也可以使用 ALTER TABLE
或 CREATE INDEX
添加到现有表中。
全文搜索使用 MATCH() ... AGAINST
语法。有关使用信息,请参阅 第 14.9 节,“全文搜索函数”。
InnoDB
全文索引在本节中的以下主题下进行描述:
当创建 InnoDB
全文索引时,将创建一组索引表,如下所示:
mysql> CREATE TABLE opening_lines (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
opening_line TEXT(500),
author VARCHAR(200),
title VARCHAR(200),
FULLTEXT idx (opening_line)
) ENGINE=InnoDB;
mysql> SELECT table_id, name, space from INFORMATION_SCHEMA.INNODB_TABLES
WHERE name LIKE 'test/%';
+----------+----------------------------------------------------+-------+
| table_id | name | space |
+----------+----------------------------------------------------+-------+
| 333 | test/fts_0000000000000147_00000000000001c9_index_1 | 289 |
| 334 | test/fts_0000000000000147_00000000000001c9_index_2 | 290 |
| 335 | test/fts_0000000000000147_00000000000001c9_index_3 | 291 |
| 336 | test/fts_0000000000000147_00000000000001c9_index_4 | 292 |
| 337 | test/fts_0000000000000147_00000000000001c9_index_5 | 293 |
| 338 | test/fts_0000000000000147_00000000000001c9_index_6 | 294 |
| 330 | test/fts_0000000000000147_being_deleted | 286 |
| 331 | test/fts_0000000000000147_being_deleted_cache | 287 |
| 332 | test/fts_0000000000000147_config | 288 |
| 328 | test/fts_0000000000000147_deleted | 284 |
| 329 | test/fts_0000000000000147_deleted_cache | 285 |
| 327 | test/opening_lines | 283 |
+----------+----------------------------------------------------+-------+
前六个索引表组成了倒排索引,也称为辅助索引表。当传入文档被标记化时,单词(也称为““tokens””)将被插入索引表中,连同位置信息和关联的 DOC_ID
。单词被完全排序并根据单词的第一个字符的字符集排序权重分区到六个索引表中。
倒排索引被分区到六个辅助索引表中,以支持并行索引创建。默认情况下,两个线程对传入文档进行标记化、排序和插入索引表中。可以使用 innodb_ft_sort_pll_degree
变量配置线程数。在创建大表的全文索引时,考虑增加线程数。
辅助索引表的名称以 fts_
开头,以 index_
结尾。每个辅助索引表都与索引表关联,通过表的 #
table_id
在辅助索引表名称中的十六进制值来关联。例如,test/opening_lines
表的 table_id
是 327
,十六进制值为 0x147
。如前面的示例所示,十六进制值 ““147””
出现在与 test/opening_lines
表关联的辅助索引表名称中。
十六进制值表示全文索引的 index_id
也出现在辅助索引表名中。例如,在辅助表名 test/fts_0000000000000147_00000000000001c9_index_1
中,十六进制值 1c9
的十进制值为 457。可以通过查询 Information Schema INNODB_INDEXES
表来标识在 opening_lines
表 (idx
) 上定义的索引(457)。
mysql> SELECT index_id, name, table_id, space from INFORMATION_SCHEMA.INNODB_INDEXES
WHERE index_id=457;
+----------+------+----------+-------+
| index_id | name | table_id | space |
+----------+------+----------+-------+
| 457 | idx | 327 | 283 |
+----------+------+----------+-------+
索引表存储在自己的表空间中,如果主表是在 文件每表 表空间中创建的。否则,索引表存储在索引表所在的表空间中。
前面的示例中显示的其他索引表称为公共索引表,用于删除处理和存储全文索引的内部状态。与每个全文索引创建的倒排索引表不同,这组表是特定表上的所有全文索引的公共部分。
即使删除了全文索引,公共索引表也将保留。删除全文索引时,创建的 FTS_DOC_ID
列将保留,因为删除 FTS_DOC_ID
列将需要重建之前索引的表。公共索引表用于管理 FTS_DOC_ID
列。
-
fts_*_deleted
和fts_*_deleted_cache
包含已删除但数据尚未从全文索引中删除的文档 ID(DOC_ID)。
fts_*_deleted_cache
是fts_*_deleted
表的内存版本。 -
fts_*_being_deleted
和fts_*_being_deleted_cache
包含正在删除的文档 ID(DOC_ID),其数据当前正在从全文索引中删除。
fts_*_being_deleted_cache
表是fts_*_being_deleted
表的内存版本。 -
fts_*_config
存储全文索引的内部状态信息。最重要的是,它存储了
FTS_SYNCED_DOC_ID
,该值标识了已解析和刷新到磁盘的文档。在崩溃恢复期间,FTS_SYNCED_DOC_ID
值用于标识未刷新到磁盘的文档,以便重新解析并将其添加回全文索引缓存中。要查看该表中的数据,请查询 Information SchemaINNODB_FT_CONFIG
表。
当文档被插入时,它将被标记化,并将单个词和相关数据插入到全文索引中。这个过程,即使对于小文档,也可能会导致许多小的插入到辅助索引表中,从而使得这些表的并发访问成为一个点 contention。InnoDB
使用全文索引缓存来临时缓存索引表插入,以避免这个问题。这个内存缓存结构将插入缓存,直到缓存满,然后批量将其刷新到磁盘(到辅助索引表)。您可以查询 Information Schema INNODB_FT_INDEX_CACHE
表以查看最近插入的行的标记化数据。
缓存和批量刷新行为避免了频繁更新辅助索引表,这可能会在繁忙的插入和更新期间导致并发访问问题。批处理技术还避免了相同单词的多次插入,并将插入合并为单个条目,从而提高插入效率并将辅助索引表保持尽可能小。
变量 innodb_ft_cache_size
用于配置全文索引缓存大小(每个表的基础上),这将影响全文索引缓存何时刷新。您还可以使用 innodb_ft_total_cache_size
变量定义全文索引缓存的全局大小限制,以便在给定的实例中应用于所有表。
全文索引缓存存储与辅助索引表相同的信息。然而,全文索引缓存仅缓存最近插入行的tokenized数据。已经刷新到磁盘(到辅助索引表)的数据不会在查询时被带回全文索引缓存。辅助索引表中的数据将被直接查询,并与全文索引缓存中的结果合并,然后返回。
InnoDB
使用一个唯一的文档标识符,称为 DOC_ID
,将全文索引中的单词映射到文档记录中出现的位置。该映射需要在索引表上定义一个 FTS_DOC_ID
列。如果没有定义 FTS_DOC_ID
列,InnoDB
将在创建全文索引时自动添加一个隐藏的 FTS_DOC_ID
列。以下示例演示了这种行为。
以下表定义不包括 FTS_DOC_ID
列:
mysql> CREATE TABLE opening_lines (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
opening_line TEXT(500),
author VARCHAR(200),
title VARCHAR(200)
) ENGINE=InnoDB;
当您使用 CREATE FULLTEXT INDEX
语法在表上创建全文索引时,将返回一个警告,报告 InnoDB
正在重建表以添加 FTS_DOC_ID
列。
mysql> CREATE FULLTEXT INDEX idx ON opening_lines(opening_line);
Query OK, 0 rows affected, 1 warning (0.19 sec)
Records: 0 Duplicates: 0 Warnings: 1
mysql> SHOW WARNINGS;
+---------+------+--------------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------------+
| Warning | 124 | InnoDB rebuilding table to add column FTS_DOC_ID |
+---------+------+--------------------------------------------------+
同样的警告也将在使用 ALTER TABLE
添加全文索引到没有 FTS_DOC_ID
列的表时返回。如果您在 CREATE TABLE
时创建全文索引并且不指定 FTS_DOC_ID
列,InnoDB
将添加一个隐藏的 FTS_DOC_ID
列,不会警告。
在 CREATE TABLE
时定义 FTS_DOC_ID
列比创建全文索引时更便宜。如果在加载数据之前定义了 FTS_DOC_ID
列,那么表和其索引不需要重建以添加新列。如果您不关心 CREATE FULLTEXT INDEX
的性能,可以省略 FTS_DOC_ID
列,让 InnoDB
为您创建它。InnoDB
将创建一个隐藏的 FTS_DOC_ID
列和一个唯一索引(FTS_DOC_ID_INDEX
)在 FTS_DOC_ID
列上。如果您想创建自己的 FTS_DOC_ID
列,列必须定义为 BIGINT UNSIGNED NOT NULL
并命名为 FTS_DOC_ID
(所有大写),如下所示:
FTS_DOC_ID
列不需要定义为 AUTO_INCREMENT
列,但这样做可能使加载数据更容易。
mysql> CREATE TABLE opening_lines (
FTS_DOC_ID BIGINT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
opening_line TEXT(500),
author VARCHAR(200),
title VARCHAR(200)
) ENGINE=InnoDB;
如果您选择定义 FTS_DOC_ID
列,您需要负责管理该列以避免空值或重复值。FTS_DOC_ID
值不能被重用,这意味着 FTS_DOC_ID
值必须不断增加。
可选地,您可以创建所需的唯一 FTS_DOC_ID_INDEX
(所有大写)在 FTS_DOC_ID
列上。
mysql> CREATE UNIQUE INDEX FTS_DOC_ID_INDEX on opening_lines(FTS_DOC_ID);
如果您不创建 FTS_DOC_ID_INDEX
,InnoDB
将自动创建它。
FTS_DOC_ID_INDEX
不能定义为降序索引,因为 InnoDB
SQL 解析器不使用降序索引。
允许的最大 FTS_DOC_ID
值和新 FTS_DOC_ID
值之间的间隙为 65535。
为了避免重建表,FTS_DOC_ID
列将在删除全文索引时保留。
删除带有全文索引列的记录可能会在辅助索引表中导致许多小删除,从而使得这些表的并发访问成为一个争用点。为了避免这个问题,每当从索引表中删除记录时,删除的文档的 DOC_ID
都将被记录在特殊的 FTS_*_DELETED
表中,而索引记录仍保留在全文索引中。在返回查询结果之前,使用 FTS_*_DELETED
表中的信息来过滤掉已删除的 DOC_ID
。这种设计的优点是删除操作非常快捷和廉价。缺点是索引的大小不会立即减少删除记录。要删除已删除记录的全文索引条目,请在索引表上运行 OPTIMIZE TABLE
,并将 innodb_optimize_fulltext_only=ON
,以重建全文索引。有关更多信息,请参阅 优化 InnoDB 全文索引。
InnoDB
全文索引具有特殊的事务处理特性,因为其缓存和批处理行为。特别是,全文索引上的更新和插入是在事务提交时处理的,这意味着全文搜索只能看到已提交的数据。以下示例演示了这种行为。全文搜索只有在插入的行被提交后才返回结果。
mysql> CREATE TABLE opening_lines (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
opening_line TEXT(500),
author VARCHAR(200),
title VARCHAR(200),
FULLTEXT idx (opening_line)
) ENGINE=InnoDB;
mysql> BEGIN;
mysql> INSERT INTO opening_lines(opening_line,author,title) VALUES
('Call me Ishmael.','Herman Melville','Moby-Dick'),
('A screaming comes across the sky.','Thomas Pynchon','Gravity\'s Rainbow'),
('I am an invisible man.','Ralph Ellison','Invisible Man'),
('Where now? Who now? When now?','Samuel Beckett','The Unnamable'),
('It was love at first sight.','Joseph Heller','Catch-22'),
('All this happened, more or less.','Kurt Vonnegut','Slaughterhouse-Five'),
('Mrs. Dalloway said she would buy the flowers herself.','Virginia Woolf','Mrs. Dalloway'),
('It was a pleasure to burn.','Ray Bradbury','Fahrenheit 451');
mysql> SELECT COUNT(*) FROM opening_lines WHERE MATCH(opening_line) AGAINST('Ishmael');
+----------+
| COUNT(*) |
+----------+
| 0 |
+----------+
mysql> COMMIT;
mysql> SELECT COUNT(*) FROM opening_lines WHERE MATCH(opening_line) AGAINST('Ishmael');
+----------+
| COUNT(*) |
+----------+
| 1 |
+----------+
您可以通过查询以下 INFORMATION_SCHEMA
表来监控和检查 InnoDB
全文索引的特殊文本处理方面:
您还可以通过查询 INNODB_INDEXES
和 INNODB_TABLES
来查看全文索引和表的基本信息。
有关更多信息,请参阅 第 17.15.4 节,“InnoDB INFORMATION_SCHEMA FULLTEXT 索引表”。