PostgreSQL码农集散地

PostgreSQL FETCH WITH TIES 代替 LIMIT OFFSET 解决分页性能优化GAP问题

PostgreSQL fetch with ties 代替 limit offset 解决分页性能优化gap问题

作者

digoal

日期

2023-11-11

标签

PostgreSQL , PolarDB , 分页 , limit offset , fetch with ties。


背景

fetch with ties 代替 limit offset 解决分页性能优化gap问题

https://www.postgresql.org/docs/current/sql-select.html

《PostgreSQL 13 offset fetch first with ties - 返回ordered peer行S》

当使用limit offset翻页翻到很后面时, 性能会非常差, 原因是offset需要扫描大量的记录并进行过滤. 所以出现了很多优化分页的方法. 其中增加位置偏移条件最为常用, 但是掌握不好容易出现gap.

使用位置偏移条件优化分页的例子

create table t_off (id serial primary key, info text, c int, ts timestamp);  
create index on t_off (c, ts);

insert into t_off (info,c,ts) select md5(random()::text), random()*100, now() from generate_series(1,1000);
insert into t_off (info,c,ts) select md5(random()::text), random()*100, now() from generate_series(1,1000);
insert into t_off (info,c,ts) select md5(random()::text), random()*100, now() from generate_series(1,1000);
insert into t_off (info,c,ts) select md5(random()::text), random()*100, now() from generate_series(1,1000);

limit offset

老标准: limit , 可能出现gap

db1=> explain select * from t_off where c=1 order by ts limit 10;  
QUERY PLAN
-------------------------------------------------------------------------------------
Limit (cost=0.28..7.64 rows=10 width=49)
-> Index Scan using t_off_c_ts_idx on t_off (cost=0.28..36.34 rows=49 width=49)
Index Cond: (c = 1)
(3 rows)

用于优化的ts条件, 查询完上次的记录得到的最大ts:

db1=> explain select * from t_off where c=1 and ts>'2023-11-11 00:31:57.304588' order by ts limit 10;  
QUERY PLAN
----------------------------------------------------------------------------------------------------
Limit (cost=0.28..8.51 rows=10 width=49)
-> Index Scan using t_off_c_ts_idx on t_off (cost=0.28..30.72 rows=37 width=49)
Index Cond: ((c = 1) AND (ts > '2023-11-11 00:31:57.304588'::timestamp without time zone))
(3 rows)

问题: 用于优化的ts条件有重复值, 会出现gap

db1=> select * from t_off where c=1 order by ts limit 5;  
id | info | c | ts
-----+----------------------------------+---+----------------------------
16 | ce70259b8c0b5e83dca5f20cef93d1ee | 1 | 2023-11-11 00:31:57.304588
79 | 3a08bcf762a062e86318f516fddea793 | 1 | 2023-11-11 00:31:57.304588
230 | 01bc9f9c59b70311369aaa33c3cca3a7 | 1 | 2023-11-11 00:31:57.304588
257 | bb22ec8f00de73c19017479fdf981116 | 1 | 2023-11-11 00:31:57.304588
286 | 70bfb092ab95cc8c8f5aef9e2762af0c | 1 | 2023-11-11 00:31:57.304588
(5 rows)

db1=> select * from t_off where c=1 order by ts limit 10;
id | info | c | ts
-----+----------------------------------+---+----------------------------
16 | ce70259b8c0b5e83dca5f20cef93d1ee | 1 | 2023-11-11 00:31:57.304588
79 | 3a08bcf762a062e86318f516fddea793 | 1 | 2023-11-11 00:31:57.304588
230 | 01bc9f9c59b70311369aaa33c3cca3a7 | 1 | 2023-11-11 00:31:57.304588
257 | bb22ec8f00de73c19017479fdf981116 | 1 | 2023-11-11 00:31:57.304588
286 | 70bfb092ab95cc8c8f5aef9e2762af0c | 1 | 2023-11-11 00:31:57.304588
311 | c3b8f1eab66fe88cfd59e92d039fbaa6 | 1 | 2023-11-11 00:31:57.304588
452 | f1c000b8a200207c52b6c3f9a8babe72 | 1 | 2023-11-11 00:31:57.304588
470 | 7b4702f5a4ff75ef237cbf7af5b8d082 | 1 | 2023-11-11 00:31:57.304588
642 | 82d641bcf235eba92a9368f0ac553dca | 1 | 2023-11-11 00:31:57.304588
749 | 669488263cfcec928ac2693995309eea | 1 | 2023-11-11 00:31:57.304588
(10 rows)

db1=> select * from t_off where c=1 and ts>'2023-11-11 00:31:57.304588' order by ts limit 5;
id | info | c | ts
------+----------------------------------+---+----------------------------
1017 | e12d1088e80136e869cf82816b28ab51 | 1 | 2023-11-11 00:31:57.313518
1058 | 39dc148d9223bcaddcdb2096750e513b | 1 | 2023-11-11 00:31:57.313518
1070 | 31c83c561119140721f94e8b28669914 | 1 | 2023-11-11 00:31:57.313518
1102 | ee8f076e2e29299ace4c5fb71ddf9dda | 1 | 2023-11-11 00:31:57.313518
1148 | 37232358c5995513c19545b8232aced1 | 1 | 2023-11-11 00:31:57.313518
(5 rows)

fetch with ties

新标准: limit 改成 fetch , 返回可以根据需要超过Limit数, 把最后一条相同的ts都返回. 避免了翻页优化带来的gap. 老标准(limit)比较麻烦, 需要引入pk或uk来解决.

限制10条, 但是实际上有14条ts一样的都返回了.

db1=> select * from t_off where c=1 order by ts fetch first 10 row with ties;    
id | info | c | ts
-----+----------------------------------+---+----------------------------
16 | ce70259b8c0b5e83dca5f20cef93d1ee | 1 | 2023-11-11 00:31:57.304588
79 | 3a08bcf762a062e86318f516fddea793 | 1 | 2023-11-11 00:31:57.304588
230 | 01bc9f9c59b70311369aaa33c3cca3a7 | 1 | 2023-11-11 00:31:57.304588
257 | bb22ec8f00de73c19017479fdf981116 | 1 | 2023-11-11 00:31:57.304588
286 | 70bfb092ab95cc8c8f5aef9e2762af0c | 1 | 2023-11-11 00:31:57.304588
311 | c3b8f1eab66fe88cfd59e92d039fbaa6 | 1 | 2023-11-11 00:31:57.304588
452 | f1c000b8a200207c52b6c3f9a8babe72 | 1 | 2023-11-11 00:31:57.304588
470 | 7b4702f5a4ff75ef237cbf7af5b8d082 | 1 | 2023-11-11 00:31:57.304588
642 | 82d641bcf235eba92a9368f0ac553dca | 1 | 2023-11-11 00:31:57.304588
749 | 669488263cfcec928ac2693995309eea | 1 | 2023-11-11 00:31:57.304588
816 | 9a92daa3c8ceb90e023d127485c4a053 | 1 | 2023-11-11 00:31:57.304588
827 | 82abd37368d66eb03347429ff4b28e9e | 1 | 2023-11-11 00:31:57.304588
846 | 48f9737b18bf3e8dae7531a1b7fa4d45 | 1 | 2023-11-11 00:31:57.304588
990 | f7885a8e585ae7fa0c4b73dd356f260b | 1 | 2023-11-11 00:31:57.304588
(14 rows)

此时gap就没有了.

db1=> select * from t_off where c=1 and ts>'2023-11-11 00:31:57.304588' order by ts fetch first 10 row with ties;    
id | info | c | ts
------+----------------------------------+---+----------------------------
1017 | e12d1088e80136e869cf82816b28ab51 | 1 | 2023-11-11 00:31:57.313518
1058 | 39dc148d9223bcaddcdb2096750e513b | 1 | 2023-11-11 00:31:57.313518
1070 | 31c83c561119140721f94e8b28669914 | 1 | 2023-11-11 00:31:57.313518
1102 | ee8f076e2e29299ace4c5fb71ddf9dda | 1 | 2023-11-11 00:31:57.313518
1148 | 37232358c5995513c19545b8232aced1 | 1 | 2023-11-11 00:31:57.313518
1425 | df4ff8d17227012f240b5ecee69d5758 | 1 | 2023-11-11 00:31:57.313518
1654 | c0bb3ebf9eda3a3a0228dd7784eccbfe | 1 | 2023-11-11 00:31:57.313518
1704 | 70283bbc87968f25aed18524030e1a21 | 1 | 2023-11-11 00:31:57.313518
1737 | 7c1b782104144f3cbc97e247e5572165 | 1 | 2023-11-11 00:31:57.313518
1779 | 8af082c81acc99fa2f8afdffd58f4d11 | 1 | 2023-11-11 00:31:57.313518
1861 | 0c9ecc39655bf834d1cdc07cf09ed362 | 1 | 2023-11-11 00:31:57.313518
(11 rows)

新标准非常适合翻页优化

db1=> explain select * from t_off where c=1 order by ts fetch first 10 row with ties;    
QUERY PLAN
-------------------------------------------------------------------------------------
Limit (cost=0.28..7.64 rows=10 width=49)
-> Index Scan using t_off_c_ts_idx on t_off (cost=0.28..36.34 rows=49 width=49)
Index Cond: (c = 1)
(3 rows)

db1=> explain select * from t_off where c=1 and ts>'2023-11-11 00:31:57.304588' order by ts fetch first 10 row with ties;
QUERY PLAN
----------------------------------------------------------------------------------------------------
Limit (cost=0.28..8.51 rows=10 width=49)
-> Index Scan using t_off_c_ts_idx on t_off (cost=0.28..30.72 rows=37 width=49)
Index Cond: ((c = 1) AND (ts > '2023-11-11 00:31:57.304588'::timestamp without time zone))
(3 rows)