PostgreSQL Oracle 兼容性之 - INDEX SKIP SCAN (递归查询变态优化) 非驱动列索引扫描优化

本文涉及的产品
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
RDS AI 助手,专业版
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
简介:

标签

PostgreSQL , Oracle , index skip scan , 非驱动列条件 , 递归查询 , 子树


背景

对于输入条件在复合索引中为非驱动列的,如何高效的利用索引扫描?

在Oracle中可以使用index skip scan来实现这类CASE的高效扫描:

INDEX跳跃扫描一般用在WHERE条件里面没有使用到引导列,但是用到了引导列以外的其他列,并且引导列的DISTINCT值较少的情况。

在这种情况下,数据库把这个复合索引逻辑上拆散为多个子索引,依次搜索子索引中非引导列的WHERE条件里面的值。

使用方法如下:

/*+ INDEX_SS ( [ @ qb_name ] tablespec [ indexspec [ indexspec ]... ] ) */  

The INDEX_SS hint instructs the optimizer to perform an index skip scan for the specified table. If the statement uses an index range scan, then Oracle scans the index entries in ascending order of their indexed values. In a partitioned index, the results are in ascending order within each partition.Each parameter serves the same purpose as in "INDEX Hint". For example:

SELECT /*+ INDEX_SS(e emp_name_ix) */ last_name FROM employees e WHERE first_name = 'Steven';  

下面是来自ORACLE PERFORMANCE TUNING里的原文:

Index skip scans improve index scans by nonprefix columns. Often, scanning index blocks is faster than scanning table data blocks.

Skip scanning lets a composite index be split logically into smaller subindexes. In skip scanning, the initial column of the composite index is not specified in the query. In other words, it is skipped.

The number of logical subindexes is determined by the number of distinct values in the initial column. Skip scanning is advantageous if there are few distinct values in the leading column of the composite index and many distinct values in the nonleading key of the index.

Example 13-5 Index Skip Scan

Consider, for example, a table

employees(  
sex,   
employee_id,  
address  
)   

with a composite index on

(sex, employee_id).   

Splitting this composite index would result in two logical subindexes, one for M and one for F.

For this example, suppose you have the following index data:

('F',98)('F',100)('F',102)('F',104)('M',101)('M',103)('M',105)  

The index is split logically into the following two subindexes:

The first subindex has the keys with the value F.

The second subindex has the keys with the value M

pic

The column sex is skipped in the following query:

SELECT * FROM employeesWHERE employee_id = 101;  

A complete scan of the index is not performed, but the subindex with the value F is searched first, followed by a search of the subindex with the value M.

PostgreSQL 非skip scan

PostgreSQL支持非驱动列的索引扫描,但是需要扫描整个索引。

例子

1、创建测试表

postgres=# create table t(id int, c1 int);  
CREATE TABLE  

2、写入1000万测试数据

postgres=# insert into t select random()*1 , id from generate_series(1,10000000) id;  
INSERT 0 10000000  

3、创建多列索引

postgres=# create index idx_t on t(id,c1);  
CREATE INDEX  

4、非驱动列查询测试如下

index only scan

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from t where c1=1;  
                                                                QUERY PLAN                                                                   
-------------------------------------------------------------------------------------------------------------------------------------------  
 Index Only Scan using idx_t on public.t  (cost=10000000000.43..10000105164.89 rows=1 width=8) (actual time=0.043..152.288 rows=1 loops=1)  
   Output: id, c1  
   Index Cond: (t.c1 = 1)  
   Heap Fetches: 0  
   Buffers: shared hit=27326  
 Execution time: 152.328 ms  
(6 rows)  

index scan

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from t where c1=1;  
                                                      QUERY PLAN                                                         
-----------------------------------------------------------------------------------------------------------------------  
 Index Scan using idx_t on public.t  (cost=0.43..105165.99 rows=1 width=8) (actual time=0.022..151.845 rows=1 loops=1)  
   Output: id, c1  
   Index Cond: (t.c1 = 1)  
   Buffers: shared hit=27326  
 Execution time: 151.881 ms  
(5 rows)  

bitmap scan

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from t where c1=1;  
                                                       QUERY PLAN                                                         
------------------------------------------------------------------------------------------------------------------------  
 Bitmap Heap Scan on public.t  (cost=105164.88..105166.00 rows=1 width=8) (actual time=151.731..151.732 rows=1 loops=1)  
   Output: id, c1  
   Recheck Cond: (t.c1 = 1)  
   Heap Blocks: exact=1  
   Buffers: shared hit=27326  
   ->  Bitmap Index Scan on idx_t  (cost=0.00..105164.88 rows=1 width=0) (actual time=151.721..151.721 rows=1 loops=1)  
         Index Cond: (t.c1 = 1)  
         Buffers: shared hit=27325  
 Execution time: 151.777 ms  
(9 rows)  

seq scan(全表扫描)

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from t where c1=1;  
                                               QUERY PLAN                                                  
---------------------------------------------------------------------------------------------------------  
 Seq Scan on public.t  (cost=0.00..169248.41 rows=1 width=8) (actual time=0.014..594.535 rows=1 loops=1)  
   Output: id, c1  
   Filter: (t.c1 = 1)  
   Rows Removed by Filter: 9999999  
   Buffers: shared hit=44248  
 Execution time: 594.568 ms  
(6 rows)  

使用索引扫,因为不需要FILTER,同时扫描的BLOCK更少,所以性能比全表扫略好。但是还是扫了整个索引的PAGE,所以并不能算skip scan。

那么如何让PostgreSQL支持index skip scan呢?

PostgreSQL skip scan

实际上原理和Oracle类似,可以输入驱动列条件,然后按多个条件扫描,这样就能达到SKIP SCAN的效果。(即多颗子树扫描)。

同样也更加适合于驱动列DISTINCT值较少的情况。

用PostgreSQL的递归查询语法可以实现这样的加速效果。这种方法也被用于获取count(distinct), distinct值等。

《distinct xx和count(distinct xx)的变态递归优化方法 - 索引收敛(skip scan)扫描》

例如,我们通过这个方法,可以快速的得到驱动列的唯一值

with recursive skip as (    
  (    
    select min(t.id) as id from t where t.id is not null    
  )    
  union all    
  (    
    select (select min(t.id) as id from t where t.id > s.id and t.id is not null)     
      from skip s where s.id is not null    
  )  -- 这里的where s.id is not null 一定要加,否则就死循环了.    
)     
select id from skip ;  

然后封装到如下SQL,实现skip scan的效果

explain (analyze,verbose,timing,costs,buffers) select * from t where id in  
(  
with recursive skip as (    
  (    
    select min(t.id) as id from t where t.id is not null    
  )    
  union all    
  (    
    select (select min(t.id) as id from t where t.id > s.id and t.id is not null)     
      from skip s where s.id is not null    
  )  -- 这里的where s.id is not null 一定要加,否则就死循环了.    
)     
select id from skip   
) and c1=1  
union all   
select * from t where id is null and c1=1;  

或者

explain (analyze,verbose,timing,costs,buffers) select * from t where id = any(array  
(  
with recursive skip as (    
  (    
    select min(t.id) as id from t where t.id is not null    
  )    
  union all    
  (    
    select (select min(t.id) as id from t where t.id > s.id and t.id is not null)     
      from skip s where s.id is not null    
  )  -- 这里的where s.id is not null 一定要加,否则就死循环了.    
)     
select id from skip   
)) and c1=1  
union all   
select * from t where id is null and c1=1;  

看执行计划:

效果好多了

  
                                                                                       QUERY PLAN                                                                                          
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------  
 Append  (cost=55.00..215.22 rows=2 width=8) (actual time=0.127..0.138 rows=1 loops=1)  
   Buffers: shared hit=21  
   ->  Nested Loop  (cost=55.00..213.64 rows=1 width=8) (actual time=0.126..0.127 rows=1 loops=1)  
         Output: t.id, t.c1  
         Buffers: shared hit=18  
         ->  HashAggregate  (cost=54.57..55.58 rows=101 width=4) (actual time=0.108..0.109 rows=3 loops=1)  
               Output: skip.id  
               Group Key: skip.id  
               Buffers: shared hit=11  
               ->  CTE Scan on skip  (cost=51.29..53.31 rows=101 width=4) (actual time=0.052..0.102 rows=3 loops=1)  
                     Output: skip.id  
                     Buffers: shared hit=11  
                     CTE skip  
                       ->  Recursive Union  (cost=0.46..51.29 rows=101 width=4) (actual time=0.050..0.099 rows=3 loops=1)  
                             Buffers: shared hit=11  
                             ->  Result  (cost=0.46..0.47 rows=1 width=4) (actual time=0.049..0.049 rows=1 loops=1)  
                                   Output: $1  
                                   Buffers: shared hit=4  
                                   InitPlan 3 (returns $1)  
                                     ->  Limit  (cost=0.43..0.46 rows=1 width=4) (actual time=0.045..0.046 rows=1 loops=1)  
                                           Output: t_3.id  
                                           Buffers: shared hit=4  
                                           ->  Index Only Scan using idx_t on public.t t_3  (cost=0.43..205165.21 rows=10000033 width=4) (actual time=0.045..0.045 rows=1 loops=1)  
                                                 Output: t_3.id  
                                                 Index Cond: (t_3.id IS NOT NULL)  
                                                 Heap Fetches: 0  
                                                 Buffers: shared hit=4  
                             ->  WorkTable Scan on skip s  (cost=0.00..4.88 rows=10 width=4) (actual time=0.015..0.015 rows=1 loops=3)  
                                   Output: (SubPlan 2)  
                                   Filter: (s.id IS NOT NULL)  
                                   Rows Removed by Filter: 0  
                                   Buffers: shared hit=7  
                                   SubPlan 2  
                                     ->  Result  (cost=0.46..0.47 rows=1 width=4) (actual time=0.018..0.019 rows=1 loops=2)  
                                           Output: $3  
                                           Buffers: shared hit=7  
                                           InitPlan 1 (returns $3)  
                                             ->  Limit  (cost=0.43..0.46 rows=1 width=4) (actual time=0.018..0.018 rows=0 loops=2)  
                                                   Output: t_2.id  
                                                   Buffers: shared hit=7  
                                                   ->  Index Only Scan using idx_t on public.t t_2  (cost=0.43..76722.42 rows=3333344 width=4) (actual time=0.017..0.017 rows=0 loops=2)  
                                                         Output: t_2.id  
                                                         Index Cond: ((t_2.id > s.id) AND (t_2.id IS NOT NULL))  
                                                         Heap Fetches: 0  
                                                         Buffers: shared hit=7  
         ->  Index Only Scan using idx_t on public.t  (cost=0.43..1.56 rows=1 width=8) (actual time=0.005..0.005 rows=0 loops=3)  
               Output: t.id, t.c1  
               Index Cond: ((t.id = skip.id) AND (t.c1 = 1))  
               Heap Fetches: 0  
               Buffers: shared hit=7  
   ->  Index Only Scan using idx_t on public.t t_1  (cost=0.43..1.56 rows=1 width=8) (actual time=0.010..0.010 rows=0 loops=1)  
         Output: t_1.id, t_1.c1  
         Index Cond: ((t_1.id IS NULL) AND (t_1.c1 = 1))  
         Heap Fetches: 0  
         Buffers: shared hit=3  
 Execution time: 0.256 ms  
(56 rows)  

从150多毫秒,降低到了0.256毫秒

内核层面优化

与Oracle做法类似,或者说与递归的做法类似。

使用这种方法来改进优化器,可以达到index skip scan的效果,而且不用改写SQL。

参考

《distinct xx和count(distinct xx)的变态递归优化方法 - 索引收敛(skip scan)扫描》

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍如何基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
10月前
|
存储 监控 关系型数据库
B-tree不是万能药:PostgreSQL索引失效的7种高频场景与破解方案
在PostgreSQL优化实践中,B-tree索引虽承担了80%以上的查询加速任务,但因多种原因可能导致索引失效,引发性能骤降。本文深入剖析7种高频失效场景,包括隐式类型转换、函数包裹列、前导通配符等,并通过实战案例揭示问题本质,提供生产验证的解决方案。同时,总结索引使用决策矩阵与关键原则,助你让索引真正发挥作用。
587 0
|
Oracle Java 关系型数据库
【YashanDB知识库】如何配置jdbc驱动使getDatabaseProductName()返回Oracle
【YashanDB知识库】如何配置jdbc驱动使getDatabaseProductName()返回Oracle
|
监控 关系型数据库 数据库
PostgreSQL的索引优化策略?
【8月更文挑战第26天】PostgreSQL的索引优化策略?
594 1
|
10月前
|
固态存储 关系型数据库 数据库
从Explain到执行:手把手优化PostgreSQL慢查询的5个关键步骤
本文深入探讨PostgreSQL查询优化的系统性方法,结合15年数据库优化经验,通过真实生产案例剖析慢查询问题。内容涵盖五大关键步骤:解读EXPLAIN计划、识别性能瓶颈、索引优化策略、查询重写与结构调整以及系统级优化配置。文章详细分析了慢查询对资源、硬件成本及业务的影响,并提供从诊断到根治的全流程解决方案。同时,介绍了索引类型选择、分区表设计、物化视图应用等高级技巧,帮助读者构建持续优化机制,显著提升数据库性能。最终总结出优化大师的思维框架,强调数据驱动决策与预防性优化文化,助力优雅设计取代复杂补救,实现数据库性能质的飞跃。
1539 0
|
SQL 关系型数据库 OLAP
云原生数据仓库AnalyticDB PostgreSQL同一个SQL可以实现向量索引、全文索引GIN、普通索引BTREE混合查询,简化业务实现逻辑、提升查询性能
本文档介绍了如何在AnalyticDB for PostgreSQL中创建表、向量索引及混合检索的实现步骤。主要内容包括:创建`articles`表并设置向量存储格式,创建ANN向量索引,为表增加`username`和`time`列,建立BTREE索引和GIN全文检索索引,并展示了查询结果。参考文档提供了详细的SQL语句和配置说明。
515 2
|
JSON 关系型数据库 PostgreSQL
PostgreSQL 9种索引的原理和应用场景
PostgreSQL 支持九种主要索引类型,包括 B-Tree、Hash、GiST、SP-GiST、GIN、BRIN、Bitmap、Partial 和 Unique 索引。每种索引适用于不同场景,如 B-Tree 适合范围查询和排序,Hash 仅用于等值查询,GiST 支持全文搜索和几何数据查询,GIN 适用于多值列和 JSON 数据,BRIN 适合非常大的表,Bitmap 适用于低基数列,Partial 只对部分数据创建索引,Unique 确保列值唯一。
1309 15
|
缓存 关系型数据库 数据库
如何优化 PostgreSQL 数据库性能?
如何优化 PostgreSQL 数据库性能?
766 2
|
Oracle 关系型数据库 Linux
讲解linux下的Qt如何编译oracle的驱动库libqsqloci.so
通过这一连串的步骤,可以专业且有效地在Linux下为Qt编译Oracle驱动库 `libqsqloci.so`,使得Qt应用能够通过OCI与Oracle数据库进行交互。这些步骤适用于具备一定Linux和Qt经验的开发者,并且能够为需要使用Qt开发数据库应用的专业人士提供指导。
710 1
讲解linux下的Qt如何编译oracle的驱动库libqsqloci.so
|
监控 关系型数据库 数据库
如何优化PostgreSQL的性能?
【8月更文挑战第4天】如何优化PostgreSQL的性能?
880 7
|
SQL 关系型数据库 MySQL
SQL Server、MySQL、PostgreSQL:主流数据库SQL语法异同比较——深入探讨数据类型、分页查询、表创建与数据插入、函数和索引等关键语法差异,为跨数据库开发提供实用指导
【8月更文挑战第31天】SQL Server、MySQL和PostgreSQL是当今最流行的关系型数据库管理系统,均使用SQL作为查询语言,但在语法和功能实现上存在差异。本文将比较它们在数据类型、分页查询、创建和插入数据以及函数和索引等方面的异同,帮助开发者更好地理解和使用这些数据库。尽管它们共用SQL语言,但每个系统都有独特的语法规则,了解这些差异有助于提升开发效率和项目成功率。
2027 0

相关产品

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

    更多