默认情况下,或者使用 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()
函数中命名的列(title
和 body
)与 article
表的 FULLTEXT
索引定义中命名的列相同。要分别搜索 title
或 body
,您需要为每列创建单独的 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
搜索索引,全文搜索找到的单词的默认最小长度为三个字符,对于MyISAM
,则为四个字符。 您可以在创建索引之前通过设置配置选项来控制截止值:InnoDB
搜索索引的innodb_ft_min_token_size
配置选项,或MyISAM
的ft_min_word_len
。注意此行为不适用于使用 ngram 解析器的
FULLTEXT
索引。 对于 ngram 解析器,标记长度由ngram_token_size
选项定义。停用词列表中的单词将被忽略。 停用词是指像 “the” 或 “some” 这样常见的词,以至于它被认为没有语义值。 有一个内置的停用词列表,但它可以被用户定义的列表覆盖。
InnoDB
搜索索引和MyISAM
搜索索引的停用词列表和相关配置选项不同。 停用词处理由InnoDB
搜索索引的配置选项innodb_ft_enable_stopword
、innodb_ft_server_stopword_table
和innodb_ft_user_stopword_table
以及MyISAM
的ft_stopword_file
控制。
请参阅 第 14.9.4 节“全文停用词”,以查看默认停用词列表以及如何更改它们。 默认最小单词长度可以按照 第 14.9.6 节“微调 MySQL 全文搜索” 中的描述进行更改。
集合和查询中的每个正确单词都根据其在集合或查询中的重要性进行加权。 因此,出现在许多文档中的单词的权重较低,因为它在此特定集合中的语义值较低。 相反,如果这个词很罕见,它会得到更高的权重。 单词的权重被组合起来计算行的相关性。 此技术最适合大型集合。
对于非常小的表,单词分布不能充分反映它们的语义值,并且此模型有时可能会对 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 节“布尔全文搜索” 中解释的布尔搜索模式。