17.6.2.4 InnoDB 全文索引
全文索引是在基于文本的列(CHAR
、VARCHAR
或 TEXT
列)上创建,以加速查询和 DML 操作对这些列中的数据。
全文索引是作为 CREATE TABLE
语句的一部分定义,也可以使用 ALTER TABLE
或 CREATE INDEX
将其添加到现有表中。
全文搜索使用 MATCH() ... AGAINST
语法。有关使用信息,请见 第14.9节,“全文搜索函数”。
InnoDB
全文索引在以下主题中描述:
InnoDB
全文索引使用倒排索引设计。倒排索引存储单词列表,并为每个单词存储包含该单词的文档列表。为了支持近似搜索,各个单词的位置信息也被存储,作为字节偏移量。
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 |
+----------+----------------------------------------------------+-------+
第一个六个索引表组成了反向索引,并被称为辅助索引表。当 incoming 文档被 tokenized,单个词语(也被称为“tokens”)将与位置信息和关联的DOC_ID
一起插入到索引表中。这些词语完全排序并根据单个词语的首字符的字符集排序权重分配于六个索引表中。
反向索引被分割成六个辅助索引表以支持并行索引创建。默认情况下,两个线程将 tokenize、排序和插入词语和关联数据到索引表中。可以使用innodb_ft_sort_pll_degree
变量来配置这些线程的数量。在创建大型表的全文索引时,考虑增加线程数。
辅助索引表名称以fts_
开头,并以index_
结尾。每个辅助索引表都与被索引的表通过在辅助索引表名称中出现的十六进制值相关联,该十六进制值与被索引表的#
table_id
相匹配。例如,test/opening_lines
表的table_id
为327
,对应十六进制值为0x147。如前面的示例所示,在与test/opening_lines
表相关联的辅助索引表名称中将出现“147”十六进制值。
一个十六进制值,表示全文索引的index_id
。这个十六进制值也出现在辅助索引表名中。例如,在辅助表名test/fts_0000000000000147_00000000000001c9_0001
中,十六进制值1c9
的十进制值为457。定义在opening_lines
表上的索引(idx
)可以通过查询信息_schemaINNODB_INDEXES
表来确定,该值为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
,它标识已经被解析并flush到磁盘上的文档。在崩溃恢复中,FTS_SYNCED_DOC_ID
值用于标识尚未flush到磁盘的文档,以便重新解析并将其添加回全文索引缓存。要查看该表中的数据,请查询信息架构INNODB_FT_CONFIG
表。
当文档被插入时,它将被 tokenize,单个词语和关联数据将被插入到全文索引中。这过程,即使对小型文档,也可能导致多个小型插入操作对辅助索引表产生竞争。为了避免这个问题,InnoDB
使用了一个全文索引缓存来临时缓存最近插入行的索引表插入操作。这是一个内存缓存结构,它将保持插入操作直到缓存满,然后批量将其flush到磁盘(到辅助索引表)。您可以查询信息_schema中的INNODB_FT_INDEX_CACHE
表以查看最近插入行的tokenized数据。
缓存和批量flush行为避免了对辅助索引表的频繁更新,这可能在忙碌的插入和更新时导致并发访问问题。批处理技术也避免了同一个词语的多个插入操作,并且最小化了重复条目。相反,插入同一个词语的操作将被合并并flush到磁盘作为单个条目,从而提高插入效率,同时保持辅助索引表尽量小。
变量innodb_ft_cache_size
用于配置全文索引缓存大小(按表单),影响了全文索引缓存的flush频率。您还可以使用变量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
列,along with a unique index (FTS_DOC_ID_INDEX
) on the FTS_DOC_ID
column。如果您想自己创建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 解析器不使用降序索引。
允许的最大空隙是65535之间的最大的已用FTS_DOC_ID
值和新FTS_DOC_ID
值之间。
为了避免重建表,FTS_DOC_ID
列在删除全文索引时将被保留。
删除包含全文索引列的记录可能会在辅助索引表中导致许多小型删除操作,从而使这些表的并发访问成为瓶颈。为了避免这个问题,在从带有全文索引的表中删除记录时,系统将记录被删除的文档的DOC_ID
在特殊的FTS_*_DELETED
表中进行日志记录,并且删除后的记录仍然保留在全文索引中。在返回查询结果之前,系统将使用FTS_*_DELETED
表中的信息来过滤出已删除的DOC_ID
s。这种设计的优点是删除操作速度快且廉价。但是,这也意味着索引大小不会立即减少在删除记录后。要删除全文索引中已删除记录的条目,请运行带有innodb_optimize_fulltext_only=ON
参数的OPTIMIZE TABLE
语句,以重新构建全文索引。更多信息,请见Optimizing InnoDB Full-Text Indexes.
InnoDB
全文索引具有特殊的事务处理特性,这是由于其缓存和批处理行为。具体来说,更新和插入操作在事务提交时被处理,这意味着只有已提交的事务数据才能被搜索到。以下示例演示了这个行为。在插入行后,full-text search才会返回结果。
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 |
+----------+
监控 InnoDB 全文索引
您可以通过查询以下INFORMATION_SCHEMA
表来监控和检查InnoDB
全文索引的特殊文本处理方面:
您还可以通过查询INNODB_INDEXES
和INNODB_TABLES
来查看全文索引和表的基本信息。
更多信息,请见第17.15.4节,“InnoDB 信息架构全文索引表”。