14.9.2 布尔全文搜索
MySQL 可以使用IN BOOLEAN MODE
修饰符执行布尔全文搜索。使用这个修饰符,某些字符在搜索字符串的开始或结束处具有特殊含义。在以下查询中,+
和 -
操作符指示词语必须存在或不存在,以便匹配发生。因此,该查询检索所有包含词语“MySQL”但不包含词语“YourSQL”的行:
mysql> SELECT * FROM articles WHERE MATCH (title,body)
-> AGAINST ('+MySQL -YourSQL' IN BOOLEAN MODE);
+----+-----------------------+-------------------------------------+
| id | title | body |
+----+-----------------------+-------------------------------------+
| 1 | MySQL Tutorial | DBMS stands for DataBase ... |
| 2 | How To Use MySQL Well | After you went through a ... |
| 3 | Optimizing MySQL | In this tutorial, we show ... |
| 4 | 1001 MySQL Tricks | 1. Never run mysqld as root. 2. ... |
| 6 | MySQL Security | When configured properly, MySQL ... |
+----+-----------------------+-------------------------------------+
在实现这个特性时,MySQL 使用所谓的隐含布尔逻辑,其中
-
+
表示AND
-
-
表示NOT
-
[无操作符] 等同于
OR
布尔全文搜索具有这些特点:
-
它们不自动对行进行降序排序以根据相关性。
-
InnoDB
表需要在MATCH()
表达式中所有列上创建FULLTEXT
索引以执行布尔查询。对MyISAM
搜索索引的布尔查询可以在没有FULLTEXT
索引的情况下工作,虽然这样执行的查询速度非常慢。 -
全文参数的最小和最大单词长度适用于使用内置
FULLTEXT
解析器或MeCab插件创建的FULLTEXT
索引。innodb_ft_min_token_size
和innodb_ft_max_token_size
用于InnoDB
搜索索引。ft_min_word_len
和ft_max_word_len
用于MyISAM
搜索索引。对使用ngram解析器创建的
FULLTEXT
索引,不适用最小和最大单词长度参数。ngram token size由ngram_token_size
选项定义。 -
停用词列表适用于
InnoDB
搜索索引,控制由innodb_ft_enable_stopword
、innodb_ft_server_stopword_table
和innodb_ft_user_stopword_table
,适用于MyISAM
搜索索引由ft_stopword_file
控制。 -
InnoDB
全文搜索不支持在单个搜索词上使用多个操作符,例如:'++apple'
。使用多个操作符返回语法错误到标准输出。MyISAM 全文搜索成功处理同样的搜索,忽略所有操作符除了紧邻搜索词的操作符。 -
InnoDB
全文搜索只支持前导加号或减号。例如,'+apple'
是支持的,但不支持'apple+'
。指定尾部加号或减号导致InnoDB
报告语法错误。 -
InnoDB
全文搜索不支持使用前导加号与通配符 ('+*'
),加号和减号组合 ('+-'
),或前导加号和减号组合 ('+-apple'
)。这些无效查询返回语法错误。 -
InnoDB
全文搜索不支持在布尔全文搜索中使用@
符号。@
符号保留给@distance
proximity 搜索操作符。 -
它们不使用适用于
MyISAM
搜索索引的50%阈值。
布尔全文搜索能力支持以下操作符:
-
+
前导或尾部加号表示该词必须出现在每一行中返回的结果。
InnoDB
只支持前导加号。 -
-
leading 或 trailing 的减号表示该词语不能出现在返回的任何行中。
InnoDB
只支持 leading 减号。注意:
-
运算符只用于排除其他搜索条件匹配的行。因此,包含仅由-
前缀的 boolean 模式搜索将返回空结果,不会返回“除了包含被排除词语的所有行”。 -
(无操作符)
默认情况下(既不指定
+
也没有指定-
),该词语是可选的,但包含它的行被评分更高。这类似于MATCH() AGAINST()
没有IN BOOLEAN MODE
修饰符时的行为。 -
@
distance
这个操作符只在
InnoDB
表中工作。它测试两个或多个词语是否都在指定距离内开始, measured in words。将搜索词语置于双引号字符串中,紧接着@
运算符,例如distance
MATCH(col1) AGAINST('"word1 word2 word3" @8' IN BOOLEAN MODE)
-
> <
这两个操作符用于改变词语对行的相关值。
>
运算符增加相关值,<
运算符减少相关值。请看下面的示例。 -
( )
括号将表达式组合成子表达式。嵌套的括号组可以被使用。
-
~
前导波浪号作为否定操作符,导致该词语对行的相关性为负值。这对于标记“噪音”词语有用。包含这种词语的行被评估低于其他行,但不被完全排除,因为它将被使用
-
操作符排除。 -
*
星号作为截断(或通配)操作符。与其他操作符不同,它附加到要影响的词语中。词语匹配如果以前导星号操作符开始。
如果指定了截断操作符,词语即使太短或是停用词,也不会从布尔查询中删除。是否太短由
innodb_ft_min_token_size
设置决定,对于InnoDB
表,或者ft_min_word_len
对MyISAM
表。这些选项不适用于使用ngram解析器的FULLTEXT
索引。通配词语被认为是前缀,必须出现在一个或多个词语开始。如果最小词长为4,搜索
'+
可能返回比搜索word
+the*''+
少,因为第二个查询忽略了太短的搜索词语word
+the'the
。 -
"
双引号 (
"
) 中的短语只匹配包含该短语字面意思的行。全文搜索引擎将短语拆分成单词,并在FULLTEXT
索引中对单词进行搜索。非单词字符不需要精确匹配:短语搜索只要求结果包含短语中的同样单词,顺序也相同。例如,"test phrase"
匹配"test, phrase"
。如果短语中没有在索引中的单词,结果为空。单词可能不在索引中,因为它们可能不存在于文本中、是停用词或短于索引的最小长度。
以下示例演示了使用布尔全文操作符的搜索字符串:
-
'apple banana'
找到包含至少一个单词的行。
-
'+apple +juice'
找到包含两个单词的行。
-
'+apple macintosh'
找到包含单词“apple”,但排名更高如果也包含“macintosh”。
-
'+apple -macintosh'
找到包含单词“apple”但不包含“macintosh”。
-
'+apple ~macintosh'
找到包含单词“苹果”的行,但如果该行也包含单词“苹果电脑”“苹果-苹果电脑”不同,因为存在“苹果电脑”的行将不会被返回。
-
'+苹果+(turnover<strudel)'
找到包含单词“苹果”和“翻转”,或“苹果”和“STRUDEL”(顺序不限),并将“苹果翻转”评分高于“苹果STRUDEL”.
-
'苹果*'
找到包含单词如“苹果”、“苹果们”、“苹果酱”或“苹果树”的行。
-
'一些词语'
找到包含exact短语“一些词语”(例如,包含“一些智慧词语”但不包含“一些噪音词语”).注意,
"
字符将短语括起来,是操作符字符,不是搜索字符串本身的引号。
InnoDB全文搜索基于Sphinx全文搜索引擎,算法基于BM25和TF-IDF排名算法。因此,InnoDB
布尔全文搜索的相关性排名可能与MyISAM
相关性排名不同。
InnoDB
使用“词频-逆文档频率”(TF-IDF
)加权系统来评估一个文档对于给定的全文搜索查询的相关性。该TF-IDF
加权基于文档中词语出现的频率,减去文档集合中的词语出现频率。在其他字样,词语在文档中出现的频率越高,文档集合中的词语出现频率越低,文档排名越高。
词频(TF
)值是词语在文档中的出现次数。逆文档频率(IDF
)值使用以下公式计算,其中total_records
是文档集合的记录数,matching_records
是搜索词语在文档集合中的记录数。
${IDF} = log10( ${total_records} / ${matching_records} )
当文档中出现多次某个词语时,IDF值将被TF值乘以:
${TF} * ${IDF}
使用TF
和IDF
值,计算文档的相关性排名公式如下:
${rank} = ${TF} * ${IDF} * ${IDF}
公式在以下示例中进行了演示。
这是单词搜索的相关性排名计算示例。
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 (1.04 sec)
mysql> INSERT INTO articles (title,body) VALUES
-> ('MySQL Tutorial','This database tutorial ...'),
-> ("How To Use MySQL",'After you went through a ...'),
-> ('Optimizing Your Database','In this database tutorial ...'),
-> ('MySQL vs. YourSQL','When comparing databases ...'),
-> ('MySQL Security','When configured properly, MySQL ...'),
-> ('Database, Database, Database','database database database'),
-> ('1001 MySQL Tricks','1. Never run mysqld as root. 2. ...'),
-> ('MySQL Full-Text Indexes', 'MySQL fulltext indexes use a ..');
Query OK, 8 rows affected (0.06 sec)
Records: 8 Duplicates: 0 Warnings: 0
mysql> SELECT id, title, body,
-> MATCH (title,body) AGAINST ('database' IN BOOLEAN MODE) AS score
-> FROM articles ORDER BY score DESC;
+----+------------------------------+-------------------------------------+---------------------+
| id | title | body | score |
+----+------------------------------+-------------------------------------+---------------------+
| 6 | Database, Database, Database | database database database | 1.0886961221694946 |
| 3 | Optimizing Your Database | In this database tutorial ... | 0.36289870738983154 |
| 1 | MySQL Tutorial | This database tutorial ... | 0.18144935369491577 |
| 2 | How To Use MySQL | After you went through a ... | 0 |
| 4 | MySQL vs. YourSQL | When comparing databases ... | 0 |
| 5 | MySQL Security | When configured properly, MySQL ... | 0 |
| 7 | 1001 MySQL Tricks | 1. Never run mysqld as root. 2. ... | 0 |
| 8 | MySQL Full-Text Indexes | MySQL fulltext indexes use a .. | 0 |
+----+------------------------------+-------------------------------------+---------------------+
8 rows in set (0.00 sec)
总共有8条记录,其中3条记录匹配“数据库”搜索关键字。第一个记录(id 6
)包含搜索关键字6次,相关性排名为1.0886961221694946
。这个排名值是使用TF
值6(在记录id 6
中“数据库”搜索关键字出现6次)和IDF
值0.42596873216370745计算的,该IDF
值是根据总共8条记录和搜索关键字在其中3条记录中计算的:
${IDF} = LOG10( 8 / 3 ) = 0.42596873216370745
然后将TF
和IDF
值输入排名公式:
${rank} = ${TF} * ${IDF} * ${IDF}
在MySQL命令行客户端执行计算返回排名值为1.088696164686938。
mysql> SELECT 6*LOG10(8/3)*LOG10(8/3);
+-------------------------+
| 6*LOG10(8/3)*LOG10(8/3) |
+-------------------------+
| 1.088696164686938 |
+-------------------------+
1 row in set (0.00 sec)
您可能会注意到SELECT ... MATCH ... AGAINST
语句和MySQL命令行客户端(1.0886961221694946
versus 1.088696164686938
)返回的排名值之间有一些差异。这是因为InnoDB
内部对整数和浮点/双精度类型的转换方式,以及相关的精度和四舍五入决策,而在其他地方,例如MySQL命令行客户端或其他计算器中进行的转换方式不同。
这个示例演示了基于articles
表和前一个示例中的数据进行多词全文搜索排名计算。
如果您搜索多个单词,排名值是每个单词的排名值之和,如下公式所示:
${rank} = ${TF} * ${IDF} * ${IDF} + ${TF} * ${IDF} * ${IDF}
对两个术语('mysql tutorial')执行搜索返回以下结果:
mysql> SELECT id, title, body, MATCH (title,body)
-> AGAINST ('mysql tutorial' IN BOOLEAN MODE) AS score
-> FROM articles ORDER BY score DESC;
+----+------------------------------+-------------------------------------+----------------------+
| id | title | body | score |
+----+------------------------------+-------------------------------------+----------------------+
| 1 | MySQL Tutorial | This database tutorial ... | 0.7405621409416199 |
| 3 | Optimizing Your Database | In this database tutorial ... | 0.3624762296676636 |
| 5 | MySQL Security | When configured properly, MySQL ... | 0.031219376251101494 |
| 8 | MySQL Full-Text Indexes | MySQL fulltext indexes use a .. | 0.031219376251101494 |
| 2 | How To Use MySQL | After you went through a ... | 0.015609688125550747 |
| 4 | MySQL vs. YourSQL | When comparing databases ... | 0.015609688125550747 |
| 7 | 1001 MySQL Tricks | 1. Never run mysqld as root. 2. ... | 0.015609688125550747 |
| 6 | Database, Database, Database | database database database | 0 |
+----+------------------------------+-------------------------------------+----------------------+
8 rows in set (0.00 sec)
在第一个记录(id 8
),'mysql'出现一次,'tutorial'出现两次。对于'mysql'有六个匹配记录,对于'tutorial'有两个匹配记录。MySQL命令行客户端返回了多词搜索排名值的预期结果:
mysql> SELECT (1*log10(8/6)*log10(8/6)) + (2*log10(8/2)*log10(8/2));
+-------------------------------------------------------+
| (1*log10(8/6)*log10(8/6)) + (2*log10(8/2)*log10(8/2)) |
+-------------------------------------------------------+
| 0.7405621541938003 |
+-------------------------------------------------------+
1 row in set (0.00 sec)
前一个示例中SELECT ... MATCH ... AGAINST
语句和MySQL命令行客户端返回的排名值差异解释见上文。