postgresql-分页重复数据探索
# postgresql-分页重复数据探索
## 问题背景
许多开发和测试人员都可能遇到过列表的数据翻下一页的时候显示了上一页的数据,也就是翻页会有重复的数据。
### 如何处理?
这个问题出现的原因是因为选择的排序字段有重复,常见的处理办法就是排序的时候加上唯一字段,这样在分页的过程中数据就不会重复了。
关于这个问题文档也有解释并非是一个bug。而是排序时需要选择唯一字段来做排序,不然返回的结果不确定
### 排序返回数据重复的根本原因是什么呢?
经常优化sql的同学可能会发现,执行计划里面会有Sort Method这个关键字,而这个关键字就是排序选择的方法。abase的排序分为三种
```sql
quicksort 快速排序
top-N heapsort Memory 堆排序
external merge Disk 归并排序
```
### 推测
分页重复的问题和执行计划选择排序算法的稳定性有关。
### 简单介绍下这三种排序算法的场景:
在有索引的情况下:排序可以直接走索引。
在没有索引的情况下:当表的数据量较小的时候选择快速排序(排序所需必须内存小于work_mem),
当排序有limit,且耗费的内存不超过work_mem时选择堆排序,
当work_mem不够时选择归并排序。
### 验证推测
1.创建表,初始化数据
```sql
abase=# create table t_sort(n_int int,c_id varchar(300));
CREATE TABLE
abase=# insert into t_sort(n_int,c_id) select 100,generate_series(1,9);
INSERT 0 9
abase=# insert into t_sort(n_int,c_id) select 200,generate_series(1,9);
INSERT 0 9
abase=# insert into t_sort(n_int,c_id) select 300,generate_series(1,9);
INSERT 0 9
abase=# insert into t_sort(n_int,c_id) select 400,generate_series(1,9);
INSERT 0 9
abase=# insert into t_sort(n_int,c_id) select 500,generate_series(1,9);
INSERT 0 9
abase=# insert into t_sort(n_int,c_id) select 600,generate_series(1,9);
INSERT 0 9
```
### 三种排序
```sql
--快速排序 quicksort
abase=# explain analyze select ctid,n_int,c_id from t_sort order by n_int asc;
QUERY PLAN
------------------------------------------------------------
Sort (cost=3.09..3.23 rows=54 width=12) (actual time=0.058..0.061 rows=54 loops=1)
Sort Key: n_int
Sort Method: quicksort Memory: 27kB
-> Seq Scan on t_sort (cost=0.00..1.54 rows=54 width=12) (actual time=0.021..0.032 rows=54 loops=1)
Planning time: 0.161 ms
Execution time: 0.104 ms
(6 rows)
--堆排序 top-N heapsort
abase=# explain analyze select ctid,n_int,c_id from t_sort order by n_int asc limit 10;
QUERY PLAN
------------------------------------------------------------
Limit (cost=2.71..2.73 rows=10 width=12) (actual time=0.066..0.068 rows=10 loops=1)
-> Sort (cost=2.71..2.84 rows=54 width=12) (actual time=0.065..0.066 rows=10 loops=1)
Sort Key: n_int
Sort Method: top-N heapsort Memory: 25kB
-> Seq Scan on t_sort (cost=0.00..1.54 rows=54 width=12) (actual time=0.022..0.031 rows=54 loops=1
)
Planning time: 0.215 ms
Execution time: 0.124 ms
(7 rows)
--归并排序 external sort Disk
--插入大量值为a的数据
abase=# insert into t_sort(n_int,c_id) select generate_series(1000,2000),'a';
INSERT 0 1001
abase=# set work_mem = '64kB';
SET
abase=# explain analyze select ctid,n_int,c_id from t_sort order by n_int asc;
QUERY PLAN
-------------------------------------------------------------
Sort (cost=18.60..19.28 rows=270 width=12) (actual time=1.235..1.386 rows=1055 loops=1)
Sort Key: n_int
Sort Method: external sort Disk: 32kB
-> Seq Scan on t_sort (cost=0.00..7.70 rows=270 width=12) (actual time=0.030..0.247 rows=1055 loops=1)
Planning time: 0.198 ms
Execution time: 1.663 ms
(6 rows)
```
### 快速排序
```sql
--快速排序
abase=# explain analyze select ctid,n_int,c_id from t_sort order by n_int asc;
QUERY PLAN
------------------------------------------------------------
Sort (cost=3.09..3.23 rows=54 width=12) (actual time=0.058..0.061 rows=54 loops=1)
Sort Key: n_int
Sort Method: quicksort Memory: 27kB
-> Seq Scan on t_sort (cost=0.00..1.54 rows=54 width=12) (actual time=0.021..0.032 rows=54 loops=1)
Planning time: 0.161 ms
Execution time: 0.104 ms
(6 rows)
--获取前20条数据
abase=# select ctid,n_int,c_id from t_sort order by n_int asc limit 20;
ctid | n_int | c_id
--------+-------+------
(0,7) | 100 | 7
(0,2) | 100 | 2
(0,4) | 100 | 4
(0,8) | 100 | 8
(0,3) | 100 | 3
(0,6) | 100 | 6
(0,5) | 100 | 5
(0,9) | 100 | 9
(0,1) | 100 | 1
(0,14) | 200 | 5
(0,13) | 200 | 4
(0,12) | 200 | 3
(0,10) | 200 | 1
(0,15) | 200 | 6
(0,16) | 200 | 7
(0,17) | 200 | 8
(0,11) | 200 | 2
(0,18) | 200 | 9
(0,20) | 300 | 2
(0,19) | 300 | 1
(20 rows)
--分页获取前10条数据
abase=# select ctid,n_int,c_id from t_sort order by n_int asc limit 10 offset 0;
ctid | n_int | c_id
--------+-------+------
(0,1) | 100 | 1
(0,3) | 100 | 3
(0,4) | 100 | 4
(0,2) | 100 | 2
(0,6) | 100 | 6
(0,7) | 100 | 7
(0,8) | 100 | 8
(0,9) | 100 | 9
(0,5) | 100 | 5
(0,10) | 200 | 1
(10 rows)
--分页从第10条开始获取10条
abase=# select ctid,n_int,c_id from t_sort order by n_int asc limit 10 offset 10;
ctid | n_int | c_id
--------+-------+------
(0,13) | 200 | 4
(0,12) | 200 | 3
(0,10) | 200 | 1
(0,15) | 200 | 6
(0,16) | 200 | 7
(0,17) | 200 | 8
(0,11) | 200 | 2
(0,18) | 200 | 9
(0,20) | 300 | 2
(0,19) | 300 | 1
(10 rows)
```
limit 10 offset 0,limit 10 offset 10,连续取两页数据
此处可以看到limit 10 offset 10结果中,第三条数据重复了第一页的最后一条: (0,10) | 200 | 1
并且n_int = 200 and c_id = 5这条数据被“遗漏”了。
### 堆排序
```sql
abase=# select count(*) from t_sort;
count
-------
1055
(1 row)
--设置work_mem 4MB
abase=# show work_mem ;
work_mem
----------
4MB
(1 row)
--top-N heapsort
abase=# explain analyze select * from ( select ctid,n_int,c_id from test order by n_int asc limit 1001 offset 0) td limit 10;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------
Limit (cost=2061.33..2061.45 rows=10 width=13) (actual time=15.247..15.251 rows=10 loops=1)
-> Limit (cost=2061.33..2063.83 rows=1001 width=13) (actual time=15.245..15.247 rows=10 loops=1)
-> Sort (cost=2061.33..2135.72 rows=29757 width=13) (actual time=15.244..15.245 rows=10 loops=1)
Sort Key: test.n_int
Sort Method: top-N heapsort Memory: 95kB
-> Seq Scan on test (cost=0.00..429.57 rows=29757 width=13) (actual time=0.042..7.627 rows=2
9757 loops=1)
Planning time: 0.376 ms
Execution time: 15.415 ms
(8 rows)
--获取limit 1001 offset 0,然后取10前10条数据
abase=# select * from ( select ctid,n_int,c_id from test order by n_int asc limit 1001 offset 0) td limit 10;
ctid | n_int | c_id
----------+-------+------
(0,6) | 100 | 6
(0,2) | 100 | 2
(0,5) | 100 | 5
(87,195) | 100 | 888
(0,3) | 100 | 3
(0,1) | 100 | 1
(0,8) | 100 | 8
(0,55) | 100 | 888
(44,12) | 100 | 888
(0,9) | 100 | 9
(10 rows)
---获取limit 1001 offset 1,然后取10前10条数据
abase=# select * from ( select ctid,n_int,c_id from test order by n_int asc limit 1001 offset 1) td limit 10;
ctid | n_int | c_id
----------+-------+------
(44,12) | 100 | 888
(0,8) | 100 | 8
(0,1) | 100 | 1
(0,5) | 100 | 5
(0,9) | 100 | 9
(87,195) | 100 | 888
(0,7) | 100 | 7
(0,6) | 100 | 6
(0,3) | 100 | 3
(0,4) | 100 | 4
(10 rows)
---获取limit 1001 offset 2,然后取10前10条数据
abase=# select * from ( select ctid,n_int,c_id from test order by n_int asc limit 1001 offset 2) td limit 10;
ctid | n_int | c_id
----------+-------+------
(0,5) | 100 | 5
(0,55) | 100 | 888
(0,1) | 100 | 1
(0,9) | 100 | 9
(0,2) | 100 | 2
(0,3) | 100 | 3
(44,12) | 100 | 888
(0,7) | 100 | 7
(87,195) | 100 | 888
(0,4) | 100 | 4
(10 rows)
堆排序使用内存: Sort Method: top-N heapsort Memory: 95kB
```
当offset从0变成1后,以及变成2后,会发现查询出的10条数据不是有顺序的。
### 归并排序
```sql
--将work_mem设置为64kb让其走归并排序。
abase=# set work_mem ='64kB';
SET
abase=# show work_mem;
work_mem
----------
64kB
(1 row)
-- external merge Disk
abase=# explain analyze select * from ( select ctid,n_int,c_id from test order by n_int asc limit 1001 offset 0) td limit 10;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
Limit (cost=2061.33..2061.45 rows=10 width=13) (actual time=27.912..27.916 rows=10 loops=1)
-> Limit (cost=2061.33..2063.83 rows=1001 width=13) (actual time=27.910..27.913 rows=10 loops=1)
-> Sort (cost=2061.33..2135.72 rows=29757 width=13) (actual time=27.909..27.911 rows=10 loops=1)
Sort Key: test.n_int
Sort Method: external merge Disk: 784kB
-> Seq Scan on test (cost=0.00..429.57 rows=29757 width=13) (actual time=0.024..6.730 rows=29757 loops=1)
Planning time: 0.218 ms
Execution time: 28.358 ms
(8 rows)
--同堆排序一样,获取limit 1001 offset 0,然后取10前10条数据
abase=# select * from ( select ctid,n_int,c_id from test order by n_int asc limit 1001 offset 0) td limit 10;
ctid | n_int | c_id
--------+-------+------
(0,1) | 100 | 1
(0,2) | 100 | 2
(0,4) | 100 | 4
(0,8) | 100 | 8
(0,9) | 100 | 9
(0,5) | 100 | 5
(0,3) | 100 | 3
(0,6) | 100 | 6
(0,55) | 100 | 888
(0,7) | 100 | 7
(10 rows)
--同堆排序一样,获取limit 1001 offset 1,然后取10前10条数据
abase=# select * from ( select ctid,n_int,c_id from test order by n_int asc limit 1001 offset 1) td limit 10;
ctid | n_int | c_id
----------+-------+------
(0,2) | 100 | 2
(0,4) | 100 | 4
(0,8) | 100 | 8
(0,9) | 100 | 9
(0,5) | 100 | 5
(0,3) | 100 | 3
(0,6) | 100 | 6
(0,55) | 100 | 888
(0,7) | 100 | 7
(87,195) | 100 | 888
(10 rows)
--同堆排序一样,获取limit 1001 offset 2,然后取10前10条数据
abase=# select * from ( select ctid,n_int,c_id from test order by n_int asc limit 1001 offset 2) td limit 10;
ctid | n_int | c_id
----------+-------+------
(0,4) | 100 | 4
(0,8) | 100 | 8
(0,9) | 100 | 9
(0,5) | 100 | 5
(0,3) | 100 | 3
(0,6) | 100 | 6
(0,55) | 100 | 888
(0,7) | 100 | 7
(87,195) | 100 | 888
(44,12) | 100 | 888
(10 rows)
```
减小work_mem使用归并排序的时候,offset从0变成1后以及变成2后,任然有序。
### 还有一种情况,那就是在查询前面几页的时候会有重复,但是越往后面翻就不会重复了,现在也可以解释清楚。
```sql
如果每页10条数据,当offse较小的时候使用的内存较少。当offse不断增大,所耗费的内存也就越多。
--设置work_mem =64kb
abase=# show work_mem;
work_mem
----------
64kB
(1 row)
--查询limit 10 offset 10
abase=# explain analyze select * from ( select ctid,n_int,c_id from test order by n_int asc limit 10 offset 10) td limit 10;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
Limit (cost=1221.42..1221.54 rows=10 width=13) (actual time=12.881..12.884 rows=10 loops=1)
-> Limit (cost=1221.42..1221.44 rows=10 width=13) (actual time=12.879..12.881 rows=10 loops=1)
-> Sort (cost=1221.39..1295.79 rows=29757 width=13) (actual time=12.877..12.879 rows=20 loops=1)
Sort Key: test.n_int
Sort Method: top-N heapsort Memory: 25kB
-> Seq Scan on test (cost=0.00..429.57 rows=29757 width=13) (actual time=0.058..6.363 rows=29757 loops=1)
Planning time: 0.230 ms
Execution time: 12.976 ms
(8 rows)
--查询limit 10 offset 1000
abase=# explain analyze select * from ( select ctid,n_int,c_id from test order by n_int asc limit 10 offset 1000) td limit 10;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
Limit (cost=2065.75..2065.88 rows=10 width=13) (actual time=27.188..27.192 rows=10 loops=1)
-> Limit (cost=2065.75..2065.78 rows=10 width=13) (actual time=27.186..27.188 rows=10 loops=1)
-> Sort (cost=2063.25..2137.64 rows=29757 width=13) (actual time=26.940..27.138 rows=1010 loops=1)
Sort Key: test.n_int
Sort Method: external merge Disk: 784kB
-> Seq Scan on test (cost=0.00..429.57 rows=29757 width=13) (actual time=0.026..6.374 rows=29757 loops=1)
Planning time: 0.207 ms
Execution time: 27.718 ms
(8 rows)
可以看到当offset从10增加到1000的时候,使用的内存增加,排序的方法从堆排序变成了归并排序。而归并排序为稳定排序,所以后面的分页不会再有后一页出现前一页数据的情况。
```
## 结语
1.关于分页重复数据的问题主要是排序字段不唯一并且执行计划走了快速排序和堆排序导致。
2.当排序有重复字段,但是如果查询是归并排序,便不会存在有重复数据的问题。
3.当用重复字段排序,前面的页重复,随着offset的增大导致work_mem不足以后使用归并排序,就不存在重复的数据了。
4.排序和算法的稳定性有关,当执行计划选择不同的排序算法时,返回的结果不一样。
5.处理重复数据的常见手段就是,排序的时候选择唯一字段来排序。
[参考资料](https://stackoverflow.com/questions/13580826/postgresql-repeating-rows-from-limit-offset)
[参考资料](https://www.postgresql.org/docs/8.3/queries-limit.html)
postgresql-分页重复数据探索的更多相关文章
- postgresql-分页数据重复问题探索
postgresql-分页数据重复探索 问题背景 许多开发和测试人员都可能遇到过列表的数据翻下一页的时候显示了上一页的数据,也就是翻页会有重复的数据. 如何处理? 这个问题出现的原因是因为选择的排序字 ...
- postgresql排序分页时数据重复问题
当同时排序又分页时,如果排序的字段X不是唯一字段,当多个记录的X字段有同一个值时顺序是随机的. 这个有可能造成分页时数据重复的问题.某一页又把上一页的数据查出来了,其实数据库只有一条记录. 解决办法: ...
- oracle 分页查询数据重复问题
最近在做项目的时候发现一个问题,oracle 在查询分页数据的时候,有几条数据重复查询了,并且有几条数据在分页的时候消失了.百度了一下发现,ORACLE 在查询数据的时候返回的行不是固定的,他只是按照 ...
- postgresql数据库中对重复数据的处理
我们在使用postgresql数据库的时候,如果一张数据表在未做任何约束的情况下,很可能会出现几条完全一样的数据,即重复数据.如下图所示: 那么如果我们要删除其中的2条该怎么办呢?第一种我们可以清空表 ...
- 【SQL】Oracle和Mysql的分页、重复数据查询(limit、rownum、rowid)
上周三面试题有两道涉及Oracle的分页查询,没有意外地凉了,现在总结一下. · Mysql mysql的分页可以直接使用关键字limit,句子写起来比较方便. 语法: ① limit m,n -- ...
- Hibernate分页功能数据重复问题
今天遇到一个很憋屈的问题那就是hibernate分页查询中出现重复数据,本来一直没有在意,以为是数据问题,但是一查程序和数据都没有问题,继续深入查看,找到问题了就是order By 时出的问题,唉.. ...
- oracle去除重复数据与oracle分页
一.去除oracle中重复数据,可以使用rowid列,rowid列是一个伪列,该列在数据库中灭一个表中都有,但是我们查询数据库的时候,默认都没有给我们返回这一列,这一列用来区分数据库中的每一行时间,可 ...
- oracle 相关查询和非相关查询,oracle 去除重复数据,以及oracle的分页查询!
一.oracle中的相关查询?和非相关查询? 二.oracle去除重复数据 1. 2. 3.oracle 实现分页? 利用rownum的唯一性,和子查询,将rownum从伪列变成实际列!
- MSSQL sql server 2005/2008 row_number()函数应用之–删除表中重复记录,只保留一条不重复数据
转自:http://www.maomao365.com/?p=4942 下文主要讲述:重复数据只获取一条的方法 row_number函数在数据库中的功能是为每一行 按照一定的规则生成一个编号,我们常常 ...
随机推荐
- python的执行过程
1,解释器找到代码文件 2,将代码字符串按照文件头或者解释器默认的编码格式加载待内存,转为unicode格式 3,将代码字符串按照语法规则解释 4,转为二进制语言 5,进行执行
- day37 异步回调和协程
异步回调 """ 异步任务使用场景 爬虫 1.从目标站点下载网页数据 本质就是HTML格式字符串 2.用re从字符串中提取出你需要的数据 ""&quo ...
- python调用webservice接口
使用suds这个第三方模块 from suds.client import Clienturl = 'http://ip:port/?wsdl'cilent=Client(url)print cile ...
- 528. Random Pick with Weight index的随机发生器
[抄题]: Given an array w of positive integers, where w[i] describes the weight of index i, write a fun ...
- 156. Binary Tree Upside Down反转二叉树
[抄题]: Given a binary tree where all the right nodes are either leaf nodes with a sibling (a left nod ...
- swift 监测内存泄漏 MLeaksFinder
使用MLeaksFinder检测项目内存泄露总结 https://www.cnblogs.com/ocarol/p/5288497.html
- CentOS7 开启网卡,设置开机启用网卡
默认centos和redhat7都是不启用有线网卡的,要么手动开启,要么安装时直接启用(安装时启用网卡和指定IP最省事)! 一 .临时启用网卡,关机或重启后,网络不会自动开启1.使用命令 ip add ...
- PHP开发——数组
数组的概念 l 数组是一组数的集合.如:$arr = array(1,2,3,4,5,6) l 标量数据类型是一个值的容器,而数组就是多个值的容器. 数组的分类 l 枚举数组:数组元素的下标(索 ...
- c#NPOI读取excel 比interop和Microsoft.Jet.OLEDB.4.0 之类 的好的多
今天下午开始整理excel这块, 微软弄的那些库简直是个坑, 什么com注册之类的净是些报错. 在网上搜资料偶然碰见npoi ,好东西,值得使用 NPOI是指构建在POI 3.x版本之上的一个程序,N ...
- 要开始学习C#
之前有涉及ASP.NET,但是就仅涉及workflow这点,现在再接触还是有点陌生. 整理一些VS使用小技巧: 1,for cw ctor 按两下Tab键会出现整个的语句 2,Ctrl+shift ...