MySQL全文检索fulltext和中日韩文解析插件ngram使用记录

版权声明:本文为博主原创文章,转载敬请作出引用声明方便相互交流学习! https://blog.csdn.net/sweeper_freedoman/article/details/82847754

项目数据库中遇到一个情况,有一个字段存储的是经销厂商在工商局注册时的(官方)全称,但是用户在查询这个字段时,很多时候都会使用到各种各样的简称别称。于是,对于该字段,希望能够实现类似于搜索引擎的查询。就像数据库里面存的是“中华人民共和国”,但希望是只要用户输入“中国”、“中华”、“民国”、“中华民国”等关键字,最终都能定位到“中华人民共和国”的记录。这种情况下模糊查询和正则表达式有点捉襟见肘了,只能寄希望于其他特性。我们用的数据库是MySQL,后来查了查文档,经过测试,借助MySQL的全文检索(Full-Text Search)和相应的中文解析插件ngram解决这一问题。

在具体介绍MySQL的Full-Text Search和中文解析插件ngram之前,先聊一聊关于自然语言的话题,MySQL在实现解析时将其分成两大语系:第一种是use word delimiter language,这类语言单词与单词之间有天然的分隔符,例如英语;第二种是ideographic language,字词之间没有分隔符,其代表是汉语、日语和韩语(MySQL官方将其统称为CJK:Chinese、Japanese、Korean)。因为个人只懂中文,学习过英语和日语,所以不能拿其他语言来作例证,这一部分关于自然语言划分的内容只是看了MySQL的Reference Manual后受到启发而自己设想的结论。

对于第一种语系,MySQL解析起来非常简单,只要以空格(white space)作为界定符(delimiter)来进行分词(tokenize)操作即可。这也是MySQL最初内置的全文检索。MySQL5.6以前只能用于MyISAM表,5.6版本时InnoDB存储引擎也实现并扩展了这种全文检索。第二种CJK语系没有分隔符,即MySQL原始无法自动分词,所以需要一种新的实现来进行Full-Text Search,这就是前文提到的ngram插件,由MySQL5.7.6开始引入。



Full-Text Search全文检索是通过对字段添加全文索引(FULLTEXT INDEX),然后将索引的每条记录(document)的文本内容进行分词,并将分好的单词(word)记入辅助表(auxiliary table)来实现的。auxiliary table记录了单词与FULLTEXT index记录行的映射。`INFORMATION_SCHEMA`.`INNODB_FT_INDEX_CACHE`系统表记录了相关分词信息。设置参数“innodb_ft_aux_table”可以查看该表中的记录。

mysql> CREATE TABLE `player`.`articles` (
    -> `FTS_DOC_ID` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    -> `title` VARCHAR(200) NULL DEFAULT NULL,
    -> `body` TEXT NULL,
    -> PRIMARY KEY (`FTS_DOC_ID`),
    -> FULLTEXT INDEX `title` (`title`, `body`)
    -> )
    -> ENGINE=InnoDB
    -> ;
Query OK, 0 rows affected (0.25 sec)

mysql> INSERT INTO `player`.`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 will 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.02 sec)
Records: 6  Duplicates: 0  Warnings: 0

mysql> SET GLOBAL innodb_ft_aux_table="player/articles";
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM `INFORMATION_SCHEMA`.`INNODB_FT_INDEX_CACHE` ORDER BY `doc_id`, `position` LIMIT 8;
+----------+--------------+-------------+-----------+--------+----------+
| WORD     | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT | DOC_ID | POSITION |
+----------+--------------+-------------+-----------+--------+----------+
| mysql    |            1 |           6 |         6 |      1 |        0 |
| tutorial |            1 |           3 |         2 |      1 |        6 |
| dbms     |            1 |           1 |         1 |      1 |       15 |
| stands   |            1 |           1 |         1 |      1 |       20 |
| database |            1 |           5 |         2 |      1 |       31 |
| use      |            2 |           2 |         1 |      2 |        7 |
| mysql    |            1 |           6 |         6 |      2 |       11 |
| well     |            2 |           2 |         1 |      2 |       17 |
+----------+--------------+-------------+-----------+--------+----------+
8 rows in set (0.01 sec)

在以上DDL建表SQL中,有一个特殊的字段“FTS_DOC_ID”。InnoDB存储引擎固定使用该字段与记录行中的每个单词(一对多)映射,类型必须为“BIGINT UNSIGNED NOT NULL”,并且为其主动加上一个UNIQUE INDEX。所以,InnoDB存储引擎的Full-Text Search表一定会有一个“FTS_DOC_ID”字段,如果在建表时没有定义该字段,MySQL会隐式地主动添加该字段。因此,全文检索表中的“FTS_DOC_ID”字段直接对应`INNODB_FT_INDEX_CACHE`系统表中的“DOC_ID”列。

  • WORD:分词出来的单词。
  • FIRST_DOC_ID:该“WORD”首次出现的“DOC_ID”。
  • LAST_DOC_ID:该“WORD”最后一次次出现的“DOC_ID”。
  • DOC_COUNT:该“WORD”在多少行记录(多少个“DOC_ID”)中存在。
  • DOC_ID:对应全文检索表中的“FTS_DOC_ID”字段(显式或隐式创建),映射该“WORD”属于哪行记录。
  • POSITION。该“WORD”在该“DOC_ID”行的位置(字节)。

上面的查询为了简约展示只返回了8条记录,包含DOC_ID=1,即第一行记录的“完整”分词,而第二行记录的信息只显示了部分。但是对比发现,第一行中的单词“for”并没有记入分词,如果展示完整的查询结果,会发现几乎每行记录都存在这种“弃词”的情况。这里涉及到stopword list,在tokenize时,stopword list中的word会直接忽略。在英语中,常见的像“a”、“an”和“the”等词语并没有实际的涵义,因此将其记入分词并没有多大的意义,只是增加了索引的大小而降低了查询的性能。InnoDB存储引擎默认的stopword记录在`INFORMATION_SCHEMA`.`INNODB_FT_DEFAULT_STOPWORD`系统表,默认包含36个stopword。除此之外,MySQL系统参数“innodb_ft_min_token_size”、“innodb_ft_max_token_size”和“ft_min_word_len”、“ft_max_word_len”用来控制进行tokenize时word的长度区间,不在参数设置区间段内的word也无法全文检索。前两个参数控制InnoDB表,后两个参数控制MyISAM表,它们都是静态参数(需重启MySQL服务器来修改)。

Full-Text Search的查询语句如下:MATCH()指定被查询的字段(全文索引列);AGAINST()指定查询关键字表达式和查询模式。

MATCH (col1,col2,...) AGAINST (expr [search_modifier])

	search_modifier:
	  {
		   IN NATURAL LANGUAGE MODE
		 | IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION
		 | IN BOOLEAN MODE
		 | WITH QUERY EXPANSION
	  }

①NATURAL LANGUAGE MODE

查询带有指定word的文档。因“NATURAL LANGUAGE MODE”为默认全文检索模式,所以查询语句中可以省略该关键字。

mysql> SELECT * FROM `player`.`articles`
    -> WHERE MATCH (`title`, `body`)
    -> AGAINST ('database' IN NATURAL LANGUAGE MODE);
+------------+-------------------+------------------------------------------+
| FTS_DOC_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)

mysql> SELECT * FROM `player`.`articles`
    -> WHERE MATCH (`title`, `body`)
    -> AGAINST ('database');
+------------+-------------------+------------------------------------------+
| FTS_DOC_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)

MATCH()返回一个相关系数(relevance value),查询结果会按relevance value值降序排列,即相关性最高的结果置于首位。relevance value根据以下条件计算。

  • word是否在记录中出现
  • word在记录中出现的次数
  • word在索引字段中的数量
  • 多少行记录包含该word

以下查询可以查看relevance value。

mysql> SELECT `pa`.`FTS_DOC_ID`, `pa`.`title`, `pa`.`body`,
    -> MATCH (`pa`.`title`, `pa`.`body`) AGAINST ('Tutorial' IN NATURAL LANGUAGE MODE) AS `score`
    -> FROM `player`.`articles` AS `pa`
    -> ORDER BY `score` DESC
    -> LIMIT 4;
+------------+-----------------------+-------------------------------------+---------------------+
| FTS_DOC_ID | title                 | body                                | score               |
+------------+-----------------------+-------------------------------------+---------------------+
|          1 | MySQL Tutorial        | DBMS stands for DataBase ...        | 0.22764469683170319 |
|          3 | Optimizing MySQL      | In this tutorial we will show ...   | 0.22764469683170319 |
|          2 | How To Use MySQL Well | After you went through a ...        |                   0 |
|          4 | 1001 MySQL Tricks     | 1. Never run mysqld as root. 2. ... |                   0 |
+------------+-----------------------+-------------------------------------+---------------------+
4 rows in set (0.00 sec)

②BOOLEAN MODE

AGAINST()中关键字的前后字符会有特殊含义。BOOLEAN MODE全文检索支持以下操作符。

  • +

word必须存在。

  • -

word必须不存在。

  • (no operator)

该word可选,如果出现relevance value更高。

  • @distance

仅用于InnoDB表。查询多个单词之间的距离是否在distance(字节)内。

  • > <

分别表示出现该word时增加和降低relevance value。

  • ~

出现该word时relevance value变负值,用于制造噪音词(“noise” word)。

  • *

表示以该字符串开头的word。

  • ''

''中的内容视作一个短语(整体)

mysql> /* 查询包含“tutorial”但不包含“dbms”的记录 */
mysql> SELECT * FROM `player`.`articles`
    -> WHERE MATCH (`title`, `body`) AGAINST ('+tutorial -dbms' IN BOOLEAN MODE);
+------------+------------------+-----------------------------------+
| FTS_DOC_ID | title            | body                              |
+------------+------------------+-----------------------------------+
|          3 | Optimizing MySQL | In this tutorial we will show ... |
+------------+------------------+-----------------------------------+
1 row in set (0.01 sec)

mysql> /* 查询“mysql”与“tutorial”之间的距离在2个字节以内的记录 */
mysql> SELECT * FROM `player`.`articles`
    -> WHERE MATCH (`title`, `body`) AGAINST ('"mysql tutorial" @2' IN BOOLEAN MODE);
+------------+----------------+------------------------------+
| FTS_DOC_ID | title          | body                         |
+------------+----------------+------------------------------+
|          1 | MySQL Tutorial | DBMS stands for DataBase ... |
+------------+----------------+------------------------------+
1 row in set (0.01 sec)

mysql> SELECT * FROM `player`.`articles`
    -> WHERE MATCH (`title`, `body`) AGAINST ('"mysql tutorial" @4' IN BOOLEAN MODE);
+------------+------------------+-----------------------------------+
| FTS_DOC_ID | title            | body                              |
+------------+------------------+-----------------------------------+
|          1 | MySQL Tutorial   | DBMS stands for DataBase ...      |
|          3 | Optimizing MySQL | In this tutorial we will show ... |
+------------+------------------+-----------------------------------+
2 rows in set (0.00 sec)

mysql> /* 根据单词“tutorial”和“optimizing”统计relevance value:其中包含“optimizing”的记录增加relevance value */
mysql> SELECT `pa`.`FTS_DOC_ID`, `pa`.`title`, `pa`.`body`,
    -> MATCH (`pa`.`title`, `pa`.`body`) AGAINST ('tutorial' IN BOOLEAN MODE) AS `score`
    -> FROM `player`.`articles` AS `pa`
    -> ORDER BY `score` DESC
    -> LIMIT 2;
+------------+------------------+-----------------------------------+---------------------+
| FTS_DOC_ID | title            | body                              | score               |
+------------+------------------+-----------------------------------+---------------------+
|          1 | MySQL Tutorial   | DBMS stands for DataBase ...      | 0.22764469683170319 |
|          3 | Optimizing MySQL | In this tutorial we will show ... | 0.22764469683170319 |
+------------+------------------+-----------------------------------+---------------------+
2 rows in set (0.00 sec)

mysql> SELECT `pa`.`FTS_DOC_ID`, `pa`.`title`, `pa`.`body`,
    -> MATCH (`pa`.`title`, `pa`.`body`) AGAINST ('tutorial >optimizing' IN BOOLEAN MODE) AS `score`
    -> FROM `player`.`articles` AS `pa`
    -> ORDER BY `score` DESC
    -> LIMIT 2;
+------------+------------------+-----------------------------------+---------------------+
| FTS_DOC_ID | title            | body                              | score               |
+------------+------------------+-----------------------------------+---------------------+
|          3 | Optimizing MySQL | In this tutorial we will show ... |  1.8331639766693115 |
|          1 | MySQL Tutorial   | DBMS stands for DataBase ...      | 0.22764469683170319 |
+------------+------------------+-----------------------------------+---------------------+
2 rows in set (0.00 sec)

mysql> /* 查询包含“tutorial”的记录:其中包含“optimizing”的记录relevance value置为负值 */
mysql> SELECT `pa`.`FTS_DOC_ID`, `pa`.`title`, `pa`.`body`,
    -> MATCH (`pa`.`title`, `pa`.`body`) AGAINST ('tutorial ~optimizing' IN BOOLEAN MODE) AS `score`
    -> FROM `player`.`articles` AS `pa`
    -> ORDER BY `score` DESC
    -> LIMIT 2;
+------------+------------------+-----------------------------------+---------------------+
| FTS_DOC_ID | title            | body                              | score               |
+------------+------------------+-----------------------------------+---------------------+
|          1 | MySQL Tutorial   | DBMS stands for DataBase ...      | 0.22764469683170319 |
|          3 | Optimizing MySQL | In this tutorial we will show ... | -0.1668359637260437 |
+------------+------------------+-----------------------------------+---------------------+
2 rows in set (0.00 sec)

mysql> /* 查询包含以字母“c”开头的单词的记录 */
mysql> SELECT * FROM `player`.`articles`
    -> WHERE MATCH (`title`, `body`) AGAINST ('c*' IN BOOLEAN MODE);
+------------+-------------------+------------------------------------------+
| FTS_DOC_ID | title             | body                                     |
+------------+-------------------+------------------------------------------+
|          5 | MySQL vs. YourSQL | In the following database comparison ... |
|          6 | MySQL Security    | When configured properly, MySQL ...      |
+------------+-------------------+------------------------------------------+
2 rows in set (0.00 sec)

mysql> /* 查询包含“mysql”或者“tutorial”的记录 */
mysql> SELECT * FROM `player`.`articles`
    -> WHERE MATCH (`title`, `body`) AGAINST ('mysql tutorial' IN BOOLEAN MODE);
+------------+-----------------------+------------------------------------------+
| FTS_DOC_ID | title                 | body                                     |
+------------+-----------------------+------------------------------------------+
|          1 | MySQL Tutorial        | DBMS stands for DataBase ...             |
|          3 | Optimizing MySQL      | In this tutorial we will show ...        |
|          6 | MySQL Security        | When configured properly, MySQL ...      |
|          2 | How To Use MySQL Well | After you went through a ...             |
|          4 | 1001 MySQL Tricks     | 1. Never run mysqld as root. 2. ...      |
|          5 | MySQL vs. YourSQL     | In the following database comparison ... |
+------------+-----------------------+------------------------------------------+
6 rows in set (0.00 sec)

mysql> /* 查询包含短语“mysql tutorial”的记录 */
mysql> SELECT * FROM `player`.`articles`
    -> WHERE MATCH (`title`, `body`) AGAINST ('"mysql tutorial"' IN BOOLEAN MODE);
+------------+----------------+------------------------------+
| FTS_DOC_ID | title          | body                         |
+------------+----------------+------------------------------+
|          1 | MySQL Tutorial | DBMS stands for DataBase ... |
+------------+----------------+------------------------------+
1 row in set (0.00 sec)

③(NATURAL LANGUAGE MODE)WITH QUERY EXPANSION

全文检索扩展查询。当查询的关键词太短,需要隐含信息(implied knowledge)时比较有用。例如,当查询“database”时可能意味着“MySQL”、“Oracle”、“DB2”以及“RDBMS”等词语都应该匹配和返回。在查询语句中添加WITH QUERY EXPANSION或者IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION启用“blind query expansion”(也称“automatic relevance feedback”)。扩展查询分两步执行。

  • 根据原始的查询关键字表达式进行全文检索
  • 根据第一步查询的返回结果分词后再进行一次全文检索

因此,如果有记录包含“database”和“MySQL”,第二步查询也会找出那些包含“MySQL”的记录,即便这些记录没有包含“database”。

mysql> SELECT * FROM `player`.`articles`
    -> WHERE MATCH (`title`, `body`)
    -> AGAINST ('database' IN NATURAL LANGUAGE MODE);
+------------+-------------------+------------------------------------------+
| FTS_DOC_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)

mysql> SELECT * FROM `player`.`articles`
    -> WHERE MATCH (`title`, `body`)
    -> AGAINST ('database' WITH QUERY EXPANSION);
+------------+-----------------------+------------------------------------------+
| FTS_DOC_ID | title                 | body                                     |
+------------+-----------------------+------------------------------------------+
|          5 | MySQL vs. YourSQL     | In the following database comparison ... |
|          1 | MySQL Tutorial        | DBMS stands for DataBase ...             |
|          3 | Optimizing MySQL      | In this tutorial we will show ...        |
|          6 | MySQL Security        | When configured properly, MySQL ...      |
|          2 | How To Use MySQL Well | After you went through a ...             |
|          4 | 1001 MySQL Tricks     | 1. Never run mysqld as root. 2. ...      |
+------------+-----------------------+------------------------------------------+
6 rows in set (0.00 sec)

blind query expansion通常会返回不相关的记录而极大地增加查询结果的“噪音”(noise),因此仅当查询语句表达式简短的情况下使用。



在CJK语系中,为了解决word与word之间不使用分隔符而原始无法分词的问题,MySQL引入ngram全文解析器(ngram full-text parser)。ngram的核心思想是用户自定义分词长度。一个ngram指的是给定文本序列中n个字符的连续序列。ngram解析器将文本序列分词成n个字符的连续序列。例如,通过ngram full-text parser可以以不同的n值将“abcd”分词。

  • n=1: 'a', 'b', 'c', 'd'
  • n=2: 'ab', 'bc', 'cd'
  • n=3: 'abc', 'bcd'
  • n=4: 'abcd'

ngram parser默认的分词大小(token size)是2(bigram)。在此默认值下,字符串“abc def”会被解析成四个分词:“ab”、“bc”、“de”和“ef”。该值大小由参数“ngram_token_size”控制,值域为1 ~ 10。通常将其设置为你想要查询的token的最大size。如果只想要搜索单字符,将其设为1。越小的token size生成越小的full-text search索引和更快的查询性能。如果需要搜索由多于一个的字符组成的word,以此相应地设置“ngram_token_size”。例如,“Happy Birthday”在简体中文里是“生日快乐”,其中“生日”是“birthday”、“快乐”译作“happy”。要搜索类似的双字符word,设置“ngram_token_size”的值为2或者更大。该参数为只读变量,只能在启动行或者配置文件里面设置。

ngram parser在解析时会消除空格。

  • “ab cd”解析为:“ab”、“cd”
  • “a bc”解析为:“bc”

在以空格作为分隔符来分词的第一语系,stopword list是相同剔除(word与stopword相同时不会FULLTEXT索引);CJK语系则与此不同,stopword list为包含剔除。例如,假设ngram_token_size=2,一个包含“a,b”的记录会被解析为“a,”和“,b”,如果逗号(“,”)被定义为stopword,那么“a,”和“,b”都会被FULLTEXT索引排除,因为它们包含逗号。默认情况下,ngram parser使用MySQL默认的stopword list,它包含一列英文stopword。对于适用于CJK语系的stopword list,则必须自己创建(通过系统参数“innodb_ft_server_stopword_table”指定)。宽度超过“ngram_token_size”的stopword会被忽略。

要创建启用ngram parser的FULLTEXT index,在建表的DDL中指定“WITH PARSER ngram”。

mysql> CREATE TABLE `player`.`articles_ngram` (
    -> `FTS_DOC_ID` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    -> `title` VARCHAR(200) NULL DEFAULT NULL,
    -> `body` TEXT NULL,
    -> PRIMARY KEY (`FTS_DOC_ID`),
    -> FULLTEXT INDEX `title` (`title`, `body`) WITH PARSER ngram
    -> )
    -> ENGINE=InnoDB CHARACTER SET utf8mb4
    -> ;
Query OK, 0 rows affected (0.11 sec)

mysql> SET NAMES utf8mb4;
Query OK, 0 rows affected (0.01 sec)

mysql> INSERT INTO `player`.`articles_ngram` (`title`, `body`) VALUES
    -> ('中华人民共和国', '成立于1949年10月1日'),
    -> ('中华', '这里是指中华民族'),
    -> ('中国', '拥有五千年历史的国家'),
    -> ('中华民国', '1912年~1949年的中国大陆;1949年至今的中华台湾'),
    -> ('民国', '应该是指民国时期吧(1912年~1949年的中国)'),
    -> ('日本国', '太陽が昇るお国'),
    -> ('大和', '日本国家的主要民族'),
    -> ('岛国', '就是日本 国家的元首叫天皇'),
    -> ('UK', 'country of the sun say goodbye'),
    -> ('英国', '老牌资本主义国家');
Query OK, 10 rows affected (0.01 sec)
Records: 10  Duplicates: 0  Warnings: 0

mysql> SET GLOBAL innodb_ft_aux_table="player/articles_ngram";
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW GLOBAL VARIABLES LIKE 'ngram_token_size';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| ngram_token_size | 2     |
+------------------+-------+
1 row in set (0.02 sec)

mysql> SELECT * FROM `INFORMATION_SCHEMA`.`INNODB_FT_INDEX_CACHE` AS `ii` WHERE `ii`.`DOC_ID` = 1 ORDER BY `ii`.`POSITION`;
+--------+--------------+-------------+-----------+--------+----------+
| WORD   | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT | DOC_ID | POSITION |
+--------+--------------+-------------+-----------+--------+----------+
| 中华   |            1 |           4 |         3 |      1 |        0 |
| 华人   |            1 |           1 |         1 |      1 |        3 |
| 人民   |            1 |           1 |         1 |      1 |        6 |
| 民共   |            1 |           1 |         1 |      1 |        9 |
| 共和   |            1 |           1 |         1 |      1 |       12 |
| 和国   |            1 |           1 |         1 |      1 |       15 |
| 成立   |            1 |           1 |         1 |      1 |       22 |
| 立于   |            1 |           1 |         1 |      1 |       25 |
| 于1    |            1 |           1 |         1 |      1 |       28 |
| 19     |            1 |           5 |         3 |      1 |       31 |
| 94     |            1 |           5 |         3 |      1 |       32 |
| 49     |            1 |           5 |         3 |      1 |       33 |
| 9年    |            1 |           5 |         3 |      1 |       34 |
| 年1    |            1 |           1 |         1 |      1 |       35 |
| 10     |            1 |           1 |         1 |      1 |       38 |
| 0月    |            1 |           1 |         1 |      1 |       39 |
| 月1    |            1 |           1 |         1 |      1 |       40 |
| 1日    |            1 |           1 |         1 |      1 |       43 |
+--------+--------------+-------------+-----------+--------+----------+
18 rows in set (0.01 sec)

全文检索的SQL查询语法对于ngram解析器插件同样适用,但解析方式存在一些不同之处。

①ngram Parser Term Search

  • 在NATURAL LANGUAGE MODE检索模式下,查询关键字表达式被转换为若干个ngram词语的联合。例如,字符串“abc”(假设ngram_token_size=2)会被转换为“ab bc”。给出两行记录,一行包含“ab”而另一行包含“abc”,这两行记录都匹配检索词语“ab bc”。即匹配其一即可返回。
mysql> SELECT * FROM `player`.`articles_ngram`
    -> WHERE MATCH (`title`, `body`)
    -> AGAINST ('中华人民' IN NATURAL LANGUAGE MODE);
+------------+-----------------------+---------------------------------------------------------------+
| FTS_DOC_ID | title                 | body                                                          |
+------------+-----------------------+---------------------------------------------------------------+
|          1 | 中华人民共和国        | 成立于1949年10月1日                                           |
|          2 | 中华                  | 这里是指中华民族                                              |
|          4 | 中华民国              | 1912年~1949年的中国大陆;1949年至今的中华台湾                 |
+------------+-----------------------+---------------------------------------------------------------+
3 rows in set (0.00 sec)
  • 在BOOLEAN MODE检索模式下,查询关键字表达式被转换为一个ngram短语检索。例如,字符串“abc”(假设ngram_token_size=2)会被转换为'"ab bc"'。给出两行记录,一行包含“ab”而另一行包含“abc”,只有包含“abc”的记录行匹配检索短语'"ab bc"'。即完全匹配才可返回。
mysql> SELECT * FROM `player`.`articles_ngram`
    -> WHERE MATCH (`title`, `body`)
    -> AGAINST ('中华人民' IN BOOLEAN MODE);
+------------+-----------------------+---------------------------+
| FTS_DOC_ID | title                 | body                      |
+------------+-----------------------+---------------------------+
|          1 | 中华人民共和国        | 成立于1949年10月1日       |
+------------+-----------------------+---------------------------+
1 row in set (0.00 sec)

②ngram Parser Wildcard Search

由于ngram FULLTEXT index仅包含ngram分词,而不包含词语的词首信息,所以通配符检索可能返回预想之外的结果。

  • 如果通配符检索的前缀词语小于“ngram_token_size”,查询会返回包含以该前缀词语开头的ngram分词的所有索引行。例如,假设ngram_token_size=2,对于“a*”的检索会返回所有以“a”开头的记录。
mysql> SELECT * FROM `player`.`articles_ngram`
    -> WHERE MATCH (`title`, `body`)
    -> AGAINST ('中*' IN BOOLEAN MODE);
+------------+-----------------------+---------------------------------------------------------------+
| FTS_DOC_ID | title                 | body                                                          |
+------------+-----------------------+---------------------------------------------------------------+
|          2 | 中华                  | 这里是指中华民族                                              |
|          4 | 中华民国              | 1912年~1949年的中国大陆;1949年至今的中华台湾                 |
|          1 | 中华人民共和国        | 成立于1949年10月1日                                           |
|          3 | 中国                  | 拥有五千年历史的国家                                          |
|          5 | 民国                  | 应该是指民国时期吧(1912年~1949年的中国)                     |
+------------+-----------------------+---------------------------------------------------------------+
5 rows in set (0.01 sec)
  • 如果通配符检索的前缀词语大于“ngram_token_size”,该前缀词语会被转换为一个ngram短语,与此同时,通配符符号会被忽略。例如,假设ngram_token_size=2,“abc*”通配符检索会被转换为“ab bc”(“ngram Parser Term Search”的BOOLEAN MODE)。
mysql> SELECT * FROM `player`.`articles_ngram`
    -> WHERE MATCH (`title`, `body`)
    -> AGAINST ('中华人民*' IN BOOLEAN MODE);
+------------+-----------------------+---------------------------+
| FTS_DOC_ID | title                 | body                      |
+------------+-----------------------+---------------------------+
|          1 | 中华人民共和国        | 成立于1949年10月1日       |
+------------+-----------------------+---------------------------+
1 row in set (0.00 sec)

③ngram Parser Phrase Search

短语检索会被转换为ngram短语检索。例如,检索短语“abc”会被转换为“ab bc”,包含“abc”和“ab bc”的记录都会被返回。检索短语“abc def”会被转换为“ab bc de ef”,包含“abcdef”的记录不会被返回。

mysql> SELECT * FROM `player`.`articles_ngram`
    -> WHERE MATCH (`title`, `body`)
    -> AGAINST ('"日本国家"' IN BOOLEAN MODE);
+------------+--------+-----------------------------+
| FTS_DOC_ID | title  | body                        |
+------------+--------+-----------------------------+
|          7 | 大和   | 日本国家的主要民族          |
+------------+--------+-----------------------------+
1 row in set (0.01 sec)

mysql> SELECT * FROM `player`.`articles_ngram`
    -> WHERE MATCH (`title`, `body`)
    -> AGAINST ('"日本 国家"' IN BOOLEAN MODE);
+------------+--------+---------------------------------------+
| FTS_DOC_ID | title  | body                                  |
+------------+--------+---------------------------------------+
|          8 | 岛国   | 就是日本 国家的元首叫天皇             |
+------------+--------+---------------------------------------+
1 row in set (0.00 sec)

query expansion扩展查询同样适用于ngram parser。

mysql> SELECT * FROM `player`.`articles_ngram`
    -> WHERE MATCH (`title`, `body`)
    -> AGAINST ('国家');
+------------+--------+---------------------------------------+
| FTS_DOC_ID | title  | body                                  |
+------------+--------+---------------------------------------+
|          3 | 中国   | 拥有五千年历史的国家                  |
|          7 | 大和   | 日本国家的主要民族                    |
|          8 | 岛国   | 就是日本 国家的元首叫天皇             |
|         10 | 英国   | 老牌资本主义国家                      |
+------------+--------+---------------------------------------+
4 rows in set (0.00 sec)

mysql> SELECT * FROM `player`.`articles_ngram`
    -> WHERE MATCH (`title`, `body`)
    -> AGAINST ('国家' WITH QUERY EXPANSION);
+------------+--------------+---------------------------------------------------------------+
| FTS_DOC_ID | title        | body                                                          |
+------------+--------------+---------------------------------------------------------------+
|          8 | 岛国         | 就是日本 国家的元首叫天皇                                     |
|          3 | 中国         | 拥有五千年历史的国家                                          |
|         10 | 英国         | 老牌资本主义国家                                              |
|          7 | 大和         | 日本国家的主要民族                                            |
|          6 | 日本国       | 太陽が昇るお国                                                |
|          2 | 中华         | 这里是指中华民族                                              |
|          4 | 中华民国     | 1912年~1949年的中国大陆;1949年至今的中华台湾                 |
|          5 | 民国         | 应该是指民国时期吧(1912年~1949年的中国)                     |
+------------+--------------+---------------------------------------------------------------+
8 rows in set (0.00 sec)

除了上文第一语系解析时提到的用于InnoDB的“innodb_ft_min_token_size”和“innodb_ft_max_token_size”以及用于MyISAM的“ft_min_word_len”、“ft_max_word_len”(ngram parser会自动忽略其值设置),其他内置的Full-Text Search配置参数同样适用于ngram parser。

MySQL同时提供了一个专用于日语的MeCab full-text parser,将记录行tokenize为有实际意义的词语。

参考:

https://dev.mysql.com/doc/refman/5.7/en/fulltext-search.html

https://dev.mysql.com/doc/refman/5.7/en/innodb-ft-index-cache-table.html

《MySQL技术内幕 InnoDB存储引擎》(https://book.douban.com/subject/24708143/

猜你喜欢

转载自blog.csdn.net/sweeper_freedoman/article/details/82847754