淺談數據庫的水平拆分

轉自: http://dryr.blog.163.com/blog/static/582110132010422115946413/
一般人們分析問題,總是從問題現象,原因分析,解決方案這樣的思路來分析思考問題,我想對這個數據庫的水平拆分也按這樣的思路來簡單剖析一下。
先從問題現象入手,隨着數據庫表中數據日積月累越來越多,當表記錄數達到千萬甚至億級別時,數據庫表的訪問效率下降明顯,導致外層應用的訪問效率非常差,訪問時間急劇上升,用戶體驗下降。如果是表數據太大的原因導致訪問速度變慢,一般情況下當訪問與此表相關業務時速度會很慢,而訪問與此表無關的業務時速度會很快。
分析上面的問題現象,明顯的一個原因是因爲某些表的數據記錄太多的原因,導致數據庫訪問效率下降造成的。
既然是某些表數據記錄太多的原因,那我們的解決辦法當然是讓這些表的數據記錄減少到不影響訪問效率爲止,同時爲了考慮以後這些數據還是會不斷的增長,爲了讓這些數據增長後還是可以擴展,那需要考慮如何可以將這些數據無限制的水平拆分,而不需要修改上層應用,一般來說只要設計得當,從理論上講水平拆分都是可以無限擴展的。
那我們先把記錄數太多的表分成多張表,這時問題來了。
1、對記錄數多的表我們進行拆分,那對與之相關聯的一些表該怎麼辦?這個問題其實也是現實開發中比較普遍的一個問題,現在數據庫表一般都會與其他表有關聯的。有人會提出一個方法就是所有的表都不與其他表關聯,至少在SQL執行層面上如此,這樣不就解決了數據或業務關聯問題了啊,但這裏有個問題那就是如果都按照這樣在SQL層面完全解耦,而在應用層面再關聯的話,會導致數據庫訪問次數增加很多,而且網絡傳輸數據增加,比如A表和B表是關聯表,如果在SQL層面關聯,則只執行一個SQL;如果在SQL層面獨立,則需要執行兩個SQL,分別查詢出A表數據和B表數據,因爲沒有條件關聯過濾,則數據肯定比執行關聯SQL多很多,然後再在應用層進行關聯。所以我個人覺得對性能要求高的系統中,還是需要使用SQL層面的關聯的,但這裏有一個原則肯定是要遵守,那就是不能讓多個需要拆分表關聯,因爲這會導致拆分標準不一致而導致無法拆分。對關聯SQL中的一個表需要拆分,其他都是相對靜態的無需拆分的表,這種情況下的解決思路是將需拆分表拆分到多個庫中,而靜態表則同步到各個拆分庫中。這裏再上升一下,分析一下系統的表結構中,一般會分動態表(數據變化很大,數據量也可能很大的表)和靜態表(數據變化很小的表,一般來說都是基礎表,數據量也不會很大),將基礎的靜態表都放到一個公共庫中,將動態表根據標準分拆到分庫中,拆分完成後基礎數據都在公共庫維護,並同步到分庫中,在分庫中維護動態表,同時在查詢時動態表可以與分庫中靜態表關聯查詢,這樣就解決了這個問題。
2、數據庫表拆分的標準又是什麼,按照什麼來拆分?一般來說這個拆分標準可以按照數據範圍分,比如1-100萬一個表,100萬-200萬又是一個表;也可以按照時間順序來拆分,比如一年的數據歸到一張表中等;也可以按照地域範圍來分,比如按照地市來分,每個或多個地市一個庫等,反正這個個人覺得是按照具體的情況來分的,一般情況下,對帶有較濃的分割標誌的數據庫表,可以根據分割標誌來分割,對沒有較濃分割標誌的數據庫表,則只能按照最笨的方法如數據範圍來拆分了,有時候爲了增加拆分質量,還可以先根據一個分割標誌來分表,在根據另一個分割標誌來分區等複合式的拆分方式來水平拆分數據庫表。
3、數據庫表水平拆分後,訪問數據庫表的SQL必須要帶上分割標誌來確定目標數據庫表,如果要對多個拆分數據庫表進行查詢,則需要通過多次訪問數據庫表來完成,同時在應用層面將數據合併來做到。但有時候,一般需要可以通過至少兩種方式(或分割標誌)來獲取目標數據庫表。舉個例子,大型網絡遊戲中因爲玩家太多了(比如達到幾千萬甚至億級別時),所以將玩家的用戶信息分庫分表,當用戶登錄時,現在一般的做法都是會讓用戶自己選擇是哪一區的,根據這個選擇來確定目標數據庫表,但如果我們改一下,用戶不知道自己是哪一區的,只知道自己的用戶編號,輸入用戶編號後需要由系統自動根據用戶編號來路由到目標數據庫表。在這種情況下,個人覺得需要有一個規則來保證用戶編號的規律性,比如可以在用戶申請時,針對選擇的不同區來生成不同的用戶編號,比如1區是aaa+8位的順序編號,2區是bbb+8位的順序編號,這樣的話,對aaa,bbb之類的分類編號是可以通過數據庫表來管理的,比如用戶編號是aaa開頭和abc開頭的都是1區的用戶這樣的規則就可以管理起來,這樣當用戶輸入用戶編號時,系統通過截取用戶編號前三位,併到數據庫表中查詢出這前三位對應的哪個區,這樣就可以獲得這個用戶的目標數據庫表了。等到查詢出這個用戶信息後,這個用戶信息中必定會存在分割標誌信息的(這個例子中就是屬於哪個區的),對這個用戶信息緩存,就不再需要使用之前那種方式來確定目標數據庫表了,而只需要根據緩存的用戶信息中的屬於哪個區的信息就可以來確定目標數據庫表了。分析這兩種確定目標數據庫表的方式,一般來說前一種方式比較複雜,性能上消耗也較多,這種方式只有在第二種方式無法判斷的情況下使用,所以使用頻率相對來說非常低,而第二種方式則相對簡單,而且性能也很好,這種方式是默認使用方式,使用頻率相對來說非常高,但有時候因爲信息不全無法使用第二種方式,所以必須要有前一種方式來補充使用。
4、數據庫水平拆分成多個庫時,這時有一個事情是必須會碰到的,那就是數據庫的連接。一般來說,應用服務器或應用系統對數據庫連接的管理一般會通過連接池來管理,這樣可以大大提高效率,而不會使用動態連接。這裏就出來一個問題,當一個數據庫水平拆分成多個數據庫時,必然數據庫連接池也會增加到多個,在一定範圍內應該是不會有問題的,但畢竟應用服務器的性能也是有上限的,當數據庫水平拆分成N個數據庫時,應用服務器的性能就會吃不消了,這時候需要對應用服務器進行擴展了,比如一臺應用服務器對應幾個數據庫連接等,當然這是比較深入的事情了,這裏只把問題拋出來,不再多說。
5、說到這裏要說一下應用設計開發上的問題,首先是對目標數據庫表路由模塊必須要獨立,如果路由不獨立出來,那以後萬一路由策略變更的話會死的很慘,而且路由算法是一個典型的策略模式應用,最好能實現成策略模式,以方便以後路由策略變更時應用可以無縫的切換路由策略。其次是對開發來說,最好能使用ibatis之類的持久層,既有一定的封裝,也可以將SQL獨立配置,這樣在開發人員開發時,可以靈活的寫SQL來實現邏輯,也可以對SQL語句進行管理,同時DBA可以很方便的對這些SQL進行專業的優化,而與應用開發無關。現在一些先行者已經在努力實現將數據庫拆分的影響封裝在代理中的項目,比如變形蟲項目,這些項目的出現將會使數據庫拆分後對應用開發的影響越來越小。最後一個是事務,如果可以接受分佈式事務的性能那當然是最好的;如果不能接受,那一般的做法就是事務補償(指在同一業務操作中當事務A提交,但事務B發生錯誤回滾後,爲保持操作一致性和數據正確性,必須要做事務A操作的反操作來補償事務A的提交,消除事務A的提交對數據結果的影響),但事務補償會增加開發的工作量等問題;或者不是非常重要的業務操作時,通過保證事務B執行的成功率(比如先進行查詢或預執行操作),從而使事務B的失敗率下降到可以忽略的程度,從而可以不考慮事務的問題。
6、還有一個非常重要的需要說一下,一般來說很多都是對原有系統的改造,這樣的話就必然會有需要對原有數據的處理割接,這塊工作也是非常重要的,數據庫拆分方案做得最好,如果原有數據不能無縫的割接到新的拆分後的數據庫中的話,那都是白搭。另外還有業務層面的問題,比如數據庫表拆分引起的業務流程更改,業務操作習慣更改等方面的問題也要提早考慮和解決。
總之,數據庫表水平拆分是非常複雜的,需要綜合各個方面考慮完善,套用網友cauherk的說法“系統的切分是個很複雜的技術活,要綜合考慮,而不僅僅從數據庫層面考慮。業務的使用、分庫的原則、數據的割接、開發的侵入、可操作的易難程度、後期的管理等等都是需要考慮的因素。”。

    參考資料:http://www.javaeye.com/topic/409294
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章