亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 數據庫 > PostgreSQL > 正文

postgresql分頁數據重復問題的深入理解

2020-01-31 15:20:43
字體:
來源:轉載
供稿:網友

問題背景

許多開發和測試人員都可能遇到過列表的數據翻下一頁的時候顯示了上一頁的數據,也就是翻頁會有重復的數據。

如何處理?

這個問題出現的原因是因為選擇的排序字段有重復,常見的處理辦法就是排序的時候加上唯一字段,這樣在分頁的過程中數據就不會重復了。 關于這個問題文檔也有解釋并非是一個bug。而是排序時需要選擇唯一字段來做排序,不然返回的結果不確定

排序返回數據重復的根本原因是什么呢?

經常優化sql的同學可能會發現,執行計劃里面會有Sort Method這個關鍵字,而這個關鍵字就是排序選擇的方法。abase的排序分為三種

quicksort                       快速排序   
top-N heapsort  Memory          堆排序
external merge  Disk            歸并排序

推測

分頁重復的問題和執行計劃選擇排序算法的穩定性有關。

簡單介紹下這三種排序算法的場景:

在有索引的情況下:排序可以直接走索引。 在沒有索引的情況下:當表的數據量較小的時候選擇快速排序(排序所需必須內存小于work_mem), 當排序有limit,且耗費的內存不超過work_mem時選擇堆排序, 當work_mem不夠時選擇歸并排序。

驗證推測

1.創建表,初始化數據

abase=# create table t_sort(n_int int,c_id varchar(300));CREATE TABLEabase=# insert into t_sort(n_int,c_id) select 100,generate_series(1,9);INSERT 0 9abase=# insert into t_sort(n_int,c_id) select 200,generate_series(1,9);INSERT 0 9abase=# insert into t_sort(n_int,c_id) select 300,generate_series(1,9);INSERT 0 9abase=# insert into t_sort(n_int,c_id) select 400,generate_series(1,9);INSERT 0 9abase=# insert into t_sort(n_int,c_id) select 500,generate_series(1,9);INSERT 0 9abase=# insert into t_sort(n_int,c_id) select 600,generate_series(1,9);INSERT 0 9

三種排序

--快速排序 quicksortabase=# 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 heapsortabase=# 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 1001abase=# set work_mem = '64kB';SETabase=# 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)

快速排序

--快速排序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這條數據被“遺漏”了。

堆排序

abase=# select count(*) from t_sort; count ------- 1055(1 row)--設置work_mem 4MBabase=# show work_mem ; work_mem ---------- 4MB(1 row)​--top-N heapsortabase=# 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=29757 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條數據不是有順序的。

歸并排序

--將work_mem設置為64kb讓其走歸并排序。abase=# set work_mem ='64kB';SETabase=# show work_mem; work_mem ---------- 64kB(1 row)​-- external merge Diskabase=# 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后,任然有序。

還有一種情況,那就是在查詢前面幾頁的時候會有重復,但是越往后面翻就不會重復了,現在也可以解釋清楚。

如果每頁10條數據,當offse較小的時候使用的內存較少。當offse不斷增大,所耗費的內存也就越多。

--設置work_mem =64kbabase=# show work_mem; work_mem ---------- 64kB(1 row)--查詢limit 10 offset 10abase=# 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 1000abase=# 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的時候,使用的內存增加,排序的方法從堆排序變成了歸并排序。而歸并排序為穩定排序,所以后面的分頁不會再有后一頁出現前一頁數據的情況。

參考資料:PostgreSQL - repeating rows from LIMIT OFFSET

參考資料: LIMIT and OFFSET

結語

1.關于分頁重復數據的問題主要是排序字段不唯一并且執行計劃走了快速排序和堆排序導致。

2.當排序有重復字段,但是如果查詢是歸并排序,便不會存在有重復數據的問題。

3.當用重復字段排序,前面的頁重復,隨著offset的增大導致work_mem不足以后使用歸并排序,就不存在重復的數據了。

4.排序和算法的穩定性有關,當執行計劃選擇不同的排序算法時,返回的結果不一樣。

5.處理重復數據的常見手段就是,排序的時候可以在排序字段d_larq(立案日期)后面加上c_bh(唯一字段)來排序。

order by d_larq,c_bh;

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對武林網的支持。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
91免费人成网站在线观看18| 欧美日韩美女在线观看| 亚洲欧洲在线看| 最新国产成人av网站网址麻豆| 亚洲人高潮女人毛茸茸| 91亚洲人电影| 亚洲成人精品在线| 69久久夜色精品国产69乱青草| 一区二区三区在线播放欧美| 欧美成人sm免费视频| 国产成人aa精品一区在线播放| 在线日韩精品视频| 日韩久久午夜影院| 另类少妇人与禽zozz0性伦| 国产精品露脸av在线| 日韩精品电影网| 国产精品96久久久久久又黄又硬| 亚洲字幕在线观看| 亚洲欧美中文字幕在线一区| 日本精品视频在线观看| 国产亚洲精品一区二555| 国产精品美女久久久免费| 丰满岳妇乱一区二区三区| 日韩中文字幕在线视频播放| 久久艹在线视频| 欧美午夜宅男影院在线观看| 欧美日韩电影在线观看| 一区二区三区国产在线观看| 日韩美女在线播放| 视频直播国产精品| 国内精品久久久久影院 日本资源| 2019中文字幕全在线观看| 国产亚洲欧美aaaa| 久久久伊人日本| 永久免费精品影视网站| 不用播放器成人网| 国产精品女人久久久久久| 国产精品日韩在线播放| 欧美性资源免费| 日本亚洲精品在线观看| 久久中文字幕视频| 国产免费一区视频观看免费| 91社影院在线观看| 中文字幕亚洲欧美一区二区三区| 成年无码av片在线| 日本韩国欧美精品大片卡二| 欧美性xxxx极品高清hd直播| 黑人巨大精品欧美一区二区一视频| 亚洲国产欧美一区二区三区久久| 91精品国产高清久久久久久91| 亚洲肉体裸体xxxx137| 欧美日韩成人在线观看| 国产69精品久久久| 亚洲免费一级电影| 亚洲精品理论电影| 97高清免费视频| 亚洲日韩中文字幕在线播放| 91嫩草在线视频| 国产精品美女www爽爽爽视频| 精品久久久在线观看| 欧美精品情趣视频| 中文字幕精品www乱入免费视频| 国产精品aaa| 免费不卡在线观看av| 日韩一级黄色av| 亚洲欧美在线看| 亚洲精品国产精品国自产观看浪潮| 亚洲直播在线一区| 福利视频一区二区| 成人午夜在线视频一区| 中文字幕日本精品| 国产在线精品播放| 奇米影视亚洲狠狠色| 国产免费成人av| 日韩av在线免播放器| 国内伊人久久久久久网站视频| 亚洲欧美一区二区三区久久| 久久久精品国产亚洲| 九九热视频这里只有精品| 欧美影院成年免费版| 91成人在线视频| 国产中文日韩欧美| 国产亚洲欧洲高清一区| 中文字幕不卡av| 狠狠躁夜夜躁人人躁婷婷91| 欧美一区二区大胆人体摄影专业网站| 亚洲美女久久久| 永久免费毛片在线播放不卡| 欧美精品在线视频观看| 欧美黄色性视频| 国产精品久久不能| 91麻豆桃色免费看| 欧美极品少妇xxxxⅹ喷水| 538国产精品一区二区免费视频| 91精品国产综合久久男男| 最新国产成人av网站网址麻豆| 日韩欧美在线免费观看| 77777少妇光屁股久久一区| 一本色道久久88精品综合| 国产女精品视频网站免费| 国产极品精品在线观看| 成人乱色短篇合集| 欧美亚洲伦理www| 久久亚洲精品网站| 欧美国产极速在线| 国产欧美一区二区三区久久人妖| 国产精品久久久久久影视| 国产精品视频1区| 国产免费一区二区三区在线观看| 国产成人精品一区二区三区| 亚洲欧美激情一区| 亚洲精品中文字幕女同| 久久在线观看视频| 国产成人综合一区二区三区| 2019中文在线观看| 久久久精品久久久久| 日韩欧美视频一区二区三区| 欧美大秀在线观看| 日本道色综合久久影院| 欧亚精品在线观看| 日韩欧美国产激情| 久久久久久高潮国产精品视| 欧美高清在线观看| 欧美精品情趣视频| 欧美老少配视频| 欧美电影免费观看大全| 国产精品免费小视频| 日韩av免费一区| 国产男女猛烈无遮挡91| 一区二区三区天堂av| 国产欧美最新羞羞视频在线观看| 日韩精品黄色网| 尤物精品国产第一福利三区| 亚洲国产精品久久久| 亚洲香蕉在线观看| 欧美电影在线观看网站| 性欧美暴力猛交69hd| 国产这里只有精品| 日韩一区二区精品视频| 国产精品久久久久久搜索| 亚洲区一区二区| 日韩国产精品一区| 日韩理论片久久| 精品中文字幕视频| 91久久久久久久| 亚洲精品大尺度| 狠狠色狠色综合曰曰| 最近日韩中文字幕中文| 成人xvideos免费视频| 欧美性猛交99久久久久99按摩| 精品国模在线视频| 97国产suv精品一区二区62| 久久亚洲精品毛片| 日韩精品黄色网| 久久久久久免费精品| 91九色国产视频| 亚洲精品一区二区三区婷婷月| 国产成人精品综合久久久| 欧美中文在线视频| 狠狠躁天天躁日日躁欧美| 欧美福利在线观看| 精品亚洲一区二区三区四区五区| 国产精品你懂得| 91久久国产精品91久久性色|