PostgreSQL 全表 全字段 模糊查询的毫秒级高效实现 - 搜索引擎也颤抖了

本文涉及的产品
云数据库 RDS SQL Server,基础系列 2核4GB
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
云原生数据库 PolarDB 分布式版,标准版 2核8GB
简介: 标签 PostgreSQL , 分词 , 全文检索 , 全字段检索 , 任意字段检索 , 下拉框选择 , 搜索引擎 背景 在一些应用程序中,可能需要对表的所有字段进行检索,有些字段可能需要精准查询,有些字段可能需要模糊查询或全文检索。 比如一些前端页面下拉框的勾选和选择。 这种需求对于

标签

PostgreSQL , 分词 , 全文检索 , 全字段检索 , 任意字段检索 , 下拉框选择 , 搜索引擎


背景

在一些应用程序中,可能需要对表的所有字段进行检索,有些字段可能需要精准查询,有些字段可能需要模糊查询或全文检索。

比如一些前端页面下拉框的勾选和选择。

这种需求对于应用开发人员来说,会很蛋疼,因为写SQL很麻烦,例子:

之前写过一篇文章来解决这个问题

《PostgreSQL 行级 全文检索》

使用的是全文检索,而当用户的需求为模糊查询时? 如何来解决呢?

不难想到我之前写过的一系列文章

《PostgreSQL 百亿数据 秒级响应 正则及模糊查询》

《PostgreSQL 1000亿数据量 正则匹配 速度与激情》

《中文模糊查询性能优化 by PostgreSQL trgm》

《从难缠的模糊查询聊开 - PostgreSQL独门绝招之一 GIN , GiST , SP-GiST , RUM 索引原理与技术背景》

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

全表,所有字段的模糊查询,应该如何做呢 ?

关键技术还是pg_trgm。

《PostgreSQL 9.3 pg_trgm imporve support multi-bytes char and gist,gin index for reg-exp search》

全表全字段模糊查询的实现例子

比如有一张这样的表,有若干个字段,然后前端设计了一个页面,允许用户进行模糊搜索,但是搜索的范围是所有字段。

这样做用户体验是好了,但是对于程序来说有点蛋疼,因为我们并不知道用户想要搜索的是哪个或哪些字段。

那么怎么能做到高效的匹配呢?

创建测试表,生成测试数据

postgres=# create table t(phonenum text, info text, c1 int, c2 text, c3 text, c4 timestamp);    
CREATE TABLE    
postgres=# insert into t values ('13888888888','i am digoal, a postgresqler',123,'china','中华人民共和国,阿里巴巴,阿',now());    
INSERT 0 1    
postgres=# select * from t;    
  phonenum   |            info             | c1  |  c2   |              c3              |             c4                 
-------------+-----------------------------+-----+-------+------------------------------+----------------------------    
 13888888888 | i am digoal, a postgresqler | 123 | china | 中华人民共和国,阿里巴巴,阿 | 2016-04-19 11:15:55.208658    
(1 row)    

首先,被搜索的字段中如果有中文或者其他mutli-bytes字符,那么不能使用collate, ctype=c的数据库。

还好阿里云RDS PostgreSQL默认就不是C的,很棒。

如果不是,你可以这么指定collate 和 ctype  

postgres=# create database test with template template0 lc_collate 'zh_CN.utf8' lc_ctype 'zh_CN.utf8';  

那么接下来,我们要做的是,建立支持模糊查询的函数索引,

create extension pg_trgm;  

create or replace function record_to_text(anyelement) returns text as $$  
  select $1::text;                        
$$ language sql strict immutable;  

test=# create index idx_t_1 on t using gin (record_to_text(t) gin_trgm_ops) ;    
CREATE INDEX  

当需要使用分页,或者结果集很大时,建议使用gist   
test=# create index idx_t_2 on t using gist (record_to_text(t) gist_trgm_ops) ;   
CREATE INDEX   

查询测试

test=# explain select * from t where record_to_text(t) ~ 'digoal';  
                            QUERY PLAN                               
-------------------------------------------------------------------  
 Index Scan using idx_t_2 on t  (cost=0.38..8.39 rows=1 width=140)  
   Index Cond: (record_to_text(t.*) ~ 'digoal'::text)  
(2 rows)  

查询性能测试

先插一堆数据进去  
postgres=# insert into t select * from t;  
INSERT 0 4194304  
test=# select count(*) from t;  
  count    
---------  
 4194304  
(1 row)  

然后插几条不一样的  

insert into t values ('13888889999','i am dege, a postgresqler',123,'china','德歌 德哥 刘德华 彭德怀',now());    
insert into t values ('13888889999','i am dege, a postgresqler',123,'china','德歌 德哥 刘德华 彭德怀',now());    

vacuum analyze t;  

查询速度杠杠的

test=# explain (analyze,verbose,timing,costs,buffers) select * from t where record_to_text(t) ~ 'dege';  
                                                     QUERY PLAN                                                       
--------------------------------------------------------------------------------------------------------------------  
 Index Scan using idx_t_2 on public.t  (cost=0.41..2.43 rows=1 width=101) (actual time=0.236..0.254 rows=2 loops=1)  
   Output: phonenum, info, c1, c2, c3, c4  
   Index Cond: (record_to_text(t.*) ~ 'dege'::text)  
   Buffers: shared hit=5  
 Planning time: 0.349 ms  
 Execution time: 0.301 ms  
(6 rows)  

测试查询包含刘德华的行(因为行很少,所以建议使用GIN索引)

test=# /*+ BitmapScan(t idx_t_1) */ explain (analyze,verbose,timing,costs,buffers) select * from t where record_to_text(t) ~ '刘德华' limit 10;  
LOG:  available indexes for BitmapScan(t): idx_t_1  
LOG:  pg_hint_plan:  
used hint:  
BitmapScan(t idx_t_1)  
not used hint:  
duplication hint:  
error hint:  

LOG:  pg_hint_plan:  
used hint:  
not used hint:  
BitmapScan(t idx_t_1)  
duplication hint:  
error hint:  

                                                       QUERY PLAN                                                         
------------------------------------------------------------------------------------------------------------------------  
 Limit  (cost=441.00..442.26 rows=1 width=101) (actual time=0.239..0.255 rows=2 loops=1)  
   Output: phonenum, info, c1, c2, c3, c4  
   Buffers: shared hit=4  
   ->  Bitmap Heap Scan on public.t  (cost=441.00..442.26 rows=1 width=101) (actual time=0.238..0.252 rows=2 loops=1)  
         Output: phonenum, info, c1, c2, c3, c4  
         Recheck Cond: (record_to_text(t.*) ~ '刘德华'::text)  
         Heap Blocks: exact=1  
         Buffers: shared hit=4  
         ->  Bitmap Index Scan on idx_t_1  (cost=0.00..441.00 rows=1 width=0) (actual time=0.086..0.086 rows=2 loops=1)  
               Index Cond: (record_to_text(t.*) ~ '刘德华'::text)  
               Buffers: shared hit=3  
 Planning time: 0.494 ms  
 Execution time: 0.313 ms  
(13 rows)  


test=# /*+ BitmapScan(t idx_t_1) */  select * from t where record_to_text(t) ~ '刘德华' limit 10;  
LOG:  available indexes for BitmapScan(t): idx_t_1  
LOG:  pg_hint_plan:  
used hint:  
BitmapScan(t idx_t_1)  
not used hint:  
duplication hint:  
error hint:  

LOG:  pg_hint_plan:  
used hint:  
not used hint:  
BitmapScan(t idx_t_1)  
duplication hint:  
error hint:  

  phonenum   |           info            | c1  |  c2   |           c3            |             c4               
-------------+---------------------------+-----+-------+-------------------------+----------------------------  
 13888889999 | i am dege, a postgresqler | 123 | china | 德歌 德哥 刘德华 彭德怀 | 2017-01-06 17:04:42.19215  
 13888889999 | i am dege, a postgresqler | 123 | china | 德歌 德哥 刘德华 彭德怀 | 2017-01-06 17:04:42.514895  
(2 rows)  

Time: 1.225 ms  

语句超时

通常这种索引命中,根据返回的结果集数量,响应时间可能是 0.几 毫秒到 几十 毫秒不等。

不过有些时候,可能因为用户输入的信息量太少,比如输入了2个字符,那么被匹配到的token信息会很多,导致变慢。

使用GiST可以缓解。

那么应用层可以做一个保护,比如超过1秒,则报语句超时。

postgres=# set statement_timeout = '1s';  
SET  

or  

test=# /*+ Set(statement_timeout 1s) */ select * from t where record_to_text(t) ~ 'd' limit 10;  
LOG:  pg_hint_plan:  
used hint:  
Set(statement_timeout 1s)  
not used hint:  
duplication hint:  
error hint:  

LOG:  pg_hint_plan:  
used hint:  
Set(statement_timeout 1s)  
not used hint:  
duplication hint:  
error hint:  

  phonenum   |            info             | c1  |  c2   |              c3              |             c4               
-------------+-----------------------------+-----+-------+------------------------------+----------------------------  
 13888888888 | i am digoal, a postgresqler | 123 | china | 中华人民共和国,阿里巴巴,阿 | 2017-01-06 16:51:16.840941  
 13888888888 | i am digoal, a postgresqler | 123 | china | 中华人民共和国,阿里巴巴,阿 | 2017-01-06 16:51:16.840941  
 13888888888 | i am digoal, a postgresqler | 123 | china | 中华人民共和国,阿里巴巴,阿 | 2017-01-06 16:51:16.840941  
 13888888888 | i am digoal, a postgresqler | 123 | china | 中华人民共和国,阿里巴巴,阿 | 2017-01-06 16:51:16.840941  
 13888888888 | i am digoal, a postgresqler | 123 | china | 中华人民共和国,阿里巴巴,阿 | 2017-01-06 16:51:16.840941  
 13888888888 | i am digoal, a postgresqler | 123 | china | 中华人民共和国,阿里巴巴,阿 | 2017-01-06 16:51:16.840941  
 13888888888 | i am digoal, a postgresqler | 123 | china | 中华人民共和国,阿里巴巴,阿 | 2017-01-06 16:51:16.840941  
 13888888888 | i am digoal, a postgresqler | 123 | china | 中华人民共和国,阿里巴巴,阿 | 2017-01-06 16:51:16.840941  
 13888888888 | i am digoal, a postgresqler | 123 | china | 中华人民共和国,阿里巴巴,阿 | 2017-01-06 16:51:16.840941  
 13888888888 | i am digoal, a postgresqler | 123 | china | 中华人民共和国,阿里巴巴,阿 | 2017-01-06 16:51:16.840941  
(10 rows)  

hint的使用

使用规则很简单

当使用游标返回时,使用gist

当输入的字符少于3个时,使用GIST

当评估行很少时,使用GIN

其他情况都是要GIN

有了以上规则,你就可以通过HINT,强制使用哪个索引了。

hint使用方法参考:

《从难缠的模糊查询聊开 - PostgreSQL独门绝招之一 GIN , GiST , SP-GiST , RUM 索引原理与技术背景》

其他优化

业务层面也可以做出一些优化,比如可以先用全文检索,如果没有匹配到,再用模糊查询。

又比如gist, gin, rum索引应该如何选择,可以参考这篇文档

《从难缠的模糊查询聊开 - PostgreSQL独门绝招之一 GIN , GiST , SP-GiST , RUM 索引原理与技术背景》

只读实例

按照前面的测试,通常来说一个查询的响应应该在1毫秒以内,

对于一个32核的机器,这种模糊查询能达到的QPS估计在8万左右。

如果你发现单节点,在已优化的情况下,已经不能满足查询的并发,那么可以构建只读实例。

构建只读实例的方法也很简单,请参考

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

https://github.com/aliyun/rds_dbsync

参考

《PostgreSQL 行级 全文检索》

《PostgreSQL 百亿数据 秒级响应 正则及模糊查询》

《PostgreSQL 1000亿数据量 正则匹配 速度与激情》

《中文模糊查询性能优化 by PostgreSQL trgm》

搜索引擎引以为豪的rank排序, phrase已经不是什么秘密,在PostgreSQL里面已经攻破。如下文章有介绍。   

《从难缠的模糊查询聊开 - PostgreSQL独门绝招之一 GIN , GiST , SP-GiST , RUM 索引原理与技术背景》

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

《PostgreSQL 9.3 pg_trgm imporve support multi-bytes char and gist,gin index for reg-exp search》

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
6月前
|
关系型数据库 PostgreSQL
PostgreSQL排序字段不唯一导致分页查询结果出现重复数据
PostgreSQL排序字段不唯一导致分页查询结果出现重复数据
134 0
|
6月前
|
SQL 关系型数据库 数据库
实时计算 Flink版操作报错之使用SQL 将 PostgreSQL 的 date 类型字段转换为 TIMESTAMP 类型时遇到报错,该如何处理
在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
|
6月前
|
运维 Cloud Native 关系型数据库
云原生数据仓库产品使用合集之原生数据仓库AnalyticDB PostgreSQL版如果是列存表的话, adb支持通过根据某个字段做upsert吗
阿里云AnalyticDB提供了全面的数据导入、查询分析、数据管理、运维监控等功能,并通过扩展功能支持与AI平台集成、跨地域复制与联邦查询等高级应用场景,为企业构建实时、高效、可扩展的数据仓库解决方案。以下是对AnalyticDB产品使用合集的概述,包括数据导入、查询分析、数据管理、运维监控、扩展功能等方面。
|
5月前
|
SQL 关系型数据库 PostgreSQL
PostgreSQL和greenplum的copy命令可以添加字段吗?
【6月更文挑战第5天】PostgreSQL和greenplum的copy命令可以添加字段吗?
90 3
|
5月前
|
关系型数据库 PostgreSQL
postgresql如何将没有关联关系的两张表的字段合并
【6月更文挑战第2天】postgresql如何将没有关联关系的两张表的字段合并
141 3
|
6月前
|
关系型数据库 MySQL 数据库
实时计算 Flink版产品使用合集之使用PostgreSQL作为源时,遇到before字段为NULL该如何处理
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
6月前
|
分布式计算 关系型数据库 大数据
MaxCompute产品使用合集之怎么才可以将 PostgreSQL 中的 geometry 空间类型字段同步到 MaxCompute 或另一个 PostgreSQL 数据库
MaxCompute作为一款全面的大数据处理平台,广泛应用于各类大数据分析、数据挖掘、BI及机器学习场景。掌握其核心功能、熟练操作流程、遵循最佳实践,可以帮助用户高效、安全地管理和利用海量数据。以下是一个关于MaxCompute产品使用的合集,涵盖了其核心功能、应用场景、操作流程以及最佳实践等内容。
|
6月前
|
SQL 关系型数据库 PostgreSQL
PostgreSQL【SQL 01】根据条件更新字段值或追加信息STRPOS(string, substring)函数使用及LIKE函数对比
PostgreSQL【SQL 01】根据条件更新字段值或追加信息STRPOS(string, substring)函数使用及LIKE函数对比
156 0
java初中级面试题(SSM+Mysql+微服务(SpringCloud+Dubbo)+消息队列(RocketMQ)+缓存(Redis+MongoDB)+设计模式+搜索引擎(ES)+JVM
java初中级面试题(SSM+Mysql+微服务(SpringCloud+Dubbo)+消息队列(RocketMQ)+缓存(Redis+MongoDB)+设计模式+搜索引擎(ES)+JVM
520 0
java初中级面试题(SSM+Mysql+微服务(SpringCloud+Dubbo)+消息队列(RocketMQ)+缓存(Redis+MongoDB)+设计模式+搜索引擎(ES)+JVM
java初中级面试题(SSM+Mysql+微服务(SpringCloud+Dubbo)+消息队列(RocketMQ)+缓存(Redis+MongoDB)+设计模式+搜索引擎(ES)+JVM
659 0

相关产品

  • 云原生数据库 PolarDB
  • 云数据库 RDS PostgreSQL 版