分表分頁/跨庫分頁為什么這么難?
來源:http://yjmyzz.cnblogs.com
(1,2,3,4,5,6,7,8)
如果要取出上面紅色標(biāo)識的2,3這二條記錄,limit 1,2 就行了。
現(xiàn)在假如分成2張表(即:原來的數(shù)列,拆分成2個非空子數(shù)列),一般來講,有二種常用分法:
分段法(比如:有時間屬性的數(shù)據(jù),類似訂單這種,可以按下單時間拆分,每個月1張表)
(1,2,3,4)
(5,6,7,8)
沿用之前的limit x,y的思路,每個分表上 limit 1,2,會得到如下2個子數(shù)列:
(2,3)
(6,7)
(1,2,3,4,5,6,7,8) 比如,我們要從4個位置開始,連續(xù)取2個元素,即: limit 3,2
(1,2,3,4) => limit 3,2 =>(4)
(5,6,7,8) => limit 3,2 =>(8)
最后合并出來的結(jié)果是(4,8) 與正確結(jié)果 (4,5)相比,顯然不對。
1.2 模余均攤法(比如:字段值對2取模求余數(shù),根據(jù)余數(shù)決定分到哪個表,該方法也簡稱為取余法)
(1,3,5,7)
(2,4,6,8)
歸并排序的思路在分段法上行不通,對于取模均攤同樣也不行,仍以 limit 1,2為例,原始序列取出來的結(jié)果是(2,3),如果用歸并的思路:
(1,3,5,7)=> limit 1,2 =>(3 ,5)
(2,4,6,8)=> limit 1,2 =>(4, 6)
內(nèi)存合并排序后,取前2個,最終結(jié)果為(3 , 4)
結(jié)論:不管分庫分表采用什么分法,簡單歸并的思路,都無法正確解決分頁問題。
用前面的例子驗證一下:
原序列:(1,2,3,4,5,6,7,8),需要取出limit 1,2 ,即:(2,3)
按分段法拆成2段:
(1 , 2 , 3 , 4) => limit 1,2 =>改寫成 limit 0, 1+2 => (1,2,3)
(5 , 6 , 7 , 8) => limit 1,2 =>改寫成 limit 0, 1+2 => (5,6,7)
將子數(shù)列合并排序=> { 1,2,3,5,6,7} => 按原始偏移量 limit 1,2 =>{2,3} 正確
如果原數(shù)列中要取的數(shù)據(jù),正好落在2個子數(shù)列上(1,2,3,4,5,6,7,8),需要取出limit 3,2 ,即:(4,5)
(1 , 2 , 3 , 4) => limit 3,2 =>改寫成 limit 0, 3+2 => (1,2,3,4)
(5 , 6 , 7 , 8) => limit 3,2 =>改寫成 limit 0, 3+2 => (5,6,7,8)
將子數(shù)列合并排序=> (1,2,3,4,5,6,7,8) => 按原始偏移量 limit 3,2 => (4,5) 也符合預(yù)期。
取模均攤拆成2段
(1,3,5,7) => limit 1,2 ->改寫成 limit 0, 1+2 => (1,3 ,5)
(2,4,6,8) => limit 1,2 ->改寫成 limit 0, 1+2=> (2,4,6)
將子序列合并=> (1,2,3,4,5,6) => 按原始偏移量 limit 1,2 =>(2,3) 正確
這也是"業(yè)界難題-跨庫分頁”一文中提到的一個方法,大致思路如下:在某1頁的數(shù)據(jù)均攤到各分表的前提下(注:這個前提很重要,也就是說不會有一個分表的數(shù)據(jù)特別多或特別少),換句話說:這個方案不適用分段法,按如下步驟操作:
1)原sql中的limit offset,pagesize 改寫成 limit offset/n ,pagesize (注:n為分表個數(shù),如果offset/n除不盡,向下取整,避免最后的結(jié)果丟數(shù)據(jù))-- 這個的意思,其實就是假設(shè)原表這一頁的數(shù)據(jù),會均分到各個分表(所以,我一再強調(diào),前提是數(shù)據(jù)是均攤的,如果某個分表的記錄很少,極端情況下,甚至是空的,這個就不對了,最終結(jié)果會少數(shù)據(jù))
3)各分表上的sql,where條件部分改寫成 id between min_id and origin_max_id (注:origin_max_id為上一步,每個分表查詢結(jié)果集中的最大值,顯然min_id=自身最小id的那張分表,不用再重復(fù)查詢) -- 這一步的目的在于,因為步驟1)查出來的結(jié)果,通常會比原表上該頁的數(shù)據(jù)少,所以這里重新將起始點設(shè)置到正確的位置,即:min_id,再查1次,相當(dāng)于范圍擴(kuò)大了,以保證數(shù)據(jù)不會丟。不過,這里有一個可優(yōu)化的地方,仔細(xì)想想,這1次查詢出來的結(jié)果,跟步驟1)中的查出來的結(jié)果,必然有一部分是重復(fù)的,因此改寫部分,只需要 id between min_id and origin_min_id就可以了(origin_min_id 即為原來分表結(jié)果上的最小id)
4)將上一步查詢出來的結(jié)果,在內(nèi)存中合并排序去重(注:如果上一步采用了優(yōu)化方案,就應(yīng)該是把1)與3)這二次查詢的結(jié)果全取出來合并排序去重),然后從開始連續(xù)取pagesize條數(shù)據(jù)即可(注:offset/n除不盡的話,向下取整了,也就是起始點可能向前多移了,所以有可能開始的第1條記錄,其實是上1頁的最后1條記錄,要追求精確的話,可以在應(yīng)用層記錄上一頁最后1條記錄的id,然后跟本次查詢結(jié)果前1條記錄對比,如果發(fā)現(xiàn)是一樣的,開始取數(shù)據(jù)的位置,就要向后移1位,如果考慮id有重復(fù)的話,就要根據(jù)情況多移幾位)
驗證一下看看效果:
場景1(前提:取余法)
原序列:(1,2,3,4,5,6,7,8),需要取出limit 2,2 ,即:(3,4)
第1次查詢
(1,3,5,7) -> limit 2,2 -> 改寫成 limit 1,2 -> (3,5)
(2,4,6,8) -> limit 2,2 -> 改寫成 limit 1,2 -> (4,6)
最小值為3
第2次查詢
(1,3,5,7) -> between 3 and 5 -> (3,5)
(2,4,6,8) -> between 3 and 6 -> (4,6)
將第2次查詢的結(jié)果合并:
(3,4,5,6) ->取頭開始,取pageSize即2個元素 -> (3,4) 正確
場景2(前提:取余法)
原序列:(1,2,3,4,5,6,7,8),需要取出limit 1,2 ,即:(2,3)
第1次查詢
(1,3,5,7) -> limit 1,2 -> 改寫成 limit 0,2 -> (1,3) --注:因為1/2除不盡,這里向下取整了
(2,4,6,8) -> limit 1,2 -> 改寫成 limit 0,2 -> (2,4)
最小值為1
第2次查詢
(1,3,5,7) -> between 1 and 3 -> (1,3)
(2,4,6,8) -> between 1 and 4 -> (2,4)
將上面的結(jié)果合并:
(1,2,3,4) -> (2,3) (注:起始點,第1次查詢改寫時,向下取整了,所以這里要向移1位,從第2個數(shù)字開始取pagesize條數(shù)據(jù))
場景3(前提:分段法)
為什么說分段法,這個方案不適用,可以看下面的分析:
原序列:(1,2,3,4,5,6,7,8),需要取出limit 2,2 ,即:(3,4)
第1次查詢
(1,2,3,4) -> limit 2,2 -> limit 1,2 -> {2,3} --注:這里就已經(jīng)把正確的數(shù)據(jù)給丟掉了
(5,6,7,8) -> limit 2,2 -> limit 1,2 -> {5,6} --注:這一段里根本就沒有這一頁的數(shù)據(jù)
最小值2
第2次查詢
(1,2,3,4) -> between 2 and 3 -> {2,3}
(5,6,7,8) -> between 2 and 6 -> {5,6}
(2,3,5,6) -> (2,3) 這個跟預(yù)期結(jié)果就對不上了。
場景4(前提:取余法)
取余法的前提下,如果某個分表的數(shù)據(jù),被清掉一部分,也就是某個分表數(shù)據(jù)偏少,會發(fā)生什么?
比如下面這個,按奇數(shù)、偶數(shù)分成2個子序列,但是偶數(shù)故意刪除了幾個(相當(dāng)于現(xiàn)實業(yè)務(wù)中,這個分表上的數(shù)據(jù)被干掉了一部分)
原序列:(1,3,5,6,7,8,9,11),需要取出limit 2,2 ,即:(5,6)
第1次查詢
(1,3,5,7,9,11) -> limit 2,2 -> 改寫成limit 1,2 -> (3,5)
(6,8) -> limit 2,2 -> 改寫成limit 1,2 -> (8)
第2次查詢
(1,3,5,7,9,11) -> between 3 and 5 -> (3,5)
(6,8) -> between 3 and 8 -> (6,8)
合并后
(3,5,6,8) -> (3,5) 這跟預(yù)期結(jié)果也對不上。
感謝您的閱讀,也歡迎您發(fā)表關(guān)于這篇文章的任何建議,關(guān)注我,技術(shù)不迷茫!小編到你上高速。
正文結(jié)束
1.不認(rèn)命,從10年流水線工人,到谷歌上班的程序媛,一位湖南妹子的勵志故事
3.從零開始搭建創(chuàng)業(yè)公司后臺技術(shù)棧
5.37歲程序員被裁,120天沒找到工作,無奈去小公司,結(jié)果懵了...
6.IntelliJ IDEA 2019.3 首個最新訪問版本發(fā)布,新特性搶先看
一個人學(xué)習(xí)、工作很迷茫?
點擊「閱讀原文」加入我們的小圈子!

