译自MySQL8官方文档 12.9.1 自然语言全文搜索
作者:谎言诞行    发布时间:2024年02月04日

前言

最近在学习和了解 MySQL 8 自带的全文搜索功能。自己在看资料的时候顺便翻译了一下,希望对大家有用。(章节序号可能有所差异,因为官方文档会不定期更新)

官方原文链接如下:《Natural Language Full-Text Searches》


默认没有修饰符的情况下,或者指定 IN NATURAL LANGUAGE MODE 修饰符,MATCH() 函数会根据文本集合对字符串执行自然语言搜索。集合是包含在 FULLTEXT 索引中的一列或者多列。搜索字符串作为 AGAINST() 的参数给出。对于表中的每一行,MATCH() 返回一个相关性值; 也就是说,搜索字符串与 MATCH() 列表中命名的列中该行中的文本之间的相似性度量。

mysql> CREATE TABLE articles (
    ->   id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
    ->   title VARCHAR(200),
    ->   body TEXT,
    ->   FULLTEXT (title,body)
    -> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.08 sec)
mysql> INSERT INTO articles (title,body) VALUES
    ->   ('MySQL Tutorial','DBMS stands for DataBase ...'),
    ->   ('How To Use MySQL Well','After you went through a ...'),
    ->   ('Optimizing MySQL','In this tutorial, we show ...'),
    ->   ('1001 MySQL Tricks','1. Never run mysqld as root. 2. ...'),
    ->   ('MySQL vs. YourSQL','In the following database comparison ...'),
    ->   ('MySQL Security','When configured properly, MySQL ...');
Query OK, 6 rows affected (0.01 sec)
Records: 6  Duplicates: 0  Warnings: 0
mysql> SELECT * FROM articles
    -> WHERE MATCH (title,body)
    -> AGAINST ('database' IN NATURAL LANGUAGE MODE);
+----+-------------------+------------------------------------------+
| id | title             | body                                     |
+----+-------------------+------------------------------------------+
|  1 | MySQL Tutorial    | DBMS stands for DataBase ...             |
|  5 | MySQL vs. YourSQL | In the following database comparison ... |
+----+-------------------+------------------------------------------+
2 rows in set (0.00 sec)

默认情况下,搜索以不区分大小写的方式执行。若要执行区分大小写的全文搜索,请对索引列使用区分大小写或二进制排序规则。例如,可以为 使用 utf8mb4 字符集的列分配 utf8mb4_0900_as_cs 或 utf8mb4_bin 的排序规则,使其在全文搜索中区分大小写。

当 MATCH() 用于 WHERE 子句时,如前面所示的示例所示,只要满足以下条件,返回的行就会首先自动以最高相关性排序:

  • 不得有明确的 ORDER BY 子句。

  • 必须使用全文索引扫描而不是表扫描来执行搜索。

  • 如果查询联接表,则全文索引扫描必须是联接中最左边的非常量表。

鉴于刚才列出的条件,在必要或需要时,使用 ORDER BY 指定显式排序顺序通常不那么费力。相关性值是非负浮点数。零相关性意味着没有相似性。相关性是根据行(文档)中的字数、行中唯一字数、集合中的总字数以及包含特定字词的行数来计算的。

注意

术语“文档”可以与术语“行”互换使用,这两个术语都是指行的索引部分。术语“集合”是指索引列,包括所有行。

若要简单地计算匹配项,可以使用如下查询:

mysql> SELECT COUNT(*) FROM articles
    -> WHERE MATCH (title,body)
    -> AGAINST ('database' IN NATURAL LANGUAGE MODE);
+----------+
| COUNT(*) |
+----------+
|        2 |
+----------+
1 row in set (0.00 sec)

您可能会发现重写查询会更快,如下所示:

mysql> SELECT
    -> COUNT(IF(MATCH (title,body) AGAINST ('database' IN NATURAL LANGUAGE MODE), 1, NULL))
    -> AS count
    -> FROM articles;
+-------+
| count |
+-------+
|     2 |
+-------+
1 row in set (0.03 sec)

第一个查询,执行一些额外的工作(按相关性对结果进行排序),但也可以使用基于 WHERE 子句的索引查找。 如果搜索匹配的行数很少,则索引查找可能会使第一个查询更快。

第二个查询,执行全表扫描,如果搜索词存在于大多数行中,则可能比索引查找更快。


对于自然语言全文搜索,MATCH() 函数中命名的列必须与表中某些 FULLTEXT 索引中包含的列相同。对于前面的查询,请注意,MATCH() 函数中命名的列(标题和正文)与文章表的 FULLTEXT 索引定义中命名的列相同。若要单独搜索标题或正文,请为每列创建单独的 FULLTEXT 索引。

您还可以执行“布尔搜索”或“带有查询扩展的搜索”。这些搜索类型在以下章节《12.9.2 布尔全文搜索》, 和 《12.9.3 具有查询扩展功能的全文搜索》

使用索引的全文搜索只能从 MATCH()子句中的单个表命名列,因为索引不能跨越多个表。对于 MyISAM 表,可以在没有索引的情况下进行布尔搜索(尽管速度较慢),在这种情况下,可以从多个表中命名列。

前面的示例是一个基本图示,演示如何使用 MATCH() 函数,其中行按相关性递减的顺序返回。下一个示例演示如何显式检索相关性值。返回的行不排序,因为SELECT语句既不包含WHERE也不包含ORDER BY子句:

mysql> SELECT id, MATCH (title,body)
    -> AGAINST ('Tutorial' IN NATURAL LANGUAGE MODE) AS score
    -> FROM articles;
+----+---------------------+
| id | score               |
+----+---------------------+
|  1 | 0.22764469683170319 |
|  2 |                   0 |
|  3 | 0.22764469683170319 |
|  4 |                   0 |
|  5 |                   0 |
|  6 |                   0 |
+----+---------------------+
6 rows in set (0.00 sec)

以下示例更为复杂。该查询返回相关性值,并按相关性递减的顺序对行进行排序。要达到此结果,请指定 MATCH()两次:一次在 SELECT 列表中,一次在 WHERE 子句中。 这不会导致额外的开销,因为 MySQL 优化器会注意到两个MATCH()调用是相同的,并且只调用一次全文搜索代码

mysql> SELECT id, body, MATCH (title,body)
    ->   AGAINST ('Security implications of running MySQL as root'
    ->   IN NATURAL LANGUAGE MODE) AS score
    -> FROM articles
    ->   WHERE MATCH (title,body) 
    ->   AGAINST('Security implications of running MySQL as root'
    ->   IN NATURAL LANGUAGE MODE);
+----+-------------------------------------+-----------------+
| id | body                                | score           |
+----+-------------------------------------+-----------------+
|  4 | 1. Never run mysqld as root. 2. ... | 1.5219271183014 |
|  6 | When configured properly, MySQL ... | 1.3114095926285 |
+----+-------------------------------------+-----------------+
2 rows in set (0.00 sec)

用双引号 (“) 字符括起来的短语仅与按字面意思包含该短语的行匹配,就像键入该短语一样。全文引擎将短语拆分为多个单词,并在 FULLTEXT 索引中搜索这些单词。非字字符不需要完全匹配:短语搜索仅要求匹配项包含与短语完全相同且顺序相同的单词。例如,“test phrase”与“test, phrase”匹配。如果短语不包含索引中的单词,则结果为空。例如,如果所有单词都是停用字短于索引字词的最小长度,则结果为空。

MySQL FULLTEXT 实现将任何真字字符(字母、数字和下划线)序列视为一个单词。该序列也可能包含撇号 ('),但不能连续超过一个。这意味着 aaa'bbb 被视为一个词,但 aaa''bbb 被视为两个词。单词开头或结尾的撇号由 FULLTEXT 解析器剥离; 'aaa'bbb' 将被解析为 aaa'bbb。

内置的 FULLTEXT 解析器通过查找某些分隔符来确定单词的开始和结束位置; 例如,(空格)、(逗号)和。(句点)。如果单词之间没有分隔符(例如,中文),则内置的 FULLTEXT 解析器无法确定单词的开始或结束位置。为了能够将此类语言中的单词或其他索引术语添加到使用内置 FULLTEXT 分析器的 FULLTEXT 索引中,必须对它们进行预处理,以便用任意分隔符分隔它们。或者,您可以使用 ngram 解析器插件(适用于中文、日语或韩语)或 MeCab 解析器插件(适用于日语)创建 FULLTEXT索引。

可以编写一个插件来替换内置的全文解析器。有关详细信息,请参阅 MySQL 插件 API。例如,解析器插件源代码,请参阅 MySQL 源代码发行版的 plugin/fulltext 目录。


在全文搜索中会忽略某些单词:

  • 任何太短的单词都会被忽略。对于InnoDB搜索索引,全文搜索找到的默认最小单词长度为三个字符,对于MyISAM,则为四个字符。您可以通过在创建索引之前设置配置选项来控制截止:innodb_ft_min_token_size  InnoDB 搜索索引的配置选项,或者 ft_min_word_len 对于MyISAM。
    注意:
    此行为不适用于使用 ngram 解析器的 FULLTEXT 索引。对于 ngram 解析器,令牌长度由 ngram_token_size 选项控制。

  • 所有在停用字列表中的单词都将被忽略。停用字是诸如“the”或“some”之类的词,它非常常见,以至于被认为语义值为零。有一个内置的停用字列表,但它可以被用户定义的列表覆盖。InnoDB 搜索索引和 MyISAM 搜索索引的停用字列表和相关配置选项是不同的。停用字处理由以下配置选项控制:对于InnoDB 搜索索引 innodb_ft_enable_stopwordinnodb_ft_server_stopword_table, 和innodb_ft_user_stopword_table, 对于MyISAM索引是 ft_stopword_file 。

在 《12.9.4 全文搜索的停用字》 可以查看默认停用字列表以及如何更改它们。默认的最小字长可以按照说明进行更改,参阅 《12.9.6 微调 MySQL 全文搜索》

集合和查询中的每个正确单词都根据其在集合或查询中的重要性进行加权。因此,存在于许多文档中的单词具有较低的权重,因为它在此特定集合中具有较低的语义值。相反,如果这个词是罕见的,它就会获得更高的权重。单词的权重组合在一起以计算行的相关性。此技术最适合大型集合。


MyISAM 局限性

对于非常小的表,单词分布不能充分反映其语义值,并且此模型有时可能会为 MyISAM 表上的搜索索引生成奇怪的结果。例如,尽管前面显示的articles表的每一行中都存在单词“MySQL”,但在 MyISAM 搜索索引中搜索该单词不会产生任何结果:

mysql> SELECT * FROM articles
    -> WHERE MATCH (title,body)
    -> AGAINST ('MySQL' IN NATURAL LANGUAGE MODE);
Empty set (0.00 sec)

搜索结果为空,因为单词“MySQL”至少出现在 50% 的行中,因此实际上被视为停用字。这种筛选技术更适合于大型数据集,在大型数据集中,您可能不希望结果集每隔一行从 1GB 表中返回一次,而对于小型数据集,它可能会导致常用术语的结果不佳。当您第一次尝试全文搜索以了解其工作原理时,50% 的阈值可能会让您感到惊讶,并使 InnoDB 表更适合进行全文搜索的实验。

如果创建 MyISAM 表并仅向其中插入一行或两行文本,则文本中的每个单词都至少出现在 50% 的行中。 因此,在表包含更多行之前,搜索不会返回任何结果。需要绕过 50% 限制的用户可以在 InnoDB 表上构建搜索索引,或者使用布尔搜索模式,参阅《12.9.2 布尔全文搜索》


填写一种颜色的字符(不区分大小写),1分钟有效,点击图片刷新