高性能數據庫集群-分庫分表

不過相比接下來要講的水平分表,這個複雜性就是小巫見大巫了。

2. 水平分表

水平分表適合錶行數特別大的表,有的公司要求單錶行數超過 5000 萬就必須進行分表,這個數字可以作為參考,但並不是絕對標準,關鍵還是要看錶的訪問性能。對於一些比較複雜的表,可能超過 1000 萬就要分表了;而對於一些簡單的表,即使存儲數據超過 1 億行,也可以不分表。但不管怎樣,當看到表的數據量達到千萬級別時,作為架構師就要警覺起來,因為這很可能是架構的性能瓶頸或者隱患。

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

 路由

水平分表後,某條數據具體屬於哪個切分後的子表,需要增加路由算法進行計算,這個算法會引入一定的複雜性。

常見的路由算法有:

範圍路由:選取有序的數據列(例如,整形、時間戳等)作為路由的條件,不同分段分散到不同的數據庫表中。以最常見的用戶 ID 為例,路由算法可以按照 1000000 的範圍大小進行分段,1 ~ 999999 放到數據庫 1 的表中,

1000000 ~ 1999999 放到數據庫 2 的表中,以此類推。範圍路由設計的複雜點主要體現在分段大小的選取上,分段太小會導致切分後

子表數量過多,增加維護複雜度;分段太大可能會導致單表依然存在性能問題,一般建議分段大小在 100 萬至 2000 萬之間,具體需要根據業務選取合適的分段大小。

範圍路由的優點是可以隨著數據的增加平滑地擴充新的表。例如,現在的用戶是 100 萬,如果增加到 1000 萬,只需要增加新的表就可以了,原有的數據不需要動。

範圍路由的一個比較隱含的缺點是分佈不均勻,假如按照 1000 萬來進行分

表,有可能某個分段實際存儲的數據量只有 1000 條,而另外一個分段實際存儲的數據量有 900 萬條。

Hash 路由:選取某個列(或者某幾個列組合也可以)的值進行 Hash 運算,然後根據 Hash 結果分散到不同的數據庫表中。同樣以用戶 ID 為例,假如我們一開始就規劃了 10 個數據庫表,路由算法可以簡單地用 user_id % 10 的值來表示數據所屬的數據庫表編號,ID 為 985 的用戶放到編號為 5 的子表中,ID 為 10086 的用戶放到編號為 6 的字表中。

Hash 路由設計的複雜點主要體現在初始表數量的選取上,表數量太多維護比較麻煩,表數量太少又可能導致單表性能存在問題。而用了 Hash 路由後,增加字表數量是非常麻煩的,所有數據都要重分佈。

Hash 路由的優缺點和範圍路由基本相反,Hash 路由的優點是表分佈比較均勻,缺點是擴充新的表很麻煩,所有數據都要重分佈。

配置路由:配置路由就是路由表,用一張獨立的表來記錄路由信息。同樣以用

戶 ID 為例,我們新增一張 user_router 表,這個表包含 user_id 和 table_id 兩列,根據 user_id 就可以查詢對應的 table_id。

配置路由設計簡單,使用起來非常靈活,尤其是在擴充表的時候,只需要遷移指定的數據,然後修改路由表就可以了。配置路由的缺點就是必須多查詢一次,會影響整體性能;而且路由表本身如果太大(例如,幾億條數據),性能同樣可能成為瓶頸,如果我們再次將路由表分庫分表,則又面臨一個死循環式的路由算法選擇問題。

• join 操作

水平分表後,數據分散在多個表中,如果需要與其他表進行 join 查詢,需要在業務代碼或者數據庫中間件中進行多次 join 查詢,然後將結果合併。

• count() 操作

水平分表後,雖然物理上數據分散到多個表中,但某些業務邏輯上還是會將這些表當作一個表來處理。例如,獲取記錄總數用於分頁或者展示,水平分表前用一個 count() 就能完成的操作,在分表後就沒那麼簡單了。常見的處理方式有下面兩種:

count() 相加:具體做法是在業務代碼或者數據庫中間件中對每個表進行

count() 操作,然後將結果相加。這種方式實現簡單,缺點就是性能比較低。例如,水平分表後切分為 20 張表,則要進行 20 次 count(*) 操作,如果串行的話,可能需要幾秒鐘才能得到結果。

記錄數表:具體做法是新建一張表,假如表名為“記錄數表”,包含

table_name、row_count 兩個字段,每次插入或者刪除子表數據成功後,都更新“記錄數表”。

這種方式獲取表記錄數的性能要大大優於 count() 相加的方式,因為只需要一次簡單查詢就可以獲取數據。缺點是複雜度增加不少,對子表的操作要同步操作“記錄數表”,如果有一個業務邏輯遺漏了,數據就會不一致;且針對“記錄數表”的操作和針對子表的操作無法放在同一事務中進行處理,異常的情況下會出現操作子表成功了而操作記錄數表失敗,同樣會導致數據不一致。

此外,記錄數表的方式也增加了數據庫的寫壓力,因為每次針對子表的 insert 和 delete 操作都要 update 記錄數表,所以對於一些不要求記錄數實時保持精確的業務,也可以通過後臺定時更新記錄數表。定時更新實際上就是“count() 相加”和“記錄數表”的結合,即定時通過 count() 相加計算表的記錄數,然後更新記錄數表中的數據。

 order by 操作

水平分表後,數據分散到多個子表中,排序操作無法在數據庫中完成,只能由業務代碼或者數據庫中間件分別查詢每個子表中的數據,然後彙總進行排序。

實現方法

和數據庫讀寫分離類似,分庫分表具體的實現方式也是“程序代碼封裝”和“中間件封裝”,但實現會更復雜。讀寫分離實現時只要識別 SQL 操作是讀操作還是寫操作,通過簡單的判斷 SELECT、UPDATE、INSERT、DELETE 幾個關鍵字就可以做到,而分庫分表的實現除了要判斷操作類型外,還要判斷 SQL 中具體需要操作的表、操作函數(例如 count 函數)、order by、group by 操作等,然後再根據不同的操作進行不同的處理。例如 order by 操作,需要先從多個庫查詢到各個庫的數據,然後再重新 order by 才能得到最終的結果。

小結

今天我為你講了高性能數據庫集群的分庫分表架構,包括業務分庫產生的問題和分表的兩種方式及其帶來的複雜度,希望對你有所幫助。

這就是今天的全部內容,留一道思考題給你吧,你認為什麼時候引入分庫分表是合適的?是數據庫性能不夠的時候就開始分庫分表麼?

歡迎你把答案寫到留言區,和我一起討論。相信經過深度思考的回答,也會讓

高性能數據庫集群-分庫分表


分享到:


相關文章: