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_cs
或utf8mb4_bin
来使其在全文搜索中大小写敏感。
当MATCH()
用于WHERE
子句,例如前面展示的示例中,返回的行自动按照相似度从高到低排序,只要满足以下条件:
-
不能存在明确的
ORDER BY
子句。 -
搜索必须使用全文索引扫描,而不是表扫描。
-
如果查询中join了表,full-text索引扫描必须是左侧非常量表。
考虑到上述条件,通常情况下指定使用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
解析器的全文索引中添加带有某些语言的单词或其他索引项,必须先对其进行预处理,使其以某个任意分隔符分开。或者,您可以使用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%的行中出现,于是被视为停用词。这项过滤技术对大规模数据集更合适,而不是小规模数据集,否则可能导致流行术语的结果不佳。
50%阈值可能会在您第一次尝试全文搜索时让您感到惊讶,使InnoDB
表更适合于全文搜索的实验。如果您创建一个MyISAM
表,并将一到两个文本行插入其中,那么文本中的每个单词都至少在50%的行中出现。结果,直到表包含更多行,所有搜索都不会返回任何结果。需要绕过50%限制的用户可以在InnoDB
表上建立搜索索引,也可以使用第14.9.2节,“布尔全文搜索”中解释的布尔搜索模式。