文档首页
MySQL 9.0 参考手册
相关文档 下载此手册
PDF (US Ltr) - 40.0Mb
PDF (A4) - 40.1Mb
手册页 (TGZ) - 258.2Kb
手册页 (Zip) - 365.3Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 9.0 参考手册  /  ...  /  自然语言全文搜索

14.9.1 自然语言全文搜索

默认情况下或使用 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_csutf8mb4_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() 函数中命名的列(titlebody)与 article 表的 FULLTEXT 索引定义中命名的列相同。要分别搜索 titlebody,您将为每一列创建单独的 FULLTEXT 索引。

您还可以执行布尔搜索或带查询扩展的搜索。这些搜索类型在 第 14.9.2 节,“布尔全文搜索”第 14.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 搜索索引,默认的全文搜索找到的单词的最小长度为 3 个字符,对于 MyISAM 则为 4 个字符。您可以在创建索引之前设置一个配置选项来控制截止值:innodb_ft_min_token_size 配置选项用于 InnoDB 搜索索引,或 ft_min_word_len 用于 MyISAM

    注意

    此行为不适用于使用 ngram 解析器的 FULLTEXT 索引。对于 ngram 解析器,令牌长度由 ngram_token_size 选项定义。

  • 停用词列表中的单词会被忽略。停用词是诸如 thesome 之类的单词,这些单词非常常见,因此被认为没有语义价值。有一个内置的停用词列表,但它可以被用户定义的列表覆盖。停用词列表和相关的配置选项对于 InnoDB 搜索索引和 MyISAM 搜索索引是不同的。停用词处理由配置选项 innodb_ft_enable_stopwordinnodb_ft_server_stopword_tableinnodb_ft_user_stopword_table (对于 InnoDB 搜索索引)以及 ft_stopword_file (对于 MyISAM 搜索索引)控制。

请参阅 第 14.9.4 节“全文停用词”,了解默认的停用词列表以及如何更改它们。默认的最小单词长度可以更改,如 第 14.9.6 节“微调 MySQL 全文搜索” 所述。

集合和查询中的每个正确单词都会根据其在集合或查询中的重要性进行加权。因此,存在于许多文档中的单词权重较低,因为它在该特定集合中的语义价值较低。相反,如果单词很少见,它会获得更高的权重。单词的权重被合并起来,用于计算行的相关性。这种技术最适合大型集合。

MyISAM 限制

对于非常小的表,单词分布不能充分反映它们的语义价值,并且这种模型有时会针对 MyISAM 表上的搜索索引产生奇怪的结果。例如,尽管单词 MySQL 存在于前面显示的 articles 表中的每一行中,但在 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 表上建立搜索索引,或者使用 第 14.9.2 节“布尔全文搜索” 中解释的布尔搜索模式。