PostgreSQL中offset...limit分页优化的一些常见手段

 更新时间:2023年05月18日 15:45:50   作者:魂醉  
我们在使用数据库进行分页查询时,随着offset过滤的数据越来越多,查询也会越来越慢,下面这篇文章主要给大家介绍了关于PostgreSQL中offset...limit分页优化的一些常见手段,需要的朋友可以参考下
(福利推荐:【腾讯云】服务器最新限时优惠活动,云服务器1核2G仅99元/年、2核4G仅768元/3年,立即抢购>>>:9i0i.cn/qcloud

(福利推荐:你还在原价购买阿里云服务器?现在阿里云0.8折限时抢购活动来啦!4核8G企业云服务器仅2998元/3年,立即抢购>>>:9i0i.cn/aliyun

发现问题

大部分开发人员习惯使用order by offset limit进行分页,使用该方法可能会导致扫描的数据放大,因为offset的行会被扫描。表现就是一般offset的行比较小的情况也,也就是翻页,是很快的,但是一旦offset的值很大,翻页的数量很大,那么一定会变慢。

看个例子:

#建表,插入数据,并分析收取统计信息
CREATE UNLOGGED TABLE data (
   id bigint GENERATED ALWAYS AS IDENTITY,
   value double precision NOT NULL,
   created timestamp with time zone NOT NULL
);
 
SELECT setseed(0.2740184);
 
INSERT INTO data (value, created)
SELECT random() * 1000, d
FROM generate_series(
        TIMESTAMP '2022-01-01 00:00:00 UTC',
        TIMESTAMP '2022-12-31 00:00:00 UTC',
        INTERVAL '1 second'
     ) AS d(d);
 

ALTER TABLE data ADD PRIMARY KEY (id);
 
VACUUM (ANALYZE) data;

下面我们翻页获取数据看看性能如何:

#如下SQL,创建一个符合索引最合适
SELECT value, created
FROM data
WHERE value BETWEEN 0 AND 10
ORDER BY created;

#创建索引
CREATE INDEX data_created_value_idx ON data
   (created, value);


#可以看到是Index Only Scan,结果很快
postgres=# explain analyze SELECT value, created
FROM data
WHERE value BETWEEN 0 AND 10
ORDER BY created
LIMIT 50;
                                                                     QUERY PLAN                                                                     
----------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.56..126.51 rows=50 width=16) (actual time=0.022..0.195 rows=50 loops=1)
   ->  Index Only Scan using data_created_value_idx on data  (cost=0.56..801382.02 rows=318146 width=16) (actual time=0.021..0.190 rows=50 loops=1)
         Index Cond: ((value >= '0'::double precision) AND (value <= '10'::double precision))
         Heap Fetches: 0
 Planning Time: 0.084 ms
 Execution Time: 0.210 ms

#但是当我们OFFSET 200000的时候,执行计划就变了,代价比较高了,这就是我们前面所说的offset都要扫描
postgres=# explain analyze SELECT value, created
FROM data
WHERE value BETWEEN 0 AND 10
ORDER BY created
OFFSET 200000 LIMIT 50;
                                                                 QUERY PLAN                                                                 
--------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=434757.47..434763.31 rows=50 width=16) (actual time=2697.793..2704.289 rows=50 loops=1)
   ->  Gather Merge  (cost=411422.51..442355.57 rows=265122 width=16) (actual time=2627.028..2695.579 rows=200050 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         ->  Sort  (cost=410422.49..410753.89 rows=132561 width=16) (actual time=2607.976..2613.950 rows=67369 loops=3)
               Sort Key: created
               Sort Method: external merge  Disk: 2760kB
               Worker 0:  Sort Method: external merge  Disk: 2640kB
               Worker 1:  Sort Method: external merge  Disk: 2640kB
               ->  Parallel Seq Scan on data  (cost=0.00..396876.00 rows=132561 width=16) (actual time=0.042..2551.663 rows=104958 loops=3)
                     Filter: ((value >= '0'::double precision) AND (value <= '10'::double precision))
                     Rows Removed by Filter: 10378242
 Planning Time: 0.102 ms
 Execution Time: 2704.851 ms
(14 rows)

如上我们看到的,翻页越多,性能越差,唯一的好处,就是书写简单。

优化手段1:使用游标

由于普通游标只能在单个事务的上下文中工作。因此,普通游标对于分页的作用有限,因为在事务打开时进行用户交互是一个非常差的体验:长事务不仅会使表锁保持很长时间(这可能阻塞DDL或TRUNCATE语句),而且还会阻塞autovacuum的进程,从而导致表膨胀。

WITH HOLD | WITHOUT HOLD 默认值为WITHOUT HOLD, 使用WITH HOLD将CURSOR的使用范围扩大到SESSION级别,WITHOUT HOLD是TRANSACTION级别,另外,WITH HOLD将消耗更多的资源(内存或临时文件)来保持数据。

游标说明可以参考:https://www.postgresql.org/docs/13/sql-declare.html

#创建一个游标,取出满足的条件的结果集
begin;
DECLARE c SCROLL CURSOR WITH HOLD FOR
SELECT value, created
FROM data
WHERE value BETWEEN 0 AND 10
ORDER BY created;
COMMIT;

#移动游标并取出50行,和OFFSET 200000 LIMIT 50效果一样
MOVE ABSOLUTE 200000 IN c;
FETCH 50 FROM c;

#使用完游标后,必须记得关闭游标
close c;

使用游标的优点和缺点:

优点:

1.游标适用于所有分页查询,不管是第一页还是最后一页,效率一样

2.游标的结果集是稳定的

缺点:

1.当完成操作时,一定不要忘记关闭游标,否则结果集将保存在服务器上,直到数据库会话结束

2.如果游标长时间打开,数据将变的陈旧,无法获取动态的最新数据

3.游标长时间打开,相当于一个长事物,长事物的负面影响,相信大家有一定共识

优化手段2:使用位点

位点的原理很简单,就是记录上次查询出来的结果作为一个位点,查询的时候基于这个点的条件去查询。这样也就可以去掉offset了。注意要有一个pk,没有的话,需要加一个类似字段,这样位点才不会重复。

例子如下:

#通过查询记住下一页的起始位点,例子中id作为pk,标识唯一
SELECT id, value, created
FROM data
WHERE value BETWEEN 0 AND 10
ORDER BY created, id
LIMIT 50;
  id  |        value        |        created         
------+---------------------+------------------------
.........略.........
 4568 |   7.771510504657186 | 2022-01-01 01:16:07+08
 4586 |  1.2500308700502671 | 2022-01-01 01:16:25+08
 4607 |  3.3096537558421346 | 2022-01-01 01:16:46+08
 
#我们必须记住从页面的最后一行创建的id的值。然后我们可以取下一页
SELECT id, value, created
FROM data
WHERE value BETWEEN 0 AND 10
  AND (created, id) > ('2022-01-01 01:16:46+08', 4607)
ORDER BY created, id
LIMIT 50;

postgres=# explain SELECT id, value, created
FROM data
WHERE value BETWEEN 0 AND 10
AND (created, id) > ('2022-01-01 01:16:46+08', 4607)
ORDER BY created, id
LIMIT 50;
                                                                               QUERY PLAN                                                                     
          
--------------------------------------------------------------------------------------------------------------------------------------------------------------
----------
 Limit  (cost=4.32..194.08 rows=50 width=24)
   ->  Incremental Sort  (cost=4.32..1207236.72 rows=318103 width=24)
         Sort Key: created, id
         Presorted Key: created
         ->  Index Scan using data_created_value_idx on data  (cost=0.56..1192922.08 rows=318103 width=24)
               Index Cond: ((created >= '2022-01-01 01:16:46+08'::timestamp with time zone) AND (value >= '0'::double precision) AND (value <= '10'::double pr
ecision))
               Filter: (ROW(created, id) > ROW('2022-01-01 01:16:46+08'::timestamp with time zone, 4607))
(7 rows)

#加一个更适合的索引,执行计划会更好一些
CREATE INDEX data_keyset_idx ON data (created, id, value);
postgres=#   explain SELECT id, value, created
FROM data
WHERE value BETWEEN 0 AND 10
  AND (created, id) > ('2022-01-01 01:16:46+08', 4607)
ORDER BY created, id
LIMIT 50;
--------------------------------------------------------------------------------------------------------------------------------------------------------------
-----------------------
 Limit  (cost=0.56..160.58 rows=50 width=24)
   ->  Index Only Scan using data_keyset_idx on data  (cost=0.56..1018064.43 rows=318103 width=24)
         Index Cond: ((ROW(created, id) > ROW('2022-01-01 01:16:46+08'::timestamp with time zone, 4607)) AND (value >= '0'::double precision) AND (value <= '1
0'::double precision))
(3 rows)

位点优化的有点和缺点:

优点:

1.每次查询只获取我们需要的数据,不需要扫描不额外的数据,减少了相关资源代价

2.每个查询将展示最新并发数据修改的当前数据

缺点:

1.需要一个专门为查询而设计的特殊索引

2.只有事先能获取到确切的位点,查询时才有用

参考:
https://www.cybertec-postgresql.com/en/pagination-problem-total-result-count/#total-count

总结

到此这篇关于PostgreSQL中offset...limit分页优化的一些常见手段的文章就介绍到这了,更多相关PostgreSQL offset...limit分页优化内容请搜索程序员之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持程序员之家!

相关文章

  • PostgreSQL 自动Vacuum配置方式

    PostgreSQL 自动Vacuum配置方式

    这篇文章主要介绍了PostgreSQL 自动Vacuum配置方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • postgreSql分组统计数据的实现代码

    postgreSql分组统计数据的实现代码

    这篇文章给大家介绍postgreSql的监控记录表里多条不同时间的数据,只取最新的数据,并分组统计,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2020-12-12
  • postgresql的now()与Oracle的sysdate区别说明

    postgresql的now()与Oracle的sysdate区别说明

    这篇文章主要介绍了postgresql的now()与Oracle的sysdate区别说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • PostgreSQL实时查看数据库实例正在执行的SQL语句实例详解

    PostgreSQL实时查看数据库实例正在执行的SQL语句实例详解

    在任何数据库中,分析和优化SQL的执行,最重要的工作就是执行计划的解读,而说到执行计划得先了解postgresql的查询执行过程,下面这篇文章主要给大家介绍了关于PostgreSQL实时查看数据库实例正在执行的SQL语句的相关资料,需要的朋友可以参考下
    2023-01-01
  • postgresql coalesce函数数据转换方式

    postgresql coalesce函数数据转换方式

    这篇文章主要介绍了postgresql coalesce函数数据转换方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • postgresql 中position函数的性能详解

    postgresql 中position函数的性能详解

    这篇文章主要介绍了postgresql 中position函数的性能详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • PostgreSQL教程(四):数据类型详解

    PostgreSQL教程(四):数据类型详解

    这篇文章主要介绍了PostgreSQL教程(四):数据类型详解,本文讲解了数值类型、字符类型、布尔类型、位串类型、数组、复合类型等数据类型,需要的朋友可以参考下
    2015-05-05
  • 详解PostgreSQL?14.4安装使用及一些安装的异常问题

    详解PostgreSQL?14.4安装使用及一些安装的异常问题

    这篇文章主要介绍了PostgreSQL?14.4的安装以及使用以及一些安装的异常,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-07-07
  • 基于PostgreSQL的时序数据库TimescaleDB的基本用法和概念

    基于PostgreSQL的时序数据库TimescaleDB的基本用法和概念

    时序数据是指按照时间顺序存储的数据,TimescaleDB是一个开源的、扩展了PostgreSQL的时序数据库扩展,本文就给大家详细的介绍一下基于PostgreSQL的时序数据库TimescaleDB的基本用法和概念,需要的朋友可以参考下
    2023-06-06
  • 关于postgresql?timestamp时间戳问题

    关于postgresql?timestamp时间戳问题

    这篇文章主要介绍了关于postgresql?timestamp时间戳问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03

最新评论

?


http://www.vxiaotou.com