1. 程式人生 > >資料庫分庫分表存在的問題及解決方案

資料庫分庫分表存在的問題及解決方案

讀寫分離分散了資料庫讀寫操作的壓力,但是沒有分散儲存壓力,當資料庫的資料量達到千萬甚至上億條的時候,單臺數據庫伺服器的儲存能力就會達到瓶頸,主要體現在以下幾個方面:

  1. 資料量太大,讀寫效能會下降,即使有索引,索引也會變得很大,效能同樣會下降
  2. 資料檔案會變得很大,資料庫備份和恢復需要消耗更長的時間
  3. 資料檔案越大,極端情況下丟失資料的風險就會越高

基於上述原因,單個數據庫伺服器儲存的資料量不能太大,需要控制在一定的範圍內,為了滿足業務資料儲存的需求,需要將儲存分散到多臺資料庫伺服器上

常見的分散儲存的方法有分庫和分佈兩大類

  • 業務分庫  業務分庫之的是按照業務模組將資料分散到不同的資料庫伺服器,雖然業務分庫能夠分散儲存和訪問的壓力,但是同時也帶來了新的問題,主要存在的問題如下:
  1. join操作問題     業務分庫後,原本在同一個資料庫中的表分散到不同資料庫中,導致無法使用SQL中的join查詢
  2. 事務問題           原本在同一個資料庫中不同的表可以在同一個事物中修改,業務分庫後,表分散到不同的資料庫中,無法通過事務統一修改,雖然資料庫廠商針對此問題提供了一些分散式事務解決方案(例如,MySQL的XA),但是效能實在太低,與高性功能儲存的目標是相違背的
  3. 成本問題          業務分庫同時也帶來了成本的代價,本來1臺伺服器搞定的事情,現在需要3臺,如果考慮備份,那就是2臺變成了6臺

基於上述原因,對於初創業務,並不建議一開始就這樣拆分,主要有幾個原因:

  1. 初創業務存在很大的不確定性,業務不一定能發展起來,業務開始的時候並沒有真正的儲存和訪問壓力,業務分庫並不能為業務帶來價值
  2. 業務分庫後,表之間的join查詢,資料庫事務無法簡單實現了發
  3. 業務分庫後,因為不同的資料要讀寫不同的資料庫,程式碼需要增加根據資料型別對映到不同資料庫的邏輯,增加了工作量,而業務初創期最重要的是快速實現,快速驗證,業務分庫會拖慢業務節奏
  • 分表  將不同的業務資料分散儲存到不同的資料庫伺服器,能夠支撐百萬甚至千萬使用者規模的業務,但是如果業務繼續發展,同一個業務的單表資料也會達到單臺數據庫伺服器的處理瓶頸,此時就需要對單表進行拆分,單表資料拆分有兩種方式:垂直分表和水平分表

分表能夠有效的分散儲存壓力和帶來效能提升,但是和分庫一樣,也會引入各種複雜性,主要存在的問題如下:

  1. 垂直分表  垂直分表適合將表中某些不常用而且佔了大量空間的列拆分出去,垂直分表的引入的複雜性主要體現在表操作的數量會增加,例如原來只要一次查詢的就可以獲取,現在要查詢兩次或者多次才能獲得想要的資料
  2. 水平分表  水平分表適合錶行數特別大的表,如果單錶行數超過5000萬就必須進行分表,這個數字可以作為參考,但是並不是絕對的標準,關鍵還是要看錶的訪問效能

水平分表相比垂直分表,會引入更多的複雜性,主要表現在以下幾個方面:

  • 路由   水平分表後,某條資料具體屬於哪個切分後的表,需要增加路由演算法進行計算,這個演算法會引入一定的複雜性,常見的路由演算法有如下幾種:
  1. 範圍路由   選擇有序的資料列作為路由條件,不同分段分散到不同的資料庫表中,以常見的使用者ID為例,路由演算法可以按照10000的範圍大小進行分段 1-9999放到資料庫1中的表,10000-19999的資料放到資料庫2中的表,依次類推,範圍路由演算法的複雜性主要體現在分段大小的選取上,分段太小會導致切分後的子表資料量過多,增加維護複雜度;分段太大可能會導致單表依然存在效能問題,一般建議分段大學在100萬到200萬之間,具體要根據業務選擇合適的大小分段,路由演算法的優點就是可以隨著資料的增加可以平滑的擴充新的表,原有的資料不需要懂,範圍路由的一個比較隱含的缺點就是分佈不均勻 
  2. Hahs路由演算法  選擇某個列(或者某幾個列組合也可以)的進行Hash運算,然後根據Hash結果分散到不同的資料庫表中,同樣根據使用者ID為例,假如一開始就規劃10個數據庫表,路由演算法可以簡單的用user_id%10的值來表示資料所屬的資料庫表編號,ID為985的使用者放到編號為5的子表中,ID為10086的使用者放到編號為6的子表中;Hash 路由演算法設計的複雜點主要體現在初始表數量的選取上,表數量太多維護比較麻煩,表資料量太少又可能導致單表效能問題,而用了Hash路由後,增加表的數量非常麻煩,所有資料都要重新分佈,Hash路由演算法的優缺點和範圍路由基本相反,Hash路由演算法的優點是表分佈比較均勻,缺點是擴充新的表很麻煩,所有資料需要重新分佈
  3. 配置路由  配置路由就是路由表,用一張獨立的表來記錄路由資訊,同樣根據使用者ID為例,我們新增一張user_router表,這個表包含user_id和table_id兩列,根據user_id就可以查詢對應的table_id,配置路由設計簡單,使用起來非常靈活,尤其是在擴充表的時候,只需要遷移指定書,然後修改路由表就可以。配置路由的缺點就是必須多查詢一次,會影響整體的效能;而且路由表本身如果太大,效能同樣可能成為瓶頸,如果我們再次將路由表分庫分表,則面臨一個死迴圈式的路由演算法選擇問題

分表操作和分庫操作一樣,同樣會存在一些問題,主要體現在如下幾個方面:

  1. join操作           水平分表後,資料分散到多個表中,如果需要與其他表進行join 查詢,需要在業務程式碼或者資料庫中介軟體中進行多次join查詢,然後將結果合併
  2. count()操作     水平分表後,雖然物理上資料分散到多個表中,但是某些業務邏輯上還是會將這些表當作一個表進行處理,例如,獲取記錄總數用於分頁或展示,水平分表之前用一個count()就能完成的操作,在分表之後就沒有那麼簡單了,常見的處理方式有如下兩種:
  • count()相加   具體做法就是在業務程式碼或者資料庫中介軟體中對每個表進行count()操作,然後將結果相加,這種方式實現簡單,缺點就是效能比較低
  • 記錄數表       具體做法就是新建一張表,例如表名為:記錄數表,包含table_name,row_count兩個欄位,每次插入或刪除子表資料成功後,都更新記錄數表,這種方式獲取表記錄數的效能要大大優於count()相加方式,因為只需要一次簡單的查詢就可以獲得資料,缺點是複雜度增加不少,對子表的操作要同步操作記錄數表,如果一個業務邏輯遺漏了,資料就會不一致;而且針對記錄數表的操作和針對子表的操作無法放在同一個事物中進行處理,異常的情況會出現操作子表成功了而操作記錄數表示不,同樣導致資料不一致,同時,記錄數表的方式也增加了資料庫的寫壓力,因為每次針對子表的insert 和 delete操作需要update記錄數表,所以對於一些不要去記錄數實時保持精確的業務,也可以通過後臺定時更新記錄數表,定時更新實際上就是count()相加和記錄數表的結合,定時通過count()相加計算表的記錄數,然後更新記錄數表中的資料

    3 order by 操作     水平分表後,資料分散到多個子表中,排序操作無法在資料庫中完成,只能由業務程式碼或資料庫中介軟體分表查詢美國子表中的資料,然後彙總進行排序