默认情况下或使用 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_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)
以下示例更复杂。该查询返回相关性值,并且还按降序对行进行排序。要实现此结果,需要在SELECT
列表和MATCH()
函数中指定两次。这不会增加额外的开销,因为 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
。Note这种行为不适用于使用 ngram 解析器的FULLTEXT索引。对于 ngram 解析器,token 长度由
ngram_token_size
选项定义。 -
停止词列表中的单词将被忽略。停止词是指像 “the” 或 “some” 这样的词语,它们太常见,以至于被认为没有语义价值。有一个内置的停止词列表,但可以被用户定义的列表所覆盖。停止词列表和相关的配置选项对于
InnoDB
搜索索引和MyISAM
索引是不同的。停止词处理由配置选项innodb_ft_enable_stopword
、innodb_ft_server_stopword_table
和innodb_ft_user_stopword_table
控制对于InnoDB
搜索索引,而ft_stopword_file
控制MyISAM
索引。
请参阅 第 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 节,“布尔全文本搜索” 中解释的布尔搜索模式。