Documentation Home
MySQL 8.4 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 39.8Mb
PDF (A4) - 39.9Mb
Man Pages (TGZ) - 257.9Kb
Man Pages (Zip) - 364.9Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 8.4 Reference Manual  /  ...  /  Natural Language Full-Text Searches

14.9.1 自然语言全文搜索

默认情况下或使用IN NATURAL LANGUAGE MODE修饰符,函数MATCH()对字符串执行自然语言搜索,对于一个文本集合。集合是包含在FULLTEXT索引中的一个或多个列。搜索字符串作为AGAINST()函数的参数。对于表中的每一行,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子句。

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

  • 如果查询中join了表,full-text索引扫描必须是左侧非常量表。

考虑到上述条件,通常情况下指定使用ORDER BY明确的排序顺序是必要或有用的。

相似度值为非负浮点数。零相似度表示无相似度。相似度根据行(文档)中的词语数量、行中唯一词语数量、总词语数量和包含特定词语的行数量计算。

Note

术语“文档可以与术语“互换,两个术语都指向索引部分的行。术语“集合指向索引列,并包含所有行。

为了简单地计数匹配,可以使用以下查询:

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语句不包含WHEREORDER 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解析器的全文索引中添加带有某些语言的单词或其他索引项,必须先对其进行预处理,使其以某个任意分隔符分开。或者,您可以使用ngram解析器插件(用于中文、日语或韩语)或MeCab解析器插件(用于日语)创建FULLTEXT索引。

也可以编写一个替换内置全文解析器的插件。详细信息请见MySQL 插件 API。例如插件源代码,请查看 MySQL 源代码分布中的 plugin/fulltext 目录。

在全文搜索中忽略的一些单词:

  • 任何太短的单词都被忽略。全文搜索默认找到单词的最小长度为 InnoDB 搜索索引中的三个字符或 MyISAM 的四个字符。你可以在创建索引前设置配置选项来控制截断:innodb_ft_min_token_size 配置选项用于 InnoDB 搜索索引,或者ft_min_word_len 用于 MyISAM

    Note

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

  • 停用词列表中的单词被忽略。停用词是指像““the””或““some””这样非常常见的单词,认为它没有语义价值。有一个内置的停用词列表,但可以被用户定义的列表覆盖。停用词列表和相关配置选项对 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 Limitation

对于非常小的表,单词分布不能充分反映它们的语义价值,这种模型可能在 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%的行中出现,于是被视为停用词。这项过滤技术对大规模数据集更合适,而不是小规模数据集,否则可能导致流行术语的结果不佳。

50%阈值可能会在您第一次尝试全文搜索时让您感到惊讶,使InnoDB表更适合于全文搜索的实验。如果您创建一个MyISAM表,并将一到两个文本行插入其中,那么文本中的每个单词都至少在50%的行中出现。结果,直到表包含更多行,所有搜索都不会返回任何结果。需要绕过50%限制的用户可以在InnoDB表上建立搜索索引,也可以使用第14.9.2节,“布尔全文搜索”中解释的布尔搜索模式。