02.29 分庫分表的幾種常見玩法及如何解決跨庫查詢問題?

冬季凱風


在現在的互聯網架構中,分庫分表是一種非常常見的手段,主要用於解決單表或者單庫數據過多而導致的性能問題。

通常,我們分庫有水平切分和垂直切分兩種方式

垂直切分在我們的微服務架構中很常見,將數據庫根據業務模塊進行拆分,業務的邏輯處理都通過服務調用來進行,而不是將邏輯放在數據層面,這樣就能降低數據庫表與表之間的耦合度。

而水平切分,就是我們通常用來解決數據問題的手段了。將數據庫中單表的數據進行切分,分成多張相同的表單,數據按照一定的規則分佈到不同的數據庫實例中,從而達到降低數據量、提高性能的目的。

而水平切分,就需要有分庫的依據

使用哪個字段來作為分庫的依據呢?

通常情況下,我們會選擇主鍵作為分庫的依據,根據一定的算法,將數據均勻的分佈到每個數據庫實例中,同時,儘量讓請求也均勻的分佈到每個數據庫實例上。

例如:我們將訂單表進行了切分,一分為二(DB1、BD2),訂單表的主鍵就是訂單ID,我們想要均勻的分佈數據的話,我們可以對訂單ID進行判斷,是單數,我們就放在DB1中,是雙數,我們就放到DB2中,這樣,我們的數據分佈就非常的平均,同時,我們的請求在概率上,也是平均的。

當然,分庫依據可以很多,這個可以根據自己的業務場景進行設置,只要明白,我們分庫是為了緩解數據庫的壓力,降低單表的數據量,如果我們分庫以後,DB1的數據量和請求數遠大於DB2,那麼我們分庫的意義就不是很大了。

而分庫以後,最難解決的就是分頁查詢的問題

通常情況下,我們的分頁查詢都是通過時間維度進行排序的。如以下sql:

select * from T order by time offset X limit Y;

但是,分庫以後,不同的數據庫如何進行查詢排序呢?我們就來說一跨庫的分頁查詢方式。

全局視野方式

假設,我們現在要查詢某張表的第三頁數據,每頁100條數據,曾經沒有分庫的時候,我們只需要

select * from T order by time offset 200 limit 100;

但是,分庫以後,這第三頁的100條數據就有很多種分佈方式了。

1)均勻分佈(極端情況)

數據非常均勻的分佈在兩個庫中,想要找到第三頁的數據,就在兩個庫中各取50%就好了。

2)全部來自一個庫(極端情況)

數據非常不平均的分配到了一個庫中,所有的數據都來至於一個庫,也就是說,只需要取這個單庫的數據就可以了。

3)散亂分佈(通常情況)

這種情況下,我們很難知道,第三頁的數據應該在不同的庫中從第幾條開始取數,因為分庫後,我們丟失了全局視野。因此,如果我們想要精準的找到目標數據,就必須重新構建全局的視野。

如何重新構建這種全局視野呢?

還是用我們要查詢第三頁的數據來舉例,我們可以將兩個庫中的第一到第三頁的數據全部查詢出來,然後在內存中合併後進行排序,再取出第三頁的數據。

我們的sql也就發生了變化,從

select * from T order by time offset 200 limit 100;

改為

select * from T order by time offset 0 limit 100+200;

全局視野方式進行查詢的好處很明顯,就是能夠讓業務數據絕對精準的返回。但是缺點也是明顯,數據的查詢量大,而且消耗的內存資源較多,當頁碼增大的時候,性能會集聚的下降。

如果想要解決全局視野方式的缺點,我們可以做出交互上的一點小犧牲來實現

禁止跳轉頁方式

相信這個分頁方式大家都不陌生,但是,這種分頁方式確實讓我們分庫以後的查詢難度幾何級的提升,如果想要解決跨頁查詢的問題,我們可以對我們的分頁控件進行優化,只保留“上一下”、“下一頁”的功能,去掉跳轉頁的功能。

當禁止跳頁以後,我們每次查詢後,就能夠得到當頁最後一次查詢結果的時間,我們要查詢一個分頁中的記錄時,是需要查詢大於當前時間的100條記錄就可以了。

兩個數據庫中各取100條,然後再彙總排序,這樣就能夠大大的提升查詢的效率,同時也保證了數據的精準。

我們的sql也就改成了

select * from T order by time where time>@preMaxTime limit 100;

使用此方式,我們就不會因為頁碼增加而出現性能的下降了,只是用戶的交互體驗會稍差一些了。當然,如果是APP用戶,就不用擔心這點了,因為APP用戶很少使用跳轉頁的交互方式。

允許精度損失方式

允許精度損失的方式就比較暴力,我們不去管數據的分佈問題,只是單純的每個庫中取出50條數據,然後排序展示。

在業務中,可能會出現第二頁的部分數據時間上早於第一頁的數據,這主要還是根據我們的保存數據時候分分佈情況來決定。如果我們存儲數據的時候,分佈得越平均,這種查詢方式得到的結果自然就越精準。

使用這種方式,我們就不需要考慮性能上的問題,也不需要考慮頁面跳轉和頁碼的問題,查詢的複雜度是最低的,是比較推薦的一種查詢方式。

當然,如果你的業務不允許這樣的情況出現,還需要滿足交互、效率等等各種需求,那麼,就只有使用最後一個方式了。

二次查詢方式

這可以說是解決分庫查詢的究極武器了,能夠保證數據的精準度、查詢的效率、用戶的交互頁面,犧牲的只是小小的性能開銷和一些代碼難度的上升。

方式其實也不難,假設我們要查詢第21頁的數據,每頁5條。這個時候,我們先假設數據是平均分佈的,但是我們在每個庫都查詢全量的5條數據。也就是:

select * from T order by time offset 100 limit 5;

這時,我們得到的數據可能是這樣的。

而兩個DB中,最小的時間是1487500001【minTime】,這個時間記錄下來。兩個DB中各自的最大時間也記錄下來,分別是DB1:1487500041【maxTime1】 和 DB2:1487500061【maxTime2】。

這時,我們在使用時間去兩個數據庫中再次進行查詢。

select * from T where time between minTime and maxTime1 order by time;
select * from T where time between minTime and maxTime2 order by time;

由於之前minTime來自於DB1,因此,DB1的數據不會發生變化,但是DB2中的條件被放寬了,因此可能會查詢出更多的數據。結果可能如下:

而兩個結果集合並以後,相當於就獲得了全局視野,也就可以很容易的找出這一頁需要的5條數據了。

如果誰還有更好的分庫分頁查詢的方法,也歡迎指教!


分享到:


相關文章: