分庫分表的幾種常見玩法及如何解決跨庫查詢問題?
在現在的互聯網架構中,分庫分表是一種非常常見的手段,主要用于解決單表或者單庫數據過多而導致的性能問題。
通常,我們分庫有水平切分和垂直切分兩種方式垂直切分在我們的微服務架構中很常見,將數據庫根據業務模塊進行拆分,業務的邏輯處理都通過服務調用來進行,而不是將邏輯放在數據層面,這樣就能降低數據庫表與表之間的耦合度。
而水平切分,就是我們通常用來解決數據問題的手段了。將數據庫中單表的數據進行切分,分成多張相同的表單,數據按照一定的規則分布到不同的數據庫實例中,從而達到降低數據量、提高性能的目的。
而水平切分,就需要有分庫的依據使用哪個字段來作為分庫的依據呢?
通常情況下,我們會選擇主鍵作為分庫的依據,根據一定的算法,將數據均勻的分布到每個數據庫實例中,同時,盡量讓請求也均勻的分布到每個數據庫實例上。
例如:我們將訂單表進行了切分,一分為二(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條數據了。
如果誰還有更好的分庫分頁查詢的方法,也歡迎指教!