一次特殊的分庫分表

需求

目前在一家在線教育公司工做,負責題庫項目。題庫項目因爲以前設計的結構有問題,因此新構建了一版新題庫。數據並無兼容舊題庫。所以業務端在查詢用戶的練習記錄的時候就須要調用新老題庫的接口,而後合併。
分庫分表最麻煩的就是排序分頁查詢了。題庫和商城訂單相似,通常只須要查詢某個用戶的練習記錄,排序通常都是按照時間倒序。
老題庫的練習記錄爲千萬級,新題庫爲百萬級。有些用戶的練習記錄可達到幾萬(免費的量就是大...)。所以用戶的練習記錄頁面必須進行分頁,並且要按照時間倒序排列,且支持各類篩選。前端

分析

解決這個問題主要有兩個大方向:
1.在存儲練習記錄的時候,單獨存儲一張彙總表,包含新老題庫的練習記錄的簡略信息。查詢的時候,先查詢這張表,再分別到新老題庫查具體信息。
2.優化查詢。數據庫

先看方法1,會增長建立練習的難度,不管這張彙總表存在新題庫仍是老題庫,都須要同步另外一個數據,這就涉及到數據一致性的問題了。因爲不在一個庫裏,會產生分佈式事務的額外問題。那可能有人會問,爲何舊題庫還有練習記錄呢?新題庫目前尚未支持完全部的考試類型,不支持的考試類型還在舊題庫中作。並且即便是新的考試類型,少部分類型的題目暫時還在使用舊題庫的。
此外,因爲練習記錄的查詢條件較多,所以,這張彙總表裏的字段,索引不會少,幾千萬條數據仍是比較佔用空間的。
通過考慮決定放棄方法1,採用優化查詢的方式。網絡

方案1直接全量查詢後分頁

先從最簡單,也是效果最差的方案提及吧。這個全量查詢其實也不是真正的全量查詢。好比須要查詢前10條,則只須要在新題庫中取出前10條,舊題庫中取出前10條。而後進行歸併排序,找到真正的前10條。
可是當須要查詢1000-1010條的時候,則須要在新舊題庫中各取出前1010條,而後進行歸併排序,找到第1000-1010條。由於可能舊題庫的全部數據都在新題庫以前,也可能一部分在新題庫以前。
因此這種方案則須要在新舊題庫中分別查詢offset+limit條數據。顯而易見,當offset太大的時候,會形成帶寬的極度浪費,網絡io擁堵,影響整個服務。分佈式

方案2前端傳來上一頁的時間

有沒有發現方案1中再查前10條的時候,其實只須要查20條,只有2倍的limit,由於limit不會太大,這沒有形成多少的額外損耗。爲何呢?
由於在查前10條的時候,知道一個額外條件,就是練習記錄時間小於當前時間。那在查詢11-20條記錄的時候,這個時間是多少呢? 優化

如上圖,假設在查前10條記錄的時候,這10條記錄由新題庫的前n條數據和舊題庫的前o條數據組成。新題庫第n條數據對應時間爲Tn,舊題庫第o條數據對應時間爲To。
那麼再查第11-20條數據的時候,是否是能夠新題庫中查詢時間小於Tn的前10條數據,在舊題庫中查詢時間小於To的前10條數據,而後對這兩份數據進行歸併排序,取出前10條。
因此只須要前端在查詢下一頁的時候,將上一頁的Tn,To帶過來。就能實現這樣方式了。設計

優缺點

優勢:
1.實現邏輯較爲簡單;
2.效率高;
缺點: 不能隨機訪問(例如從第一頁跳轉到第5頁),比較適合移動端的下拉加載。blog

方案3

這篇文章的題目爲特殊的分庫分表。正常的分庫分表遇到此類問題通常都是怎麼解決的呢?通常都是根據具體業務需求來分庫分表,讓數據分佈有特色。好比業務中的查詢條件都是查詢某個用戶的,則能夠經過userId的哈希值來進行分庫分表。查詢條件都是查詢某個月的,則能夠按照建立時間來分表。
那這種新老數據的分佈有什麼特色呢?總結下。排序

特色

1.最開始的數據都在老題庫中;
2.某些新題庫上線的考試,新記錄大部分在新題庫中,經過查詢數據庫,發現新舊比例約爲50:1;
3.用戶通常只作一個考試下的題目;
4.查詢條件通常都是限定了考試。索引

規律

能夠總結出以下規律:用戶的練習記錄分佈要考慮新題庫是否已經上線他考的這門考試。 若新題庫沒有上線這門考試,則題目所有在舊題庫中;
若新題庫上線了這們考試,則新題庫上線前用戶的練習記錄都在舊題庫中,上線後,絕大部分在新題庫。接口

分析

能夠看出難點在於新題庫上線了的考試,假設新題庫上線後,用戶作的都是新題庫的題目,是否是這個問題就簡單多了呢?
那若是回到現實,用戶在新題庫上線後,還會使用舊題庫,這時候主要是上圖中哪一步的難度加大了呢?能夠看到查詢新舊題庫的區間不會那麼容易肯定了。由於這兩者的前後關係不是那麼明確了。

新題庫數據充足的狀況

咱們先考慮第一種狀況,新題庫中的總量 >offset+limit。新題庫的第offset+limit條數據Data在全局排名是多少呢?設此條數據的時間爲T1,則須要查找舊題庫中時間大於T1的數量n。Data的全局排名則爲offset+limit+n。能夠看出最終結果就在新題庫的[0,offset+limit]和舊題庫的[0,n]中。
能不能再把這個區間縮小呢?

首先新題庫的offset+limit在全局排名是offset+limit+n,n>=0。右區間不能再精確了。而左區間呢?因爲右區間的排名在offset+limit+n,咱們要保證左區間的全局排名小於等於offset。往前找limit+n條,這條數據在全局排名是多少呢?設這條數據的時間爲T2,設T2到T1這個時間段有舊題庫的數據m個,那從上圖中能夠清晰的看到全局排名是offset+limit+n-(limit+n)-m,也就是offset-m。
能夠看到這個全局區間在[offset-m,offset+limit+n],包括了需求的[offset,offset+limit],咱們只須要在[offset-m,offset+limit+n]這個區間中找到[m,m+limit]即是最終結果。

流程圖

效率

能夠看到上面的方式實際上是適合全部狀況的。與以前總結的新舊題庫的數據分佈的特色沒有關係。其實這些特色主要是影響了效率。
效率主要體如今兩個方面,一個是帶寬(查了多少數據),一個是速度。

帶寬

先看看帶寬方面吧。原本咱們須要查詢limit條數據,如今變成了查新題庫limit+n條,舊題庫m條。
n是什麼呢?新題庫排名offset+limit的數據對應的時間爲T1,T1前舊題庫的數據個數。還記得咱們的數據分佈特色嗎?在新題庫上線後,新題庫的個數是舊題庫的50倍。按照這個倍數算的話,n約爲(offset+limit)/50,某些用戶的練習個數可以達到1萬,1萬/50,也就是200。
m只是n的一部分,更小了。

速度

新題庫首先查詢了一條數據,是根據時間進行排序的。舊題庫查詢了count,條件也包括時間。新題庫根據時間排序,查詢了區間內的數據。舊題庫根據時間查詢了數據。
能夠看到一共四次查詢。並且每次都是和時間有關。因此在必定要創建時間字段上的索引,或是以時間開頭的複合索引。

新題庫數據不足的狀況

接下來看下新題庫數據不足的狀況,這種狀況下,咱們仍是仿照上面,如今新題庫上面找。此次新題庫沒有offset+limit這麼多條數據了,那咱們就查找新題庫的最後一條數據Data,並查出新題庫中的數據總數count。

首先咱們是能夠獲得Data的全局排名的,只須要根據Data的時間,到舊題庫中查詢一下排名在他前面的個數。
Data的全局排名有三種狀況:
1.在M1中。這種狀況,最終結果中沒有任何新題庫的數據。這個時候能夠全部數據都在老題庫中取。區間改成[offset-count,offset+limit-count]。 2.在M3中。這種狀況,其實和上面討論了半天的新題庫數據充足的狀況是一致的。 3.在M2中。這種狀況,能夠把前面的數據按照M3的作法求出來,後面的數據按照M1de方法求出來,最後拼接一下。

優缺點

優勢: 1.在新舊這種模型下,可以支持全部的分頁需求。 2.效率較高 缺點: 1.只有在這種分佈特色下,效率纔會高。 2.實現較爲麻煩。

總結

在不支持隨機訪問的狀況下,方案2無疑是最佳選擇。 當數據有相似的分佈特色時,且須要支持隨機訪問,能夠選擇方案3。

相關文章
相關標籤/搜索