PostgreSQL 全文检索加速 快到没有朋友 - RUM索引接口(潘多拉魔盒)

作者

digoal

日期

2016-10-19

标签

PostgreSQL , RUM , GIN , full text search , 全文检索 , bitmap scan


背景

全文检索,模糊查询在现实的应用中用得非常多,特别是搜索引擎。

通常我们会想到使用搜索引擎来解决,但是需要考虑数据同步到搜索引擎,以及同步延迟,更新,一致性的问题。

并且使用搜索引擎我们还得多维护一个组件。

那么有没有更好的办法呢?

答案是有的,在PostgreSQL中,有内置的全文检索数据类型,以及全模糊查询的索引支持。

效率当然也是杠杠的,比如10亿的TOKEN检索,可以在毫秒级返回。

PostgreSQL 9.6在全文检索这块还做了更多的增强,比如RUM插件,被Oleg称为打开了潘多拉魔盒,在检索效率方面比GIN有极大的提升。

场景描述

我碰到过很多用户这样使用,用逗号将需要检索的元素分割开,当成字符串存储在数据库中,然后使用模糊查询的方法对数据进行检索。

create table test(c1 text);
insert into test values ('1,100,2331,344,502,.........');
insert ............
.....

比如1000万条这样的记录,然后要根据元素组合进行查询。

select * from test where c1 like '%1%' or c1 like '%502%' and c1 like '%2331%';

这种查询效率非常低下,如果要做到毫秒级的返回,几乎不可想象。

PostgreSQL 数组类型

其实以上场景,在PostgreSQL中,可以使用数组类型来满足。

create table arr_test(c1 int[]);create index idx_arr_test on arr_test using gin(c1);insert into arr_test values(array[1,100,2331,344,502,......]);
......

PostgreSQL 数组支持GIN索引,可以实现快速的检索。

例如在1000万记录中检索包含1或2的记录。

postgres=# explain analyze select * from arr_test where c1 && array[1,2] order by c1 offset 19000 limit 100;QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------Limit  (cost=112837.69..112837.94 rows=100 width=424) (actual time=91.440..91.475 rows=100 loops=1)->  Sort  (cost=112790.19..113039.57 rows=99750 width=424) (actual time=82.915..90.477 rows=19100 loops=1)Sort Key: c1Sort Method: external merge  Disk: 8440kB->  Bitmap Heap Scan on arr_test  (cost=816.06..93595.94 rows=99750 width=424) (actual time=9.180..37.380 rows=19925 loops=1)Recheck Cond: (c1 && '{1,2}'::integer[])Heap Blocks: exact=19605->  Bitmap Index Scan on idx_arr_test  (cost=0.00..791.12 rows=99750 width=0) (actual time=5.196..5.196 rows=19925 loops=1)Index Cond: (c1 && '{1,2}'::integer[])Planning time: 0.131 msExecution time: 93.929 ms
(11 rows)

PostgreSQL 全文检索类型

除了使用数组,PostgreSQL还支持全文检索类型,你可以存储为tsvector,使用tsquery进行查询。

postgres=# create table gin_test(c1 tsvector);
CREATE TABLEpostgres=# create index idx_gin_test on gin_test using gin (c1) ;
CREATE INDEX

全文检索类型同样支持索引,可以加速查询。

例如在1000万记录中检索包含1或2的记录。


潘多拉魔盒RUM

我们看到使用GIN索引时,扫描方式为BITMAP,所以有一个SORT的动作,这个在很大的LIST中是比较耗时的。

9.6的一个插件RUM索引接口,对全文检索的支持更加强大,不需要SORT,直接走INDEX SCAN的接口,也就是说RUM同时还实现了<=>即文本相似度的属性检索。

Oleg说RUM打开了潘多拉魔盒,除此之外9.6在全文检索方面还有极大的提升,9.6的release notes里也有重点说明,这使得PostgreSQL在文本检索能力方面又更加强大了。

忘掉搜索引擎吧,使用PostgreSQL。

测试RUM

https://yq.aliyun.com/articles/59212

postgres=# create table rum_test(c1 tsvector);
CREATE TABLEpostgres=# CREATE INDEX rumidx ON rum_test USING rum (c1 rum_tsvector_ops);
CREATE INDEX

性能指标 : 数组 对比 全文检索类型(GIN对比RUM索引)

下面对比一下数组GIN索引,全文检索类型GIN索引,全文检索类型RUM索引

表结构

postgres=# create table rum_test(c1 tsvector);
CREATE TABLEpostgres=# create table gin_test(c1 tsvector);
CREATE TABLEpostgres=# create table arr_test(c1 int[]);
CREATE TABLE

插入1000万记录,每个字段100个随机值,相当于在10亿随机值中匹配。

$ vi test.sql
insert into rum_test select to_tsvector(string_agg(c1::text,',')) from  (select (100000*random())::int from generate_series(1,100)) t(c1);$ pgbench -M prepared -n -r -P 1 -f ./test.sql -c 50 -j 50 -t 200000$ vi test.sql
insert into gin_test select to_tsvector(string_agg(c1::text,',')) from  (select (100000*random())::int from generate_series(1,100)) t(c1);$ pgbench -M prepared -n -r -P 1 -f ./test.sql -c 50 -j 50 -t 200000$ vi test.sql
insert into arr_test select array_agg(c1) from  (select (100000*random())::int from generate_series(1,100)) t(c1);$ pgbench -M prepared -n -r -P 1 -f ./test.sql -c 50 -j 50 -t 200000

创建索引

postgres=# set maintenance_work_mem ='64GB';
SET
postgres=# CREATE INDEX rumidx ON rum_test USING rum (c1 rum_tsvector_ops);
CREATE INDEXpostgres=# create index idx_gin_test on gin_test using gin (c1) ;
CREATE INDEXpostgres=# create index idx_arr_test on arr_test using gin (c1) ;
CREATE INDEX

查询效率对比

1. 查询包含1或2的记录

全文检索类型, rum索引
postgres=# explain analyze select * from rum_test where c1 @@ to_tsquery('english','1 | 2');QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------Index Scan using rumidx on rum_test  (cost=16.00..99121.61 rows=99749 width=1387) (actual time=6.403..24.981 rows=19840 loops=1)Index Cond: (c1 @@ '''1'' | ''2'''::tsquery)Planning time: 0.075 msExecution time: 26.086 ms
(4 rows)全文检索类型, GIN索引
postgres=# explain analyze select * from gin_test where c1 @@ to_tsquery('english','1 | 2');QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------Bitmap Heap Scan on gin_test  (cost=816.06..99386.94 rows=99750 width=1387) (actual time=9.551..34.121 rows=19847 loops=1)Recheck Cond: (c1 @@ '''1'' | ''2'''::tsquery)Heap Blocks: exact=19764->  Bitmap Index Scan on idx_gin_test  (cost=0.00..791.12 rows=99750 width=0) (actual time=5.554..5.554 rows=19847 loops=1)Index Cond: (c1 @@ '''1'' | ''2'''::tsquery)Planning time: 0.113 msExecution time: 35.279 ms
(7 rows)数组类型, GIN索引
postgres=# explain analyze select * from arr_test where c1 && array[1,2];QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------Bitmap Heap Scan on arr_test  (cost=816.06..93595.94 rows=99750 width=424) (actual time=9.148..31.648 rows=19925 loops=1)Recheck Cond: (c1 && '{1,2}'::integer[])Heap Blocks: exact=19605->  Bitmap Index Scan on idx_arr_test  (cost=0.00..791.12 rows=99750 width=0) (actual time=5.214..5.214 rows=19925 loops=1)Index Cond: (c1 && '{1,2}'::integer[])Planning time: 0.095 msExecution time: 32.810 ms
(7 rows)

2. 排序输出

全文检索类型, rum索引
postgres=# explain analyze select * from rum_test where c1 @@ to_tsquery('english','1 | 2') order by c1 <=> to_tsquery('english','1 | 2') offset 19000 limit 100;QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------Limit  (cost=18988.45..19088.30 rows=100 width=1391) (actual time=58.912..59.165 rows=100 loops=1)->  Index Scan using rumidx on rum_test  (cost=16.00..99620.35 rows=99749 width=1391) (actual time=16.426..57.892 rows=19100 loops=1)Index Cond: (c1 @@ '''1'' | ''2'''::tsquery)Order By: (c1 <=> '''1'' | ''2'''::tsquery)Planning time: 0.133 msExecution time: 59.220 ms
(6 rows)全文检索类型, GIN索引
postgres=# explain analyze select * from gin_test where c1 @@ to_tsquery('english','1 | 2') order by c1 offset 19000 limit 100;QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------Limit  (cost=176684.69..176684.94 rows=100 width=1387) (actual time=117.809..117.865 rows=100 loops=1)->  Sort  (cost=176637.19..176886.57 rows=99750 width=1387) (actual time=94.889..116.929 rows=19100 loops=1)Sort Key: c1Sort Method: external merge  Disk: 26968kB->  Bitmap Heap Scan on gin_test  (cost=816.06..99386.94 rows=99750 width=1387) (actual time=9.625..38.336 rows=19847 loops=1)Recheck Cond: (c1 @@ '''1'' | ''2'''::tsquery)Heap Blocks: exact=19764->  Bitmap Index Scan on idx_gin_test  (cost=0.00..791.12 rows=99750 width=0) (actual time=5.610..5.610 rows=19847 loops=1)Index Cond: (c1 @@ '''1'' | ''2'''::tsquery)Planning time: 0.134 msExecution time: 126.122 ms
(11 rows)数组类型, GIN索引
postgres=# explain analyze select * from arr_test where c1 && array[1,2] order by c1 offset 19000 limit 100;QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------Limit  (cost=112837.69..112837.94 rows=100 width=424) (actual time=90.619..90.656 rows=100 loops=1)->  Sort  (cost=112790.19..113039.57 rows=99750 width=424) (actual time=82.067..89.622 rows=19100 loops=1)Sort Key: c1Sort Method: external merge  Disk: 8440kB->  Bitmap Heap Scan on arr_test  (cost=816.06..93595.94 rows=99750 width=424) (actual time=9.087..36.870 rows=19925 loops=1)Recheck Cond: (c1 && '{1,2}'::integer[])Heap Blocks: exact=19605->  Bitmap Index Scan on idx_arr_test  (cost=0.00..791.12 rows=99750 width=0) (actual time=5.138..5.138 rows=19925 loops=1)Index Cond: (c1 && '{1,2}'::integer[])Planning time: 0.122 msExecution time: 93.057 ms
(11 rows)

RUM 附加能力

rum检索支持近似度排行,这个在搜索应用中太有用了。

通过相似度分值表示文本和检索条件的相似度。

// 分词举例
postgres=#  select * from to_tsvector('jiebacfg', '小明硕士毕业于中国科学院计算所,后在日本京都大学深造');to_tsvector
----------------------------------------------------------------------------------'中国科学院':5 '小明':1 '日本京都大学':10 '毕业':3 '深造':11 '硕士':2 '计算所':6
(1 row)
// 有相似度
postgres=#  select * from rum_ts_distance(to_tsvector('jiebacfg', '小明硕士毕业于中国科学院计算所,后在日本京都大学深造') , to_tsquery('计算所'));rum_ts_distance
-----------------16.4493
(1 row)
// 没有相似度
postgres=#  select * from rum_ts_distance(to_tsvector('jiebacfg', '小明硕士毕业于中国科学院计算所,后在日本京都大学深造') , to_tsquery('计算'));rum_ts_distance
-----------------Infinity
(1 row)
// 或相似度
postgres=# select * from rum_ts_distance(to_tsvector('jiebacfg', '小明硕士毕业于中国科学院计算所,后在日本京都大学深造') , to_tsquery('计算所 | 硕士'));rum_ts_distance
-----------------8.22467
(1 row)
// 与相似度
postgres=# select * from rum_ts_distance(to_tsvector('jiebacfg', '小明硕士毕业于中国科学院计算所,后在日本京都大学深造') , to_tsquery('计算所 & 硕士'));rum_ts_distance
-----------------32.8987
(1 row)
// 排序
postgres=# create table test15(c1 tsvector);
CREATE TABLE
postgres=# insert into test15 values (to_tsvector('jiebacfg', 'hello china, i''m digoal')), (to_tsvector('jiebacfg', 'hello world, i''m postgresql')), (to_tsvector('jiebacfg', 'how are you, i''m digoal'));
INSERT 0 3
postgres=# select * from test15;c1
-----------------------------------------------------' ':2,5,9 'china':3 'digoal':10 'hello':1 'm':8' ':2,5,9 'hello':1 'm':8 'postgresql':10 'world':3' ':2,4,7,11 'digoal':12 'm':10
(3 rows)
postgres=# create index idx_test15 on test15 using rum(c1 rum_tsvector_ops);
CREATE INDEX
postgres=# select *,c1 <=> to_tsquery('hello') from test15;c1                          | ?column?
-----------------------------------------------------+----------' ':2,5,9 'china':3 'digoal':10 'hello':1 'm':8     |  16.4493' ':2,5,9 'hello':1 'm':8 'postgresql':10 'world':3 |  16.4493' ':2,4,7,11 'digoal':12 'm':10                     | Infinity
(3 rows)
postgres=# explain select *,c1 <=> to_tsquery('postgresql') from test15 order by c1 <=> to_tsquery('postgresql');QUERY PLAN
--------------------------------------------------------------------------------Index Scan using idx_test15 on test15  (cost=3600.25..3609.06 rows=3 width=36)Order By: (c1 <=> to_tsquery('postgresql'::text))
(2 rows)

小结

正如Oleg说的,RUM非常强大,支持相似度检索,支持非BITMAP scan,从查询效率来看,已经比GIN以及单纯的数组查询效率高出1倍。

忘掉搜索引擎,使用PostgreSQL全文检索吧。

分词方面,PG支持的中文分词插件也很多,例如结巴分词,ZHPARSER。

https://github.com/postgrespro/rum

Count

PostgreSQL 全文检索加速 快到没有朋友 - RUM索引接口(潘多拉魔盒)相关推荐

  1. 中one_气场不比BBA差,续航里程远、加速快的理想ONE值得买吗?

    如今汽车市场竞争激烈程度不用多说,相信大家也都知道.这一现象不仅体现在燃油车领域,新能源汽车的表现也不差.但是由于新能源汽车还处在起步阶段,很多消费者都比较担心续航里程和充电的问题. 而增程式电动车就 ...

  2. 廉价公开课 | 快到没朋友?程序员必会的yolo模型(附福利)

    你发现了吗?当你在网络上看电视剧时,总会"适时"弹出关联性极强的广告!这其中的原理是什么呢? 视频场景广告点位来源主要有2个:字幕词和场景识别.了解这点以后,也许你会问:视频一闪即 ...

  3. 如何在PR软件中制作加速快进视频?

    首先在电脑中打开pr软件,点击新建项目,新建一个视频加速快进的项目. 在新建项目页面输入项目视频的名称,然后点击确定. 进入软件主界面,点击左下角区域导入视频,然后将视频拖至右侧的时间轴上. 右键点击 ...

  4. “快到没朋友”的目标检测模型YOLO v3问世,之后arXiv垮掉了…

    安妮 编译整理 量子位 出品 | 公众号 QbitAI 今天有三件事挺有意思. 一是以"快到没朋友"著称的流行目标检测模型YOLO推出全新v3版,新版本又双叒叕提升了精度和速度.在 ...

  5. 荣耀magicbookr7版linux,实测:荣耀MagicBook 2019锐龙R7版快到没朋友

    原标题:实测:荣耀MagicBook 2019锐龙R7版快到没朋友 荣耀MagicBook 2019锐龙R5版发布之后,市场反应很不错,这算是一款性价比非常不错的办公笔记本电脑,不仅整机性能比上一代有 ...

  6. PostgreSQL 快速给指定表每个字段创建索引 - 2

    标签 PostgreSQL , 索引 , 所有字段 , 并行创建单个索引 , max_parallel_maintenance_workers , 异步调用 , dblink , 并行创建多个索引 , ...

  7. java 朋友圈分享接口_微信发朋友圈api接口调用代码

    微信发朋友圈api接口调用代码,推送微信朋友圈.发朋友圈 /** * 触发推送朋友圈列表 * @author wechatno:tangjinjinwx * startTime传秒 * @blog h ...

  8. 手机内部充电电流控制原理图(如果手机支持快充,比如支持9V快充,则通过充电接口的D+、D-二根线,输出对应的高低电平组合,FP6601就会控制它的3脚接地,4脚悬空,此时R3与R2并联,改变反馈下拉)

    手机内部充电电流控制原理图 来源:电工之家•作者:电工之家• 2019-12-08 10:48 • 7365次阅读 0 手机充电器电流控制方面: 现在的手机充电器,无一例外,都使用了隔离式开关电源电路 ...

  9. 微信朋友圈分享接口使用总结

    微信朋友圈分享接口是非常细节的,而且不好调试,所以在此总结一下,以帮助大家 首先应该遵循微信开发者文档介绍,用接口调试工具将你需要的接口的权限确定一下(这里得去申请接口权限)?然后将这个网址用手机端微 ...

最新文章

  1. xp http文件服务器,在XP sp3下用IIS搭建http服务器总结
  2. win7 设置自动关机
  3. 英伟达RTX 30系列卖得太好,财报业绩创新高,老黄:Arm收购完成时间已确定
  4. 普通软件项目开发过程规范(五)—— 总结 (转)
  5. ios 不同的数据类型转化为json类型
  6. 终于收到HacktoberFest的奖品啦
  7. 工业级光纤收发器产品性能特点介绍
  8. java form 上传文件_java通过表单进行文件上传的几种方法
  9. 【一鸣离职,左晖去世】互联网老兵给大家的三个建议
  10. fork()的一些测试
  11. 微软 azure_有关Microsoft Azure技术的简介和常见问题解答
  12. WannaCry不相信眼泪 它需要你的安全防御与响应能力
  13. .net core 跨平台UI框架 Avalonia
  14. springboot validation详解
  15. 【清华AI自强计划-计算机视觉课程-第三讲课程笔记1】
  16. 天刀 服务器状态,天刀实用技巧_天刀各种游戏小技巧_玩游戏网
  17. 嵌入式监控【v4l2采集-vpu编码-live555推流】
  18. PS|001制作1寸照片
  19. ARM微控制器与嵌入式系统
  20. java基于ssm的校园教学评价系统设计与实现 aspnet python springboot

热门文章

  1. 【Matlab系列】调频信号FM相干解调与非相干解调原理及其实现
  2. 2021年MathorCup高校数学建模挑战赛b题:三维团簇的能量预测(三等)
  3. (详细总结)python爬取 163收件箱邮件内容,收件箱列表的几种方法(urllib, requests, selenium)
  4. 苹果商店APP发布流程(H5 APP苹果发布流程)
  5. python汇率编程_python – 确定汇率的算法
  6. eas 税率修改_如何修改金蝶EAS业务单据中的字段为必填项
  7. 位置式PID和增量式PID的区分
  8. 电子通信计算机专业的英语论文,电子信息工程英文参考文献
  9. 计算机图形学 第四章 光栅图形学
  10. ARCore HDR 光估测深度解析