mysql全文搜索使用和踩坑总结

mysql全文索引使用方式

使用mysql全文索引首先要确认版本,全文索引时在5.7.x开始支持全文索引,由于我的版本是8.0,因此我下文的例子都是基于8.0版本执行的,其次要确认全文索引的数据表引擎,目前只支持 InnoDB和MyISAM。

搜索模式

全文搜索使用关键词
MATCH (col1,col2,...) AGAINST (expr [search_modifier])
其中col1col2 是列名,expr 搜索的文本内容,search_modifier 表示搜索模式,目前mysql共有3种搜索模式。

1. Natural Language Full-Text Searches 自然语言全文搜索模式,

该方式也是默认查询方式(即查询不带任何模式时),可以看到任何包含了搜索词database 的条目都出现在了结果里面,看例子:

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)
2. Boolean Full-Text Searches 布尔全文搜索模式

布尔通过使用+-号来匹配包含和不包含对应的关键词,看下面例子就是搜索包含关键词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 ... |
+----+-----------------------+-------------------------------------+
3. Full-Text Searches with Query Expansion 带查询扩展的全文搜索模式

带查询扩展的全文索引是指可以对查询条件进行词义的扩展,例如 mysql、oracle、sqlserver都 database 的一种,我们使用带扩展的查询和自言查询的结果不一样,下面👇的例子对比可以看出扩展的查询会把包含mysql的结果也查询出来。

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)

mysql> SELECT * FROM articles
    WHERE MATCH (title,body)
    AGAINST ('database' WITH QUERY EXPANSION);
+----+-----------------------+------------------------------------------+
| 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 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)

全文索引遇到的问题

👆上面的基本是对官方文档的翻译,因此是以英文为主,而我们的使用场景不仅仅有英文,还包含了中文英文混合的查询场景,我简单描述下我的需求:可以查询出任何包含tag(标签) 和content(内容)的中英文结果。
表结构如下:

CREATE TABLE `fulltext_test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `content` text COLLATE utf8mb4_general_ci NOT NULL,
  `tag` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
  PRIMARY KEY (`id`),
  FULLTEXT KEY `content_tag_fulltext` (`content`,`tag`) WITH PARSER `ngram` 
  ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

遇到的问题

如何根据搜索相关度排序

可以使用 match(content, tag) against(‘xxxx’) as score 来进行相关度评分,然后根据评分的结果排序,如下:

mysql> select content, tag, match(content, tag) against('公网') as score from fulltext_test where match(content, tag) against('公网') order by score desc;
+--------------------------+------+----------------------+
| content                  | tag  | score                |
+--------------------------+------+----------------------+
| 公网 ipai you too        | NULL | 0.010539556853473186 |
| 公网 cvm tke waf         | NULL | 0.010539556853473186 |
| 公网 ip cvm tke waf      | NULL | 0.010539556853473186 |
| 公网 ipb cvm tke waf     | NULL | 0.010539556853473186 |
| 公网 ipb cvm tke waf     | NULL | 0.010539556853473186 |
| 公网 ipb cvm tke waf !   | NULL | 0.010539556853473186 |
| 公网IP                   | cvm  | 0.010539556853473186 |
| 公网IP1                  | cvm  | 0.010539556853473186 |
| 公网IP2                  | cvm  | 0.010539556853473186 |
| 公网IP3                  | cvm  | 0.010539556853473186 |
| 公网IP4                  | cvm  | 0.010539556853473186 |
| 公网 IP                  | cvm  | 0.010539556853473186 |
| 公网 IPa                 | cvm  | 0.010539556853473186 |
| 公网 I love you          | cvm  | 0.010539556853473186 |
| 公网 ipai                | cvm  | 0.010539556853473186 |
+--------------------------+------+----------------------+

搜索词无结果排查

content因为包含中文和英文,不知为何部分关键词可以检索出来,部分关键词就是无法检索出来.

初始化后的数据表

mysql> select * from fulltext_test;
+----+--------------------------+------+
| id | content                  | tag  |
+----+--------------------------+------+
|  1 | 公网IP                   | cvm  |
|  2 | 公网IP1                  | cvm  |
|  3 | 公网IP2                  | cvm  |
|  4 | 公网IP3                  | cvm  |
|  5 | 公网IP4                  | cvm  |
|  6 | I love you               | cvm  |
|  7 | 公网 IP                  | cvm  |
|  8 | 公网 IPa                 | cvm  |
|  9 | 公网 I love you          | cvm  |
| 10 | 公网 ipai                | cvm  |
| 11 | 公网 ipai you too        | NULL |
| 12 | 公网 cvm tke waf         | NULL |
| 13 | 公网 ip cvm tke waf      | NULL |
| 14 | 公网 ipb cvm tke waf     | NULL |
| 15 | ipb cvm tke waf          | NULL |
| 16 | 公网 ipb cvm tke waf     | NULL |
| 17 | 公网 ipb cvm tke waf !   | NULL |
| 18 |  ipb cvm tke waf !       | NULL |
| 19 | waf ipb cvm tke          | NULL |
+----+--------------------------+------+

尝试了几个搜索词进行搜索公网,cvm,waf,tke,ipai 等进行搜索,发现部分英文词出不来, 如wafipai, 搜索结果如下:

mysql> select * from fulltext_test where match(content, tag) against('公网');
+----+--------------------------+------+
| id | content                  | tag  |
+----+--------------------------+------+
|  1 | 公网IP                   | NULL |
|  2 | 公网IP1                  | NULL |
|  3 | 公网IP2                  | NULL |
|  4 | 公网IP3                  | NULL |
|  5 | 公网IP4                  | NULL |
|  7 | 公网 IP                  | NULL |
|  8 | 公网 IPa                 | NULL |
|  9 | 公网 I love you          | NULL |
| 10 | 公网 ipai                | NULL |
| 11 | 公网 ipai you too        | NULL |
| 12 | 公网 cvm tke waf         | NULL |
| 13 | 公网 ip cvm tke waf      | NULL |
| 14 | 公网 ipb cvm tke waf     | NULL |
| 16 | 公网 ipb cvm tke waf     | NULL |
| 17 | 公网 ipb cvm tke waf !   | NULL |
+----+--------------------------+------+

mysql> select * from fulltext_test where match(content, tag) against('cvm');
+----+--------------------------+------+
| id | content                  | tag  |
+----+--------------------------+------+
| 12 | 公网 cvm tke waf         | NULL |
| 13 | 公网 ip cvm tke waf      | NULL |
| 14 | 公网 ipb cvm tke waf     | NULL |
| 15 | ipb cvm tke waf          | NULL |
| 16 | 公网 ipb cvm tke waf     | NULL |
| 17 | 公网 ipb cvm tke waf !   | NULL |
| 18 |  ipb cvm tke waf !       | NULL |
| 19 | waf ipb cvm tke          | NULL |
+----+--------------------------+------+
8 rows in set (0.00 sec)

mysql> select * from fulltext_test where match(content, tag) against('waf');
Empty set (0.00 sec)

mysql> select * from fulltext_test where match(content, tag) against('tke');
+----+--------------------------+------+
| id | content                  | tag  |
+----+--------------------------+------+
| 12 | 公网 cvm tke waf         | NULL |
| 13 | 公网 ip cvm tke waf      | NULL |
| 14 | 公网 ipb cvm tke waf     | NULL |
| 15 | ipb cvm tke waf          | NULL |
| 16 | 公网 ipb cvm tke waf     | NULL |
| 17 | 公网 ipb cvm tke waf !   | NULL |
| 18 |  ipb cvm tke waf !       | NULL |
| 19 | waf ipb cvm tke          | NULL |
+----+--------------------------+------+
8 rows in set (0.01 sec)
mysql> select * from fulltext_test where match(content, tag) against('ipai');
Empty set (0.00 sec)

这个问题仔细查了下官方文档,我大概估计原因如下,如果我分析有不对还请大家指出

FULLTEXT KEY `content_tag_fulltext` (`content`,`tag`)  WITH PARSER `ngram` 

我使用的索引因为想要支持中文索引因此需要使用分词器,mysql提供了 ngram 来进行分词,mysql官网文档fulltext-search-ngram, 分词用的方法非常简单,就是根据全局参数ngram_token_size 来进行分割的,系统默认是2,例如有字符串’abcd’,分词后:‘ab’, ‘bc’, ‘cd’。
其中分词器处理停用词介绍了这么一段
`
ngram Parser Stopword Handling
The built-in MySQL full-text parser compares words to entries in the stopword list. If a word is equal to an entry in the stopword list, the word is excluded from the index. For the ngram parser, stopword handling is performed differently. Instead of excluding tokens that are equal to entries in the stopword list, the ngram parser excludes tokens that contain stopwords. For example, assuming ngram_token_size=2, a document that contains “a,b” is parsed to “a,” and “,b”. If a comma (“,”) is defined as a stopword, both “a,” and “,b” are excluded from the index because they contain a comma.

By default, the ngram parser uses the default stopword list, which contains a list of English stopwords. For a stopword list applicable to Chinese, Japanese, or Korean, you must create your own. For information about creating a stopword list, see Section 12.10.4, “Full-Text Stopwords”.

Stopwords greater in length than ngram_token_size are ignored.
`
大致意思就是说,包含“a,b”的文档被解析为“a,”和“,b”。如果将逗号 (“,”) 定义为停用词,则“a”和“,b”都会从索引中排除,因为它们包含逗号。

那么我们之前我们有3个搜索词出不来结果是不是也因为命中了停用词规则呢?先看下停用词列表

mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD;
+-------+
| value |
+-------+
| a     |
| about |
| an    |
| are   |
| as    |
| at    |
| be    |
| by    |
| com   |
| de    |
| en    |
| for   |
| from  |
| how   |
| i     |
| in    |
| is    |
| it    |
| la    |
| of    |
| on    |
| or    |
| that  |
| the   |
| this  |
| to    |
| was   |
| what  |
| when  |
| where |
| who   |
| will  |
| with  |
| und   |
| the   |
| www   |
+-------+

ipai 分词后变成:ip,pa,ai, 每个词都包含了停用词‘i’ 或者‘a’,因此被排除了
waf 和上面同理包含了停用词 ‘a’ 索引不能搜索出来。
我验证了我的猜想,删掉分词的全文索引,就能搜索出来了。

mysql> select * from fulltext_test where match(content, tag) against('ipai');
+----+---------------------+------+
| id | content             | tag  |
+----+---------------------+------+
| 10 | 公网 ipai           | NULL |
| 11 | 公网 ipai you too   | NULL |
+----+---------------------+------+
2 rows in set (0.01 sec)

mysql> select * from fulltext_test where match(content, tag) against('waf');
+----+--------------------------+------+
| id | content                  | tag  |
+----+--------------------------+------+
| 12 | 公网 cvm tke waf         | NULL |
| 13 | 公网 ip cvm tke waf      | NULL |
| 14 | 公网 ipb cvm tke waf     | NULL |
| 15 | ipb cvm tke waf          | NULL |
| 16 | 公网 ipb cvm tke waf     | NULL |
| 17 | 公网 ipb cvm tke waf !   | NULL |
| 18 |  ipb cvm tke waf !       | NULL |
| 19 | waf ipb cvm tke          | NULL |
+----+--------------------------+------+
8 rows in set (0.00 sec)
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

竹二木

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值