分库后如何分页
前言
在實際應用中, 為了降低單表的數據量, 會對較大的表進行水平切分, 將單表的數據切分到多表多庫中.
既然要切分, 就要有一個切分的依據, 比如說按照 ID 取模等. 那么多張表聯合分頁是如何做到的呢?
如果分表的依據是字段 A, 但是需要根據字段 B 進行分頁查詢, 針對這種情況應該如何處理呢?
為了后面方便說明, 這里舉個例子.
有一個文章表 user_article
其中有一個文章的發表時間 publish_date. 這個時間用戶是可以修改的.
按照 ID 取模分到了兩個表中.
user_article_1 user_article_0
現在有這樣一個需求:
按照文章的發表時間進行排序分頁
單表
先來看在單表的時候, 我們是如何查詢的, 之后再擴展到多表.
select * from `user_article` order by `publish_date` offset 0 limit 10;單表查詢很簡單, 一條 sql 搞定.
多表
對于多表的情況, 將其抽象一下, 就是有兩個有序列表:
[1, 5, 7, 8, 9]
[2, 3, 4, 6, 10]
現在要取合并后的有序列表第 n 頁的數據.
方案一
想到的第一個方案, 將兩個列表合并之后, 就可以退化為單列表的情況了.
對應到 sql查詢中, 如果要取第三頁的數據, 可以肯定的是, 每個列表都不會讀到第四頁, 所以我們可以將每個表的前三頁拿出來, 在內存中進行合并后, 就可以拿到全局的第三頁了.
# 取第三頁的數據 select * from `user_article_0` order by `publish_date` offset 0 limit 3*10; select * from `user_article_1` order by `publish_date` offset 0 limit 3*10;這種方案確實可以獲取到分頁的數據, 但是查詢的數據量隨著頁數的增大而增大. 如果查詢第200頁的數據, 那每張表都要返回2000條數據, 性能太低.
方案二
如果說, 我們按照頁數依次獲取, 中間不跳頁的話, 那么就可以將上一次查詢的終點保存下來, 供下一次查詢使用.
比如, 上一次查詢, 最后一條數據是8, 那么, 下一次查詢從各個列表中取出大于8的10條數據, 內存排序后取前10條, 同時將最后一條的值存下來供下一次查詢使用.
對應到sql查詢中, 就需要有一個全局的searchId.
select * from `user_article_0` where `publish_date` > 'searchId' order by `publish_date` limit 10; select * from `user_article_1` where `publish_date` > 'searchId' order by `publish_date` limit 10;每頁查詢數量固定, 效率較高. 但同時, 這種功能方案不能做到跳頁, 如果要查詢第 n 頁的數據, 前提是查詢了 n-1頁. 同時, 此方案要求 publish_date字段無重復值, 如果有20條相同的publish_date, 在翻頁的時候就會丟失部分數據.
方案三
那么, 有沒有一種方案, 即能跳頁查詢, 查詢數量也能保持在常量級呢?
說明
來了, 為了方便說明, 先從數組開始:
[1, 3, 5, 7, 11, 18, 23, 32, 41]
[2, 8, 9, 15, 17, 22, 27, 51, 60]
此時, 如果想獲取全局: offset 4 limit 4 的數據. 因為我們不知道全局偏移量4在各個數組中的各自偏移量. 所以在方案一中需要進行大量的查詢, 如果我們知道了, 問題不就解決了么.
第一步, 分別取各列表半個偏移量的數據
先分別取各個數據中offset 2 limit 4的數據. 結果如下:
[5, 7, 11, 18]
[9, 15, 17, 22]
注意: 這里的offset 2, 是通過全局偏移量/表個數算出來的.
拿到這個數據之后, 我們得到了什么? 其中的最小值5, 全局偏移量必定>=2.
- 如果數據1中小于5的值大于2個, 那么第一次查詢時結果必定不同
- 如果數據2中存在小于5的值, 那么5的全局偏移量只會增加.
第二步, 獲取最小值的全局偏移量
通過第一步的分析, 如果我們能夠知道數據2中存在多少個小于5的值, 那么我們就能夠計算出5的全局偏移量. 進而得到全局偏移量為4的數據.
查詢數據2中, data > 5 and data < 9的數據, 結果如下:
[8]
前面, 我們知道9在數據2中的偏移量為2. 同時 數據2中, >5 and <9的數據, 個數為1, 計算可得, 數據2中小于5的數據個數為: 2-1=1
又因, 5在數據1中的偏移量為2, 進而可得, 5的全局偏移量為: 2+1=3. (加上數據2中小于5的數量)
第三步, 整合數據并返回
我們將前后查詢的結果整合一下, 得到如下數據:
[5, 7, 11, 18]
[8, 9, 15, 17, 22]
再將兩數組合并為一個數組:
[5, 7, 8, 9, 11, 15, 17, 18, 22]
已知, 5的全局偏移量為3, 則偏移量為4的數據為7. 我們從7開始, 向后拿4個, 就是全局的offset 4 limit 4的數據了.
[7, 8, 9, 11]
問題
到這里, 你以為已經完成了么? 看下面這組數據:
[1, 2, 3, 4, 5, 6, 7, 8]
[9, 10, 11, 12, 13, 14, 15, 16]
很明顯, 這組數據分布十分不均勻, 按照上面的操作獲取分頁數據offset 4 limit 4
第一步折半查詢結果offset 2 limit 4
[3, 4, 5, 6]
[11, 12, 13, 14]
然后拿到3的全局偏移量2. 得到偏移量4的數據為5. 組合后返回結果為:
[5, 6, 9, 10]
這, 明眼人一看, 就知道結果應該是[5, 6, 7, 8].
很明顯, 因為數據都在一張表上, 所以導致第一次獲取數據沒有取完. 但是, 到這一步, 我們獲取到的偏移量是沒有問題的.
也就是說, 全局偏移量為4的數據為5. 那么, 我們就可以退回到方案二 進行處理了.
當然, 如果對數據的精度要求沒有那么高, 或者確信數據分布不會出現這種極限情況, 可以忽略.
貌似網上將這種方法稱為二次查詢, 沒有找到文章提到這個問題, 難道說實際應用中不會遇到么?
sql
將上面方案轉為 sql, 取第4頁的數據, 既offset 30 limit 10
第一步, 分別取各表數據
select * from `user_article_0` order by `publish_date` offset 30/2 limit 10; select * from `user_article_1` order by `publish_date` offset 30/2 limit 10;我們在這一步, 統計出查詢結果的最小值 M
第二步, 最小值全局偏移量
下方sql中的 M', 代表各個查詢結果的最小值.
select * from `user_article_0` where `publish_date`>M and `publish_date`<"M'" order by `publish_date`; select * from `user_article_1` where `publish_date`>M and `publish_date`<"M'" order by `publish_date`;此時, 通過計算, 我們已經得到M的全局偏移量了. 同時, 也拿到了偏移量30的值S.
如果數據分布十分不均勻, 在這一步, 極端情況會將前面所有數據都拿出來.
第三步, 返回結果
如果確信不會出現前面提到的極限情況, 這里直接組合結果并返回即可.
否則, 繼續執行下面查詢并返回. 此時, 排序字段不可重復.
select * from user_article_0 where publish_date > S order by publish_date limit 10; select * from user_article_1 where publish_date > S order by publish_date limit 10;此方案執行了三倍的數據庫查詢, 但優點是查詢效率恒定與頁數無關, 且支持跳頁.
方案四
因為我們的數據是按照 ID 取模, 根據概率分布, 兩個庫的數據分布應該是一樣的. 那是不是可以每個表取半頁數據, 拿回來拼上.
這樣的話, 取第3頁數據的 sql為:
select * from user_article_0 order by publish_date offset 20/2 limit 5; select * from user_article_1 order by publish_date offset 20/2 limit 5;這樣的話, 數據不太準確, 拿到的數據順序可能不對, 但重點是查詢效率高啊. 應該是有對順序精度沒什么要求的場景吧. 想到了這種方案, 但是暫時沒有想到應用場景.
如果是針對分表字段排序的話, 那么數據分布均勻, 此方案完美.
最后
具體業務應該如何選擇分頁方式呢?
- 如果不需要跳頁, 直接選擇方案二
- 如果對順序精度沒什么要求, 直接選擇方案四
- 如果只需要查詢前 n 頁數據, 且 n 比較小. 那么方案一 可能更適合你
- 如果需要查詢所有數據, 且需要跳頁, 可以選擇 方案一 和 方案三 結合的方式. 優先選擇當前效率更高的.
- 對于訪問頻率較高的前幾頁數據, 選擇 方案一
- 對于頁數比較大的數據, 選擇 方案三
當然了, 前面說的情況, 排序字段與分表字段不同, 數據分布可能不均勻. 如果是相同的字段, 那就沒這么多事了, 數據都是均勻分布的, 參考 方案四
最后, 對于排序使用的字段, 最好能夠保證其唯一性, 如果不能, order by的時候, 請添加輔助字段排序.
總結
- 上一篇: scikit-learn安装
- 下一篇: k6前级效果器怎么用_新手学习电吉他,效