Mysql实现简易版搜索引擎的示例代码


Posted in MySQL onAugust 30, 2021

前言

前段时间,因为项目需求,需要根据关键词搜索聊天记录,这不就是一个搜索引擎的功能吗?

于是我第一时间想到的就是 ElasticSearch 分布式搜索引擎,但是由于一些原因,公司的服务器资源比较紧张,没有额外的机器去部署一套 ElasticSearch 服务,而且上线时间也比较紧张,数据量也不大,然后就想到了 Mysql 的全文索引。

简介

其实 Mysql 很早就支持全文索引了,只不过一直只支持英文的检索,从5.7.6 版本开始,Mysql 就内置了 ngram 全文解析器,用来支持中文、日文、韩文分词。

Mysql 全文索引采用的是倒排索引的原理,在倒排索引中关键词是主键,每个关键词都对应着一系列文件,这些文件中都出现了这个关键词。这样当用户搜索某个关键词时,排序程序在倒排索引中定位到这个关键词,就可以马上找出所有包含这个关键词的文件。

本文测试,基于 Mysql 8.0 版本,数据库引擎采用的是 InnoDB

ngram 全文解析器

ngram 就是一段文字里面连续的 n 个字的序列。ngram 全文解析器能够对文本进行分词,每个单词是连续的 n 个字的序列。例如,用 ngram 全文解析器对“你好靓仔”进行分词:

 

n=1: '你', '好', '靓', '仔' 
n=2: '你好', '好靓', '靓仔' 
n=3: '你好靓', '好靓仔' 
n=4: '你好靓仔'

MySQL 中使用全局变量 ngram_token_size 来配置 ngram 中 n 的大小,它的取值范围是1到10,默认值是 2。通常 ngram_token_size 设置为要查询的单词的最小字数。如果需要搜索单字,就要把 ngram_token_size 设置为 1。在默认值是 2 的情况下,搜索单字是得不到任何结果的。因为中文单词最少是两个汉字,推荐使用默认值 2。

可以通过以下命令查看 Mysql 默认的 ngram_token_size 大小:

show variables like 'ngram_token_size'

Mysql实现简易版搜索引擎的示例代码

有两种方式可以设置全局变量 ngram_token_size 的值:

1、启动 mysqld 命令时指定:

mysqld --ngram_token_size=2

2、修改 Mysql 配置文件 my.ini,末尾增加一行参数:

ngram_token_size=2

创建全文索引

1、建表时创建全文索引

CREATE TABLE `article` (
  `id` bigint NOT NULL,
  `url` varchar(1024) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
  `title` varchar(256) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
  `source` varchar(32) COLLATE utf8mb4_general_ci DEFAULT '',
  `keywords` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `publish_time` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  FULLTEXT KEY `title_index` (`title`) WITH PARSER `ngram`
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

2、通过 alter table 方式

ALTER TABLE article ADD FULLTEXT INDEX title_index(title) WITH PARSER ngram;

3、通过 create index 方式

CREATE FULLTEXT INDEX title_index ON article (title) WITH PARSER ngram;

检索方式

1、自然语言检索(NATURAL LANGUAGE MODE)

自然语言模式是 MySQL 默认的全文检索模式。自然语言模式不能使用操作符,不能指定关键词必须出现或者必须不能出现等复杂查询。

示例

select * from article where MATCH(title) AGAINST ('北京旅游' IN NATURAL LANGUAGE MODE);

// 不指定模式,默认使用自然语言模式
select * from article where MATCH(title) AGAINST ('北京旅游');

Mysql实现简易版搜索引擎的示例代码

可以看出,该模式下根据“北京旅游”搜索,可以搜索出包含“北京”的或者包含“旅游”的内容,因为它是根据自然语言分成了两个关键词。

上面示例中返回的结果会自动按照匹配度排序,匹配度高的在前面,匹配度是一个非负浮点数。

示例

// 查看匹配度
select * , MATCH(title) AGAINST ('北京旅游') as score from article where MATCH(title) AGAINST ('北京旅游' IN NATURAL LANGUAGE MODE);

Mysql实现简易版搜索引擎的示例代码

2、布尔检索(BOOLEAN MODE)

布尔检索模式可以使用操作符,可以支持指定关键词必须出现或者必须不能出现或者关键词的权重高还是低等复杂查询。

示例

// 无操作符
// 包含“约会”或“攻略”
select * from article where MATCH(title) AGAINST ('约会 攻略' IN BOOLEAN MODE);

Mysql实现简易版搜索引擎的示例代码

// 使用操作符
// 必须包含“约会”,可包含“攻略”
select * from article where MATCH(title) AGAINST ('+约会 攻略' IN BOOLEAN MODE);

Mysql实现简易版搜索引擎的示例代码

更多操作符示例:

'约会 攻略' 
无操作符,表示或,要么包含“约会”,要么包含“攻略”

'+约会 +攻略'
必须同时包含两个词

'+约会 攻略'
必须包含“约会”,但是如果也包含“攻略”的话,匹配度更高。

'+约会 -攻略'
必须包含“约会”,同时不能包含“攻略”。

'+约会 ~攻略'
必须包含“约会”,但是如果也包含“攻略”的话,匹配度要比不包含“攻略”的记录低。

'+约会 +(>攻略 <技巧)'
查询必须包含“约会”和“攻略”或者“约会”和“技巧”的记录,但是“约会 攻略”的匹配度要比“约会 技巧”高。

'约会*'
查询包含以“约会”开头的记录。

'"约会攻略"'
使用双引号把要搜素的词括起来,效果类似于like '%约会攻略%',
例如“约会攻略初级篇”会被匹配到,而“约会的攻略”就不会被匹配。

与 Like 对比

全文索引和 like 查询对比,有以下优点:

  • like 只是进行模糊匹配,全文索引却提供了一些语法语义的查询功能,会将要查的字符串进行分词操作,这决定于 Mysql 的词库。
  • 全文索引可以自己设置词语的最小、最大长度,要忽略的词,这些都是可以设置的。
  • 用全文索引去某个列查一个字符串,会返回匹配度,可以理解为匹配的关键字个数,是个浮点数。

而且全文检索的性能也是优于 like 查询的

以下是以 50w 左右数据进行的测试:

// like 查询
select * from article where title like '%北京%';

Mysql实现简易版搜索引擎的示例代码

// 全文索引查询
select * from article where MATCH(title) AGAINST ('北京' IN BOOLEAN MODE);

Mysql实现简易版搜索引擎的示例代码

可以看出 like 查询是 1.536s,全文索引查询是 0.094s,快了16倍左右。

总结

全文索引能快速搜索,但是也存在维护索引的开销。字段长度越大,创建的全文索引也越大,会影响DML语句的吞吐量。数据量不大的情况下可以采用全文索引来做搜索,简单方便,但是数据量大的话还是建议用专门的搜索引擎 ElasticSearch 来做这件事。

到此这篇关于Mysql实现简易版搜索引擎的示例代码的文章就介绍到这了,更多相关Mysql 搜索引擎内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

MySQL 相关文章推荐
MySQL 查询速度慢的原因
May 25 MySQL
MYSQL数据库使用UTF-8中文编码乱码的解决办法
May 26 MySQL
MySQL中InnoDB存储引擎的锁的基本使用教程
May 26 MySQL
MySQL为id选择合适的数据类型
Jun 07 MySQL
为什么MySQL分页用limit会越来越慢
Jul 25 MySQL
浅谈MySQL之select优化方案
Aug 07 MySQL
MySQL之select、distinct、limit的使用
Nov 11 MySQL
MySQL数据库优化之通过索引解决SQL性能问题
Apr 10 MySQL
MySQL分区路径子分区再分区
Apr 13 MySQL
mysql 子查询的使用
Apr 28 MySQL
MySQL普通表如何转换成分区表
May 30 MySQL
MySQL中order by的执行过程
Jun 05 MySQL
详细聊聊MySQL中慢SQL优化的方向
Aug 30 #MySQL
MySQL8.0的WITH查询详情
Aug 30 #MySQL
Prometheus 监控MySQL使用grafana展示
Aug 30 #MySQL
MySQL命令无法输入中文问题的解决方式
Aug 30 #MySQL
mysql 索引合并的使用
Aug 30 #MySQL
MySQL去除重叠时间求时间差和的实现
Aug 23 #MySQL
Mysql数据库中datetime、bigint、timestamp来表示时间选择,谁来存储时间效率最高
Aug 23 #MySQL
You might like
PHP模拟QQ登录的方法
2015/07/29 PHP
测试JavaScript字符串处理性能的代码
2009/12/07 Javascript
js数字转换为float,取N位小数
2014/02/08 Javascript
jsPDF导出pdf示例
2014/05/02 Javascript
js仿黑客帝国字母掉落效果代码分享
2020/11/08 Javascript
Javascript 5种方法实现过滤删除前后所有空格
2016/06/22 Javascript
JavaScript中原型链存在的问题解析
2016/09/25 Javascript
vue2.0 自定义日期时间过滤器
2017/06/07 Javascript
JS使用tofixed与round处理数据四舍五入的区别
2017/10/25 Javascript
浅谈Vue SPA 首屏加载优化实践
2017/12/15 Javascript
vue-cli开发时,关于ajax跨域的解决方法(推荐)
2018/02/03 Javascript
electron中使用bootstrap的示例代码
2018/11/06 Javascript
JavaScript格式化json和xml的方法示例
2019/01/22 Javascript
layui的布局和表格的渲染以及动态生成表格的方法
2019/09/18 Javascript
Vue实现点击当前行变色
2020/12/14 Vue.js
基于Vue2实现移动端图片上传、压缩、拖拽排序、拖拽删除功能
2021/01/05 Vue.js
Python中的MongoDB基本操作:连接、查询实例
2015/02/13 Python
python监控网站运行异常并发送邮件的方法
2015/03/13 Python
python3使用urllib模块制作网络爬虫
2016/04/08 Python
python机器学习实战之K均值聚类
2017/12/20 Python
Python3单行定义多个变量或赋值方法
2018/07/12 Python
使用python将请求的requests headers参数格式化方法
2019/01/02 Python
python django中8000端口被占用的解决
2019/12/17 Python
解决Tensorflow 内存泄露问题
2020/02/05 Python
关于Django Models CharField 参数说明
2020/03/31 Python
浅谈numpy中np.array()与np.asarray的区别以及.tolist
2020/06/03 Python
Python函数参数定义及传递方式解析
2020/06/10 Python
详解html5 canvas 微信海报分享(个人爬坑)
2018/01/12 HTML / CSS
Skyscanner阿联酋:全球领先的旅游搜索平台
2017/11/25 全球购物
美国大码时尚女装购物网站:ELOQUII
2017/12/28 全球购物
瑞典手机壳品牌:Richmond & Finch
2018/04/28 全球购物
双方协议书
2014/04/22 职场文书
巾帼文明岗申报材料
2014/05/01 职场文书
访谈节目策划方案
2014/05/15 职场文书
节约每一滴水演讲稿
2014/09/09 职场文书
解决ObjectMapper.convertValue() 遇到的一些问题
2021/06/30 Java/Android