PostgreSQL 模糊查询+大量重复值匹配 实践 - 分区索引 = any (array())

本文涉及的产品
云数据库 RDS SQL Server,基础系列 2核4GB
云原生数据库 PolarDB 分布式版,标准版 2核8GB
RDS PostgreSQL Serverless,0.5-4RCU 50GB 3个月
推荐场景:
对影评进行热评分析
简介:

标签

PostgreSQL , 模糊查询 , exists , 重复值 , = any (array())


背景

在一些场景中,可能需要对大量的数据TBL_A进行A字段模糊查询,同时将匹配记录的另一个字段B,作为条件输入项,匹配另一张表TBL_B,输出另一张表TBL_B的匹配记录。

例子

小表,表示每个ID的详细信息

create table a (  
  id int primary key,  
  xx text  
);  
  
insert into a select id,'test'||id from generate_series(1,100000) t(id);  

大表,表示FEED日志,一个AID可能产生很多条记录

create table b (  
  aid int,  
  info text  
);  

写入1000万数据

insert into b select random()*9, md5(random()::text) from generate_series(1,10000000);  
  
create extension pg_trgm;  
  
create index idx_b_2 on b using gin (info gin_trgm_ops);  

如果需要用户需要输入AID字段的条件,则建议用这样的复合索引

create extension btree_gin;  
  
create index idx_b_1 on b using gin (aid, info gin_trgm_ops);  

查询info包含某个字符串(前后模糊查询)的数据,找出它们的AID对应的详细信息。

方法1、

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from a where id = any (array(  
  select aid from b where info ~~ '%abc%'  
));  
                                                             QUERY PLAN                                                                
-------------------------------------------------------------------------------------------------------------------------------------  
 Index Scan using a_pkey on public.a  (cost=22849.95..22865.95 rows=10 width=13) (actual time=72.259..72.267 rows=9 loops=1)  
   Output: a.id, a.xx  
   Index Cond: (a.id = ANY ($0))  
   Buffers: shared hit=20209  
   InitPlan 1 (returns $0)  
     ->  Bitmap Heap Scan on public.b  (cost=796.03..22849.65 rows=101010 width=4) (actual time=15.562..61.768 rows=73204 loops=1)  
           Output: b.aid  
           Recheck Cond: (b.info ~~ '%abc%'::text)  
           Heap Blocks: exact=20180  
           Buffers: shared hit=20188  
           ->  Bitmap Index Scan on idx_b_1  (cost=0.00..770.78 rows=101010 width=0) (actual time=12.658..12.658 rows=73204 loops=1)  
                 Index Cond: (b.info ~~ '%abc%'::text)  
                 Buffers: shared hit=8  
 Planning Time: 0.157 ms  
 Execution Time: 72.315 ms  
(15 rows)  

方法2、

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from a where exists (select 1 from b where info ~~ '%abc%' and a.id=b.aid);  
                                                                  QUERY PLAN                                                                     
-----------------------------------------------------------------------------------------------------------------------------------------------  
 Merge Join  (cost=23102.75..23103.04 rows=10 width=13) (actual time=74.316..74.325 rows=9 loops=1)  
   Output: a.id, a.xx  
   Inner Unique: true  
   Merge Cond: (a.id = b.aid)  
   Buffers: shared hit=20191  
   ->  Index Scan using a_pkey on public.a  (cost=0.29..1719.49 rows=100000 width=13) (actual time=0.018..0.021 rows=10 loops=1)  
         Output: a.id, a.xx  
         Buffers: shared hit=3  
   ->  Sort  (cost=23102.44..23102.47 rows=10 width=4) (actual time=74.291..74.292 rows=10 loops=1)  
         Output: b.aid  
         Sort Key: b.aid  
         Sort Method: quicksort  Memory: 25kB  
         Buffers: shared hit=20188  
         ->  HashAggregate  (cost=23102.18..23102.28 rows=10 width=4) (actual time=74.279..74.281 rows=10 loops=1)  
               Output: b.aid  
               Group Key: b.aid  
               Buffers: shared hit=20188  
               ->  Bitmap Heap Scan on public.b  (cost=796.03..22849.65 rows=101010 width=4) (actual time=15.935..61.383 rows=73204 loops=1)  
                     Output: b.aid  
                     Recheck Cond: (b.info ~~ '%abc%'::text)  
                     Heap Blocks: exact=20180  
                     Buffers: shared hit=20188  
                     ->  Bitmap Index Scan on idx_b_1  (cost=0.00..770.78 rows=101010 width=0) (actual time=13.027..13.027 rows=73204 loops=1)  
                           Index Cond: (b.info ~~ '%abc%'::text)  
                           Buffers: shared hit=8  
 Planning Time: 0.344 ms  
 Execution Time: 74.380 ms  
(27 rows)  

结果

postgres=# select * from a where id = any (array(  
  select aid from b where info ~~ '%abc%'  
));  
 id |  xx     
----+-------  
  1 | test1  
  2 | test2  
  3 | test3  
  4 | test4  
  5 | test5  
  6 | test6  
  7 | test7  
  8 | test8  
  9 | test9  
(9 rows)  
  
Time: 57.789 ms  

其他方法

1、使用AID+模糊查询,两个条件,实际效果并不好,因为GIN复合索引实际上是内部BITMAP,并不是真正意义上的多颗树结构(分区结构)。

do language plpgsql $$  
declare  
  v_id int;  
  v_xx text;  
begin  
  for v_id,v_xx in select id,xx from a loop  
    perform 1 from b where aid=v_id and info ~~ '%abc%' limit 1;  
    if found then   
      raise notice '%, %', v_id, v_xx;  
    end if;  
  end loop;  
end;  
$$;  

2、如果aid集合较小,并且可以穷举,也可以使用分区索引(多partial index)。

分区索引模拟,如下

nohup psql -c "create index idx_b_3 on b using gin (info gin_trgm_ops) where aid=1;" >/dev/null 2>&1 &  
nohup psql -c "create index idx_b_4 on b using gin (info gin_trgm_ops) where aid=2;" >/dev/null 2>&1 &  
nohup psql -c "create index idx_b_5 on b using gin (info gin_trgm_ops) where aid=3;" >/dev/null 2>&1 &  
nohup psql -c "create index idx_b_6 on b using gin (info gin_trgm_ops) where aid=4;" >/dev/null 2>&1 &  
nohup psql -c "create index idx_b_7 on b using gin (info gin_trgm_ops) where aid=5;" >/dev/null 2>&1 &  
nohup psql -c "create index idx_b_8 on b using gin (info gin_trgm_ops) where aid=6;" >/dev/null 2>&1 &  
nohup psql -c "create index idx_b_9 on b using gin (info gin_trgm_ops) where aid=7;" >/dev/null 2>&1 &  
nohup psql -c "create index idx_b_10 on b using gin (info gin_trgm_ops) where aid=8;" >/dev/null 2>&1 &  
nohup psql -c "create index idx_b_11 on b using gin (info gin_trgm_ops) where aid=9;" >/dev/null 2>&1 &  
nohup psql -c "create index idx_b_12 on b using gin (info gin_trgm_ops) where aid not in (1,2,3,4,5,6,7,8,9);" >/dev/null 2>&1 &  

查询语句模拟如下

select * from a where id = any (array(  
select * from (select aid from b where aid=1 and info ~~ '%abc%' limit 1) t  
union all  
select * from (select aid from b where aid=2 and info ~~ '%abc%' limit 1) t  
union all  
select * from (select aid from b where aid=3 and info ~~ '%abc%' limit 1) t  
union all  
select * from (select aid from b where aid=4 and info ~~ '%abc%' limit 1) t  
union all  
select * from (select aid from b where aid=5 and info ~~ '%abc%' limit 1) t  
union all  
select * from (select aid from b where aid=6 and info ~~ '%abc%' limit 1) t  
union all  
select * from (select aid from b where aid=7 and info ~~ '%abc%' limit 1) t  
union all  
select * from (select aid from b where aid=8 and info ~~ '%abc%' limit 1) t  
union all  
select * from (select aid from b where aid=9 and info ~~ '%abc%' limit 1) t  
union all  
select * from (select aid from b where aid not in (1,2,3,4,5,6,7,8,9) and info ~~ '%abc%' limit 1) t  
));  
  
 id |  xx     
----+-------  
  1 | test1  
  2 | test2  
  3 | test3  
  4 | test4  
  5 | test5  
  6 | test6  
  7 | test7  
  8 | test8  
  9 | test9  
(9 rows)  
  
Time: 4.970 ms  

执行计划

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from a where id = any (array(  
select * from (select aid from b where aid=1 and info ~~ '%abc%' limit 1) t  
union all  
select * from (select aid from b where aid=2 and info ~~ '%abc%' limit 1) t  
union all  
select * from (select aid from b where aid=3 and info ~~ '%abc%' limit 1) t  
union all  
select * from (select aid from b where aid=4 and info ~~ '%abc%' limit 1) t  
union all  
select * from (select aid from b where aid=5 and info ~~ '%abc%' limit 1) t  
union all  
select * from (select aid from b where aid=6 and info ~~ '%abc%' limit 1) t  
union all  
select * from (select aid from b where aid=7 and info ~~ '%abc%' limit 1) t  
union all  
select * from (select aid from b where aid=8 and info ~~ '%abc%' limit 1) t  
union all  
select * from (select aid from b where aid=9 and info ~~ '%abc%' limit 1) t  
union all  
select * from (select aid from b where aid not in (1,2,3,4,5,6,7,8,9) and info ~~ '%abc%' limit 1) t  
));  
                                                            QUERY PLAN                                                               
-----------------------------------------------------------------------------------------------------------------------------------  
 Index Scan using a_pkey on public.a  (cost=199.57..215.57 rows=10 width=13) (actual time=2.833..2.842 rows=9 loops=1)  
   Output: a.id, a.xx  
   Index Cond: (a.id = ANY ($0))  
   Buffers: shared hit=55  
   InitPlan 1 (returns $0)  
     ->  Append  (cost=0.00..199.27 rows=10 width=4) (actual time=0.249..2.799 rows=10 loops=1)  
           Buffers: shared hit=34  
           ->  Limit  (cost=0.00..15.31 rows=1 width=4) (actual time=0.249..0.249 rows=1 loops=1)  
                 Output: b.aid  
                 Buffers: shared hit=3  
                 ->  Seq Scan on public.b  (cost=0.00..170791.00 rows=11158 width=4) (actual time=0.247..0.247 rows=1 loops=1)  
                       Output: b.aid  
                       Filter: ((b.info ~~ '%abc%'::text) AND (b.aid = 1))  
                       Rows Removed by Filter: 1131  
                       Buffers: shared hit=3  
           ->  Limit  (cost=0.00..14.75 rows=1 width=4) (actual time=0.243..0.244 rows=1 loops=1)  
                 Output: b_1.aid  
                 Buffers: shared hit=3  
                 ->  Seq Scan on public.b b_1  (cost=0.00..170791.00 rows=11576 width=4) (actual time=0.243..0.243 rows=1 loops=1)  
                       Output: b_1.aid  
                       Filter: ((b_1.info ~~ '%abc%'::text) AND (b_1.aid = 2))  
                       Rows Removed by Filter: 1208  
                       Buffers: shared hit=3  
           ->  Limit  (cost=0.00..15.39 rows=1 width=4) (actual time=0.023..0.023 rows=1 loops=1)  
                 Output: b_2.aid  
                 Buffers: shared hit=1  
                 ->  Seq Scan on public.b b_2  (cost=0.00..170791.00 rows=11098 width=4) (actual time=0.023..0.023 rows=1 loops=1)  
                       Output: b_2.aid  
                       Filter: ((b_2.info ~~ '%abc%'::text) AND (b_2.aid = 3))  
                       Rows Removed by Filter: 70  
                       Buffers: shared hit=1  
           ->  Limit  (cost=0.00..14.96 rows=1 width=4) (actual time=0.738..0.738 rows=1 loops=1)  
                 Output: b_3.aid  
                 Buffers: shared hit=8  
                 ->  Seq Scan on public.b b_3  (cost=0.00..170791.00 rows=11414 width=4) (actual time=0.738..0.738 rows=1 loops=1)  
                       Output: b_3.aid  
                       Filter: ((b_3.info ~~ '%abc%'::text) AND (b_3.aid = 4))  
                       Rows Removed by Filter: 3563  
                       Buffers: shared hit=8  
           ->  Limit  (cost=0.00..15.18 rows=1 width=4) (actual time=0.068..0.068 rows=1 loops=1)  
                 Output: b_4.aid  
                 Buffers: shared hit=1  
                 ->  Seq Scan on public.b b_4  (cost=0.00..170791.00 rows=11253 width=4) (actual time=0.068..0.068 rows=1 loops=1)  
                       Output: b_4.aid  
                       Filter: ((b_4.info ~~ '%abc%'::text) AND (b_4.aid = 5))  
                       Rows Removed by Filter: 311  
                       Buffers: shared hit=1  
           ->  Limit  (cost=0.00..15.09 rows=1 width=4) (actual time=0.178..0.178 rows=1 loops=1)  
                 Output: b_5.aid  
                 Buffers: shared hit=2  
                 ->  Seq Scan on public.b b_5  (cost=0.00..170791.00 rows=11316 width=4) (actual time=0.178..0.178 rows=1 loops=1)  
                       Output: b_5.aid  
                       Filter: ((b_5.info ~~ '%abc%'::text) AND (b_5.aid = 6))  
                       Rows Removed by Filter: 894  
                       Buffers: shared hit=2  
           ->  Limit  (cost=0.00..15.50 rows=1 width=4) (actual time=0.323..0.323 rows=1 loops=1)  
                 Output: b_6.aid  
                 Buffers: shared hit=4  
                 ->  Seq Scan on public.b b_6  (cost=0.00..170791.00 rows=11020 width=4) (actual time=0.322..0.322 rows=1 loops=1)  
                       Output: b_6.aid  
                       Filter: ((b_6.info ~~ '%abc%'::text) AND (b_6.aid = 7))  
                       Rows Removed by Filter: 1599  
                       Buffers: shared hit=4  
           ->  Limit  (cost=0.00..15.25 rows=1 width=4) (actual time=0.449..0.449 rows=1 loops=1)  
                 Output: b_7.aid  
                 Buffers: shared hit=5  
                 ->  Seq Scan on public.b b_7  (cost=0.00..170791.00 rows=11199 width=4) (actual time=0.448..0.448 rows=1 loops=1)  
                       Output: b_7.aid  
                       Filter: ((b_7.info ~~ '%abc%'::text) AND (b_7.aid = 8))  
                       Rows Removed by Filter: 2231  
                       Buffers: shared hit=5  
           ->  Limit  (cost=0.00..32.33 rows=1 width=4) (actual time=0.130..0.131 rows=1 loops=1)  
                 Output: b_8.aid  
                 Buffers: shared hit=2  
                 ->  Seq Scan on public.b b_8  (cost=0.00..170791.00 rows=5283 width=4) (actual time=0.130..0.130 rows=1 loops=1)  
                       Output: b_8.aid  
                       Filter: ((b_8.info ~~ '%abc%'::text) AND (b_8.aid = 9))  
                       Rows Removed by Filter: 623  
                       Buffers: shared hit=2  
           ->  Limit  (cost=0.00..45.36 rows=1 width=4) (actual time=0.392..0.392 rows=1 loops=1)  
                 Output: b_9.aid  
                 Buffers: shared hit=5  
                 ->  Seq Scan on public.b b_9  (cost=0.00..258291.00 rows=5694 width=4) (actual time=0.392..0.392 rows=1 loops=1)  
                       Output: b_9.aid  
                       Filter: ((b_9.info ~~ '%abc%'::text) AND (b_9.aid <> ALL ('{1,2,3,4,5,6,7,8,9}'::integer[])))  
                       Rows Removed by Filter: 1931  
                       Buffers: shared hit=5  
 Planning Time: 1.411 ms  
 Execution Time: 2.963 ms  
(89 rows)  

可见,分区索引,效果是比较好的。期待PG尽快支持。

另外,对于本例,用户需要IN很多值时,建议使用较大的work_mem,同时使用any(array())的写法。

work_mem='4MB'  

参考

《HTAP数据库 PostgreSQL 场景与性能测试之 25 - (OLTP) IN , EXISTS 查询》

《HTAP数据库 PostgreSQL 场景与性能测试之 26 - (OLTP) NOT IN、NOT EXISTS 查询》

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍如何基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
2月前
|
存储 SQL 关系型数据库
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
|
2月前
|
存储 关系型数据库 MySQL
MySQL数据库索引的数据结构?
MySQL中默认使用B+tree索引,它是一种多路平衡搜索树,具有树高较低、检索速度快的特点。所有数据存储在叶子节点,非叶子节点仅作索引,且叶子节点形成双向链表,便于区间查询。
86 4
|
4月前
|
存储 关系型数据库 MySQL
阿里面试:MySQL 一个表最多 加几个索引? 6个?64个?还是多少?
阿里面试:MySQL 一个表最多 加几个索引? 6个?64个?还是多少?
阿里面试:MySQL 一个表最多 加几个索引? 6个?64个?还是多少?
|
3月前
|
存储 监控 关系型数据库
B-tree不是万能药:PostgreSQL索引失效的7种高频场景与破解方案
在PostgreSQL优化实践中,B-tree索引虽承担了80%以上的查询加速任务,但因多种原因可能导致索引失效,引发性能骤降。本文深入剖析7种高频失效场景,包括隐式类型转换、函数包裹列、前导通配符等,并通过实战案例揭示问题本质,提供生产验证的解决方案。同时,总结索引使用决策矩阵与关键原则,助你让索引真正发挥作用。
211 0
|
6月前
|
关系型数据库 MySQL 数据库
Mysql的索引
MYSQL索引主要有 : 单列索引 , 组合索引和空间索引 , 用的比较多的就是单列索引和组合索引 , 空间索引我这边没有用到过 单列索引 : 在MYSQL数据库表的某一列上面创建的索引叫单列索引 , 单列索引又分为 ● 普通索引:MySQL中基本索引类型,没有什么限制,允许在定义索引的列中插入重复值和空值,纯粹为了查询数据更快一点。 ● 唯一索引:索引列中的值必须是唯一的,但是允许为空值 ● 主键索引:是一种特殊的唯一索引,不允许有空值 ● 全文索引: 只有在MyISAM引擎、InnoDB(5.6以后)上才能使⽤用,而且只能在CHAR,VARCHAR,TEXT类型字段上使⽤用全⽂文索引。
|
2月前
|
存储 SQL 关系型数据库
MySQL 核心知识与索引优化全解析
本文系统梳理了 MySQL 的核心知识与索引优化策略。在基础概念部分,阐述了 char 与 varchar 在存储方式和性能上的差异,以及事务的 ACID 特性、并发事务问题及对应的隔离级别(MySQL 默认 REPEATABLE READ)。 索引基础部分,详解了 InnoDB 默认的 B+tree 索引结构(多路平衡树、叶子节点存数据、双向链表支持区间查询),区分了聚簇索引(数据与索引共存,唯一)和二级索引(数据与索引分离,多个),解释了回表查询的概念及优化方法,并分析了 B+tree 作为索引结构的优势(树高低、效率稳、支持区间查询)。 索引优化部分,列出了索引创建的六大原则
|
3月前
|
存储 关系型数据库 MySQL
MySQL覆盖索引解释
总之,覆盖索引就像是图书馆中那些使得搜索变得极为迅速和简单的工具,一旦正确使用,就会让你的数据库查询飞快而轻便。让数据检索就像是读者在图书目录中以最快速度找到所需信息一样简便。这样的效率和速度,让覆盖索引成为数据库优化师傅们手中的尚方宝剑,既能够提升性能,又能够保持系统的整洁高效。
100 9
|
4月前
|
机器学习/深度学习 关系型数据库 MySQL
对比MySQL全文索引与常规索引的互异性
现在,你或许明白了这两种索引的差异,但任何技术决策都不应仅仅基于理论之上。你可以创建你的数据库实验环境,尝试不同类型的索引,看看它们如何影响性能,感受它们真实的力量。只有这样,你才能熟悉它们,掌握什么时候使用全文索引,什么时候使用常规索引,以适应复杂多变的业务需求。
100 12
|
8月前
|
存储 关系型数据库 MySQL
MySQL索引学习笔记
本文深入探讨了MySQL数据库中慢查询分析的关键概念和技术手段。
565 81
|
5月前
|
SQL 存储 关系型数据库
MySQL选错索引了怎么办?
本文探讨了MySQL中因索引选择不当导致查询性能下降的问题。通过创建包含10万行数据的表并插入数据,分析了一条简单SQL语句在不同场景下的执行情况。实验表明,当数据频繁更新时,MySQL可能因统计信息不准确而选错索引,导致全表扫描。文章深入解析了优化器判断扫描行数的机制,指出基数统计误差是主要原因,并提供了通过`analyze table`重新统计索引信息的解决方法。
132 3

相关产品

  • 云原生数据库 PolarDB
  • 云数据库 RDS PostgreSQL 版
  • 推荐镜像

    更多