vlambda博客
学习文章列表

mysql中文全文检索从入门到放弃

like全匹配模糊查询不能使用索引一直是sql查询的一个棘手的问题,那么mysql的全文检索真的能解决这个问题吗?

背景

最近,在工作中遇到一个查询优化的问题,简化的sql如下:

SELECT
    * 
FROM
    wxswj_nsrxx 
WHERE
    nsrmc LIKE '%东鹏%' 
    OR nsrsbh LIKE '%东鹏%' 
    OR shxydm LIKE '%东鹏%';

问题:
1、采用了全匹配模糊查询
2、使用了OR关键字

很明显这样的查询是不能走索引,而由于表的数据量非常大,有500多万数据,导致整个查询的响应速度非常不理想。

中文全文检索实战

ngram分词插使用说明:
https://dev.mysql.com/doc/refman/5.7/en/fulltext-search-ngram.htm

1、优化思路:
中文模糊匹配查询,主要涉及到分词和全文检索,而mysql里面有一种索引类型就是全文索引FULLTEXT。所以想通过全文索引来解决mysql中全匹配模糊查询的问题。

2、说明:
在MySQL 5.7.6之前,全文索引只支持英文全文索引,不支持中文全文索引,需要利用分词器把中文段落预处理拆分成单词,然后存入数据库。
从MySQL 5.7.6开始,MySQL内置了ngram全文解析器,用来支持中文分词。

3、查看当前数据库版本:

select version() from dual;

结果为5.7.28,支持中文全文检索

4、全文检索限制:
FULLTEXT indexes are created on text-based columns (CHAR, VARCHAR, or TEXT columns)
全文索引只能被创建在CHAR, VARCHAR, or TEXT的字段上。
每张表只能有一个全文检索的索引
由多列组合而成的全文检索的索引必须使用相同的字符集与排序规则

5、关闭查询缓存
sql优化前,一般会关闭查询缓存:
SHOW VARIABLES LIKE 'query_cache%';
set global query_cache_size=0;
set global query_cache_type=0;

SHOW VARIABLES LIKE 'query_cache%';

6、建立全文索引

ALTER TABLE `wxswj`.`wxswj_nsrxx`  ADD FULLTEXT INDEX `ft_index`(`nsrmc`,`nsrsbh`,`shxydm`) WITH PARSER ngram;

7、使用全文索引
通过MATCH (col1,col2,…) AGAINST (expr [search_modifier])语句,使用全文索引。

SELECT
    * 
FROM
    wxswj_nsrxx MATCH ( `nsrmc``nsrsbh``shxydm` ) against ( '东鹏' IN boolean MODE )

这里使用东鹏去模糊匹配nsrmc, nsrsbh, shxydm这三个字段,任意一个字段中包含查询关键字东鹏就返回对应记录。

8、查询执行计划

使用了新建的组合全文检索,ref达到const级别

9、优化效果
查询性能提升了100多倍。

目前为止,好像一切都非常美好,但很快坑就出现了。
当查询的关键词太长,就出现了异常?

问题一:FTS query exceeds result cache limit
当采用比较长的查询条件去匹配执行查询或甚至执行查询计划时,出现异常:

188 - FTS query exceeds result cache limit

mysql官网中对该异常的解释说明:
https://bugs.mysql.com/bug.php?id=86036

每个全文搜索查询或每个线程的InnoDB全文搜索都对查询结果进行了缓存限制,以字节为单位定义。中间和最终的InnoDB全文搜索查询结果在内存中处理。可以使用innodb_ft_result_cache_limit设置大小限制。全文搜索查询结果缓存可避免InnoDB全文搜索查询结果非常大(例如,数百万或数亿行)时过多的内存消耗。如果达到结果缓存大小限制,则返回错误,指示查询超出了最大允许的内存。

推荐解决办法:

mysql中文全文检索从入门到放弃

1、增加innodb_ft_result_cache_limit的值,使其大于4G
SHOW VARIABLES LIKE 'innodb_ft_result_cache_limit%';
set global innodb_ft_result_cache_limit=4000000000;

2、优化查询语句,限制查询返回的记录条数,减少来自中间结果的巨大缓存。一般通过显示指定limit来限制。

问题二:查询速度非常不稳定
我们通过修改innodb_ft_result_cache_limit的值,解决了缓存限制的异常问题。
当时,我们尝试修改查询条件时,发现查询性能非常不稳定。
有时候查询速度非常快,有时候甚至比不上like全匹配模块查询。
特别是当查询条件非常长的时候,问题非常明显,查询性能完全没有保证。

SELECT
    * 
FROM
    wxswj_nsrxx MATCH ( `nsrmc``nsrsbh``shxydm` ) against ( '中国航天工业科学技术咨询有限公司' IN boolean MODE )

放弃

通过调研各种资料,没有找到比较好解决方案,最后还是无奈选择放弃。

测试语句

create table test(
id int(11not null primary key auto_increment,
name varchar(100not null comment '工商名',
brand varchar(100default null comment '品牌名',
en varchar(100default null comment '英文名',
fulltext key (name,brand,en) with parser ngram
)engine=innodb default charset=utf8;
insert into test (name,brand,en) values ('芜湖美的厨卫电气制造有限公司','aa','wh');
insert into test (name,brand,en) values ('北京凡客尚品电子商务有限公司','aa','ef');
insert into test (name,brand,en) values ('凡客诚品(北京)科技有限公司','aa','dfd');
insert into test (name,brand,en) values ('瞬联讯通科技(北京)有限公司','aa','sdfs');
insert into test (name,brand,en) values ('北京畅捷通讯有限公司','aa','wsdh');
insert into test (name,brand,en) values ('北京畅捷通支付技术有限公司','aa','df');
insert into test (name,brand,en) values ('畅捷通信息技术股份有限公司','aa','whdfgh');
insert into test (name,brand,en) values ('北京畅捷科技有限公司','aa','dgdf');
insert into test (name,brand,en) values ('中国航天工业科学技术咨询有限公司','aa','whffgh');
insert into test (name,brand,en) values ('北京·松下彩色显象管有限公司','aa','wfghfgh');
insert into test(name,brand,en) select name,brand,en from test;
insert into test(name,brand,en) select name,brand,en from test;
insert into test(name,brand,en) select name,brand,en from test;
insert into test(name,brand,en) select name,brand,en from test;
insert into test(name,brand,en) select name,brand,en from test;
insert into test(name,brand,en) select name,brand,en from test;

EXPLAIN  SELECT  *  from  test  where  match  (name,brand,en)  against  ('通讯录' IN BOOLEAN MODELIMIT 100;

创建的测试数据总数据量为:655360
select count(*) from test;

SELECT  *  from  test  where name like '%美的%' or brand like '%美的%' or en like '%美的%';
耗时:0.544

EXPLAIN  SELECT  *  from  test  where  match  (name,brand,en)  against  ('美的' IN BOOLEAN MODELIMIT 100;
耗时:0.150



SELECT  *  from  test  where name like '%芜湖美的厨卫电气制造有限公司%' or brand like '%芜湖美的厨卫电气制造有限公司%' or en like '%芜湖美的厨卫电气制造有限公司%';
耗时:0.679

EXPLAIN  SELECT  *  from  test  where  match  (name,brand,en)  against  ('芜湖美的厨卫电气制造有限公司' IN BOOLEAN MODELIMIT 100;
耗时:5.626

通过加双引号,实现确切短语搜索,不对搜索条件进行分词匹配,我们测试下:

mysql中文全文检索从入门到放弃

 SELECT  *  from  test  where  match  (name,brand,en)  against  ('"芜湖美的厨卫电气制造有限公司"' IN BOOLEAN MODELIMIT 100;
耗时:5.626

发现对查询性能没有影响。

通过实验发现,当查询条件越长,查询性能越慢。
大家可以自己测试感受一下。

有关于mysql全文检索使用好的建议,欢迎分享。

结论

本次实验证明,mysql对全文检索的支持有限,限制比较大,查询性能也得不到保证,很多时候可能比不上直接使用like查询。
几十万数据的小表,可以考虑玩一下。
对一些大表需要全匹配模糊查询时,首先是和业务方商量是否可以只支持前匹配模糊查询,其次尽可能增加其他查询条件,另外通过limit限制匹配的记录数。
复杂查询下,并且一定要求对全匹配模糊查询支持且对查询性能有严格要求,那么推荐使用Elasticsearch。

关注私聊我,免费领取视频教程。

mysql中文全文检索从入门到放弃



更多精彩,关注我吧。
图注:跟着老万学java