PostgreSQL 一复合查询SQL优化例子 - (多个exists , 范围检索 , IN检索 , 模糊检索 组合)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
简介:

标签

PostgreSQL , 多个exists , 范围检索 , IN检索 , 模糊检索 , 组合 , gin , recheck , filter , subplan


背景

当一个SQL包含复杂的多个exists , 范围检索 , IN检索 , 模糊检索 , 组合查询时,可能由于索引使用不当导致查询性能较慢。

主要的问题在于,索引使用不当,可能导致几个问题:

1、索引本身扫描的耗时过多

2、位图扫描引入的recheck过多

3、subplan 引入的 filter过多

一个现实的例子,可以看到耗时集中在recheck和filter上面,每个索引扫描返回的记录数都很多,但是组合起来是0条符合条件的记录。

问题就出在索引不正确上,导致了问题。

->  Subquery Scan on "*SELECT* 2"  (cost=273453.65..432483146.70 rows=223 width=349) (actual time=25932.371..25932.371 rows=0 loops=1)  
      Output: ...................................  
      Buffers: shared hit=920071 read=269255  
      I/O Timings: read=1552.767  
      ->  Bitmap Heap Scan on zjxftypt.tab1010201 t_1  (cost=273453.65..432483144.47 rows=223 width=349) (actual time=25932.370..25932.370 rows=0 loops=1)  
            Output: t_1.storeid, t_1.xfjbh, t_1.wtsd, t_1.rs, t_1.digoal123x, t_1.dz, t_1.blfsjd, t_1.qx, t_1.gk, t_1.xfrq, t_1.djsj, t_1.djdw, t_1.xfjclzt, t_1.digoal123, t_1.xfxs  
            -- 位图扫描的条件重新过滤 , 过滤太多了  
	    Recheck Cond: ((t_1.xfrq < (to_date('2018-06-11'::character varying, 'yyyy-mm-dd'::character varying) + 1)) AND (t_1.xfrq >= to_date('2014-02-12'::character varying, 'yyyy-mm-dd'::character varying)) AND (t_1.digoal123 = 1::numeric))  
            Rows Removed by Index Recheck: 1214155  
              
	    -- 过滤exists的JOIN条件值是否满足 ,过滤太多了  
	    Filter: (((t_1.digoal123x)::text ~~ '%阿里巴巴%'::text) AND ((alternatives: SubPlan 4 or hashed SubPlan 5) OR(alternatives: SubPlan 6 or hashed SubPlan 7)))  
            Rows Removed by Filter: 5215804  
            Buffers: shared hit=920071 read=269255  
            I/O Timings: read=1552.767  
            -- 条件1,2位图扫描  
	    ->  BitmapAnd  (cost=273453.65..273453.65 rows=4909643 width=0) (actual time=2510.718..2510.718 rows=0 loops=1)  
                  Buffers: shared hit=27036 read=16539  
                  I/O Timings: read=101.425  
                  -- 自身条件1 符合条件的记录太多了  
		  ->  Bitmap Index Scan on index_tab1010201_xfrq  (cost=0.00..126565.99 rows=4943755 width=0) (actual time=1085.429..1085.429 rows=5268071 loops=1)  
                        Index Cond: ((t_1.xfrq < (to_date('2018-06-11'::character varying, 'yyyy-mm-dd'::character varying) + 1)) AND (t_1.xfrq >= to_date('2014-02-12'::character varying, 'yyyy-mm-dd'::character varying)))  
                        Buffers: shared hit=3288 read=16539  
                        I/O Timings: read=101.425  
                  -- 自身条件2 符合条件的记录太多了  
		  ->  Bitmap Index Scan on index_tab1010201_digoal123  (cost=0.00..146887.30 rows=6599316 width=0) (actual time=1355.825..1355.825 rows=6845646 loops=1)  
                        Index Cond: (t_1.digoal123 = 1::numeric)  
                        Buffers: shared hit=23748  
                  ..............sub plans  

优化举例

1、复现问题,创建测试表

create table test(id int, c1 text, c2 date, c3 text);  

SQL如下

select * from test   
where   
(  
  exists (select 1 from pg_class where oid::int = test.id)   
  or   
  exists (select 1 from pg_attribute where attrelid::int=test.id)   
)   
and c1 in ('1','2','3')   
and c2 between current_date-1 and current_date    
and c3 ~ 'abcdef';   

2、写入测试护甲1000万条

insert into test select id, (random()*10)::int::text, current_date, md5(random()::text) from generate_series(1,10000000) t(id);  

3、创建索引,使之可以在索引层面过滤掉所有数据

create extension pg_trgm;  
create extension btree_gin;  
create index idx_test_1 on test using gin (c1, c2, c3 gin_trgm_ops);  

如果是复现问题,应该是这两个索引

create index idx1 on test (c1);  
  
create index idx2 on test (c2);  

4、查看执行计划

postgres=# explain (analyze,verbose,timing,costs,buffers)   
select * from test   
where   
(  
  exists (select 1 from pg_class where oid::int = test.id)   
  or   
  exists (select 1 from pg_attribute where attrelid::int=test.id)   
)   
and c1 in ('1','2','3')   
and c2 between current_date-1 and current_date    
and c3 ~ 'abcdef';   
                                                                                         QUERY PLAN                                                                                           
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------  
 Bitmap Heap Scan on public.test  (cost=156.43..8593.79 rows=228 width=43) (actual time=837.151..837.151 rows=0 loops=1)  
   Output: test.id, test.c1, test.c2, test.c3  
   -- 位图扫描重新RECHECK过滤  
   Recheck Cond: ((test.c1 = ANY ('{1,2,3}'::text[])) AND (test.c2 >= (CURRENT_DATE - 1)) AND (test.c2 <= CURRENT_DATE) AND (test.c3 ~ 'abcdef'::text))  
   Rows Removed by Index Recheck: 1  
     
   -- exists子句的条件检查,过滤   
   Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) OR (alternatives: SubPlan 3 or hashed SubPlan 4))  
   Rows Removed by Filter: 7  
   Heap Blocks: exact=8  
   Buffers: shared hit=11658 read=23  
     
   -- 所有条件压到GIN复合索引里面  
   -- GIN多个条件时,会自动内部位图扫描  
   ->  Bitmap Index Scan on idx_test_1  (cost=0.00..156.37 rows=304 width=0) (actual time=834.418..834.418 rows=8 loops=1)  
         Index Cond: ((test.c1 = ANY ('{1,2,3}'::text[])) AND (test.c2 >= (CURRENT_DATE - 1)) AND (test.c2 <= CURRENT_DATE) AND (test.c3 ~ 'abcdef'::text))  
         Buffers: shared hit=11582 read=23  
   SubPlan 1  
     ->  Seq Scan on pg_catalog.pg_class  (cost=0.00..15.84 rows=1 width=0) (never executed)  
           Filter: ((pg_class.oid)::integer = test.id)  
   SubPlan 2  
     ->  Seq Scan on pg_catalog.pg_class pg_class_1  (cost=0.00..14.87 rows=387 width=4) (actual time=0.014..0.155 rows=388 loops=1)  
           Output: (pg_class_1.oid)::integer  
           Buffers: shared hit=11  
   SubPlan 3  
     ->  Index Only Scan using pg_attribute_relid_attnum_index on pg_catalog.pg_attribute  (cost=0.28..84.39 rows=8 width=0) (never executed)  
           Filter: ((pg_attribute.attrelid)::integer = test.id)  
           Heap Fetches: 0  
   SubPlan 4  
     ->  Index Only Scan using pg_attribute_relid_attnum_index on pg_catalog.pg_attribute pg_attribute_1  (cost=0.28..77.13 rows=2904 width=4) (actual time=0.029..1.081 rows=2941 loops=1)  
           Output: (pg_attribute_1.attrelid)::integer  
           Heap Fetches: 459  
           Buffers: shared hit=57  
 Planning time: 1.070 ms  
 Execution time: 839.834 ms  
(29 rows)  

看起来还不错,但是仔细深究实际上并没有优化太多,还可以有更好的优化。

5、深入优化,需要理解GIN复合索引内部的执行机制(位图扫描)。

因为满足C3条件的记录本身就很少,所以完全不需要使用GIN内部的位图扫描。

postgres=# select count(*) from test where c3 ~ 'abcdef';  
 count   
-------  
    23  
(1 row)  

修改为如下索引

postgres=# drop index idx_test_1 ;  
DROP INDEX  
  
postgres=# create index idx_test_1 on test using gin (c3 gin_trgm_ops) ;  
CREATE INDEX  

6、耗时编程24毫秒

postgres=# explain (analyze,verbose,timing,costs,buffers)   
select * from test   
where   
(  
  exists (select 1 from pg_class where oid::int = test.id)   
  or   
  exists (select 1 from pg_attribute where attrelid::int=test.id)  
)   
and c1 in ('1','2','3')  
and c2 between current_date-1 and current_date   
and c3 ~ 'abcdef';  
                                                                                                       QUERY PLAN                                                                                                         
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------  
 Bitmap Heap Scan on public.test  (cost=53.76..27798.16 rows=228 width=43) (actual time=24.287..24.287 rows=0 loops=1)  
   Output: test.id, test.c1, test.c2, test.c3  
   Recheck Cond: (test.c3 ~ 'abcdef'::text)  
   Rows Removed by Index Recheck: 6  
   Filter: ((test.c1 = ANY ('{1,2,3}'::text[])) AND (test.c2 <= CURRENT_DATE) AND (test.c2 >= (CURRENT_DATE - 1)) AND ((alternatives: SubPlan 1 or hashed SubPlan 2) OR (alternatives: SubPlan 3 or hashed SubPlan 4)))  
   Rows Removed by Filter: 23  
   Heap Blocks: exact=29  
   Buffers: shared hit=226  
   ->  Bitmap Index Scan on idx_test_1  (cost=0.00..53.70 rows=1000 width=0) (actual time=21.517..21.517 rows=29 loops=1)  
         Index Cond: (test.c3 ~ 'abcdef'::text)  
         Buffers: shared hit=128  
   SubPlan 1  
     ->  Seq Scan on pg_catalog.pg_class  (cost=0.00..15.84 rows=1 width=0) (never executed)  
           Filter: ((pg_class.oid)::integer = test.id)  
   SubPlan 2  
     ->  Seq Scan on pg_catalog.pg_class pg_class_1  (cost=0.00..14.87 rows=387 width=4) (actual time=0.011..0.156 rows=387 loops=1)  
           Output: (pg_class_1.oid)::integer  
           Buffers: shared hit=11  
   SubPlan 3  
     ->  Index Only Scan using pg_attribute_relid_attnum_index on pg_catalog.pg_attribute  (cost=0.28..84.39 rows=8 width=0) (never executed)  
           Filter: ((pg_attribute.attrelid)::integer = test.id)  
           Heap Fetches: 0  
   SubPlan 4  
     ->  Index Only Scan using pg_attribute_relid_attnum_index on pg_catalog.pg_attribute pg_attribute_1  (cost=0.28..77.13 rows=2904 width=4) (actual time=0.028..1.099 rows=2938 loops=1)  
           Output: (pg_attribute_1.attrelid)::integer  
           Heap Fetches: 456  
           Buffers: shared hit=58  
 Planning time: 0.801 ms  
 Execution time: 24.403 ms  
(29 rows)  
  
Time: 26.052 ms  

小结

本文的SQL比较复杂,优化的思路和其他SQL差不多,只是本例可以理解BITMAP SCAN以及GIN索引的内部BITMAP SCAN在对较大数据进行合并时,可能引入的开销。

切入点依旧是explain,找耗时段,找背后的原因,解决。

1、什么时候使用GIN复合?

当任意一个条件,选择性不好时,使用复合。

什么时候使用GIN非复合?

2、当有有一个条件,选择性很好时,把它单独拿出来,作为一个独立索引。比如本例的c3模糊查询字段,过滤性好,应该单独拿出来。

其实就是说,选择性不好的列,不要放到索引里面,即使要放,也应该等PG出了分区索引后,将这种列作为分区索引的分区键。(多颗树),或者使用partial index。

《PostgreSQL 黑科技 - 空间聚集存储, 内窥GIN, GiST, SP-GiST索引》

《宝剑赠英雄 - 任意组合字段等效查询, 探探PostgreSQL多列展开式B树 (GIN)》

《PostgreSQL GIN索引实现原理》

《PostgreSQL GIN multi-key search 优化》

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

《PostgreSQL GIN索引limit慢的原因分析》

《beyond b-tree (gin\gist索引讲解PDF)》

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
1天前
|
SQL NoSQL Java
Java使用sql查询mongodb
通过使用 MongoDB Connector for BI 和 JDBC,开发者可以在 Java 中使用 SQL 语法查询 MongoDB 数据库。这种方法对于熟悉 SQL 的团队非常有帮助,能够快速实现对 MongoDB 数据的操作。同时,也需要注意到这种方法的性能和功能限制,根据具体应用场景进行选择和优化。
23 9
|
22天前
|
SQL 存储 人工智能
Vanna:开源 AI 检索生成框架,自动生成精确的 SQL 查询
Vanna 是一个开源的 Python RAG(Retrieval-Augmented Generation)框架,能够基于大型语言模型(LLMs)为数据库生成精确的 SQL 查询。Vanna 支持多种 LLMs、向量数据库和 SQL 数据库,提供高准确性查询,同时确保数据库内容安全私密,不外泄。
92 7
Vanna:开源 AI 检索生成框架,自动生成精确的 SQL 查询
|
7天前
|
SQL Oracle 数据库
使用访问指导(SQL Access Advisor)优化数据库业务负载
本文介绍了Oracle的SQL访问指导(SQL Access Advisor)的应用场景及其使用方法。访问指导通过分析给定的工作负载,提供索引、物化视图和分区等方面的优化建议,帮助DBA提升数据库性能。具体步骤包括创建访问指导任务、创建工作负载、连接工作负载至访问指导、设置任务参数、运行访问指导、查看和应用优化建议。访问指导不仅针对单条SQL语句,还能综合考虑多条SQL语句的优化效果,为DBA提供全面的决策支持。
29 11
|
1月前
|
SQL 缓存 监控
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
本文详细解析了数据库、缓存、异步处理和Web性能优化四大策略,系统性能优化必知必备,大厂面试高频。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
|
29天前
|
SQL Java
使用java在未知表字段情况下通过sql查询信息
使用java在未知表字段情况下通过sql查询信息
36 8
|
27天前
|
SQL 缓存 数据库
SQL慢查询优化策略
在数据库管理和应用开发中,SQL查询的性能优化至关重要。慢查询优化不仅可以提高应用的响应速度,还能降低服务器负载,提升用户体验。本文将详细介绍针对SQL慢查询的优化策略。
|
27天前
|
SQL 存储 BI
gbase 8a 数据库 SQL合并类优化——不同数据统计周期合并为一条SQL语句
gbase 8a 数据库 SQL合并类优化——不同数据统计周期合并为一条SQL语句
|
27天前
|
SQL 数据库
gbase 8a 数据库 SQL优化案例-关联顺序优化
gbase 8a 数据库 SQL优化案例-关联顺序优化
|
1月前
|
SQL 数据库 UED
SQL性能提升秘籍:5步优化法与10个实战案例
在数据库管理和应用开发中,SQL查询的性能优化至关重要。高效的SQL查询不仅可以提高应用的响应速度,还能降低服务器负载,提升用户体验。本文将分享SQL优化的五大步骤和十个实战案例,帮助构建高效、稳定的数据库应用。
63 3
|
1月前
|
SQL 安全 PHP
PHP开发中防止SQL注入的方法,包括使用参数化查询、对用户输入进行过滤和验证、使用安全的框架和库等,旨在帮助开发者有效应对SQL注入这一常见安全威胁,保障应用安全
本文深入探讨了PHP开发中防止SQL注入的方法,包括使用参数化查询、对用户输入进行过滤和验证、使用安全的框架和库等,旨在帮助开发者有效应对SQL注入这一常见安全威胁,保障应用安全。
59 4

相关产品

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