前言
博主github
博主個人博客http://blog.healerjean.com
1、解釋
我們知道互聯網是由非常龐大的用戶組成,所以肯定有非常絕大的請求,這些請求又會產生非常巨大的信息存儲在數據庫中,由於數據量非常巨大,單個數據庫的表示很難容納所有數據,所以就有了分庫分表的需求。 對於數據的拆分主要有兩個方面 :垂直拆分和水平拆分
1.1、垂直拆分
垂直拆分: 根據業務的維度,將原本的一個庫(表)拆分爲多個庫(表〉,每個庫(表)
與原有的結構不同。
1.1.1、垂直分表
也就是“大表拆小表”,基於列字段進行的。一般是表中的字段較多,將不常用的, 數據較大,長度較長(比如text類型字段)的拆分到“擴展表“。 一般是針對那種幾百列的大表,也避免查詢時,數據量太大造成的“跨頁”問題。
1.1.2、垂直分庫
垂直分庫針對的是一個系統中的不同業務進行拆分,按照業務把不同的數據放到不同的庫中。其實在一個大型而且臃腫的數據庫中表和表之間的數據很多是沒有關係的,比如用戶User一個庫,商品Producet一個庫,訂單Order一個庫。 切分後,要放在多個服務器上,而不是一個服務器上
1.2、水平拆分
水平拆分: 根據分片(sharding )算法,將一個庫(表)拆分爲多個庫(表),每個庫(表)依舊保留原有的結構。
1.2.1、水平分表
針對數據量巨大的單張表(比如訂單表),按照某種規則(
Hash取模
、地理區域
、時間
等),切分到多張表裏面去。 但是這些表還是在同一個庫中
**結果:分表能解決數據量過大造成的查詢效率低下的問題 **
問題:但是無法有效解決數據的併發訪問能力。,所以庫級別的數據庫操作還是有IO瓶頸。不建議採用。
1.2.2、水平分庫+分表
將數據庫拆分,提高數據庫的寫入能力就是所謂的分庫。將單張表的數據切分到多個數據庫中,表的結構是一樣的 。
結果: 水平分庫分表能夠有效的緩解單機和單庫的性能瓶頸和壓力,突破IO、連接數、硬件資源等的瓶頸。
1.3、水平分庫分表的規則
路由:通過分庫分表規則查找到對應的表和庫的過程叫作路由。例如,分庫分表的規則是user_id % 4,當用戶新註冊了一個賬號時,假設用戶的ID是123,我們就可以通過123 % 4 = 3確定此賬號應該被保存在User3表中。當ID爲123的用戶登錄時,我們可通過123 % 4 = 3計算後,確定其被記錄在User3中。
1.3.1 、Hash取模
對hash結果取餘數 (hash() mod N):對機器編號從0到N-1,按照自定義的hash()算法,對每個請求的hash()值按N取模,得到餘數i,然後將請求分發到編號爲i的機器
使用場景:哈希分片常常應用於數據沒有時效性的情況
有一家公司在一年內能做10億條交易,假設每個數據庫分片能夠容納5000萬條數據,則至少需要20個表才能容納10億條交易。在路由時,我們根據交易ID進行哈希取模來找到數據屬於哪個分片,因此,在設計系統時要充分考慮如何設計數據庫的分庫分表的路由規則。
1.3.2 、地理區域
比如按照華東,華南,華北這樣來區分業務
使用場景:比如我們購買ECS服務器數據,以及阿里雲圖片服務器等。
1.3.3 、時間
按照時間切分,就是將6個月前,甚至一年前的數據切出去放到另外的一張表,因爲隨着時間流逝,這些表的數據 被查詢的概率變小,所以沒必要和“熱數據”放在一起,這個也是“冷熱數據分離”。
使用場景 :切片方式適用於有明顯時間特點的數據,
比如一個用戶的訂單交易數據,我們可以根據月或者季度進行切片,具體由交易數據量來決定以什麼樣的時間週期進行切割
2、分庫分表後的問題
2.1、事務
分庫分表後,就成了分佈式事務了。如果依賴數據庫本身的分佈式事務管理功能去執行事務,將付出高昂的性能代價; 如果由應用程序去協助控制,形成程序邏輯上的事務,又會造成編程方面的負擔。
2.2、SQl問題
2.2.1、多庫結果集合並(group by,order by),跨庫join
分庫分表後表之間的關聯操作將受到限制,我們無法join位於不同分庫的表,也無法join分表粒度不同的表, 結果原本一次查詢能夠完成的業務,可能需要多次查詢才能完成。
2.2.2、分頁問題
分庫後,有些分頁查詢需要遍歷所有庫。 舉個分頁的例子,比如要求按時間順序展示某個商家的訂單,每頁100條記錄,假設庫數量是8,我們來看下分頁處理邏輯:
1、全局視野法:
-
如果取第1頁數據,則需要從每個庫裏按時間順序取前100條記錄,8個庫彙總後有800條,然後對這800條記錄在應用裏進行二次排序,最後取前100條。
-
如果取第10頁數據,則需要從每個庫裏取前1000(100*10)條記錄,彙總後有8000條記錄,然後對這8000條記錄二次排序後取(900,1000)條記錄。
-
分庫情況下,對於第k頁記錄,每個庫要多取100*(k-1)條記錄,所有庫加起來,多取的記錄更多,所以越是靠後的分頁,系統要耗費更多內存和執行時間。
優點:對比沒分庫的情況,無論取那一頁,都只要從單個DB裏取100條記錄,而且無需在應用內部做二次排序,非常簡單。
缺點:每個分庫都需要返回更多的數據,增大網絡傳輸量;除了數據庫要按照time排序,服務層也需要二次排序,損耗性能;隨着頁碼的增大,性能極具下降,數據量和排序量都將大增
2、業務折中
禁止跳頁查詢,不提供“直接跳到指定頁面”的功能,只提供下一頁的功能。正常來講,不管哪一個分庫的第3頁都不一定有全局第3頁的所有數據,例如一下三種情況:
- 先找到上一頁的time的最大值(可從前臺傳入),作爲第二頁數據拉去的查詢條件,只取每頁的記錄數
- 這樣服務層還是獲得兩頁數據,再做一次排序,獲取一頁數據。
- 改進了不會因爲頁碼增大而導致數據的傳輸量和排序量增大
3、允許數據精度丟失:
需要考慮業務員上是否接受在頁碼較大是返回的數據不是精準的數據。
- 在數據量較大,且ID映射分佈足夠隨機的話,應該是滿足等概率分佈的情況的,所以取一頁的數據,我們在每個數據庫中取(每頁數據/數據庫數量)個數據。
- 當然這樣的到的結果並不是精準的,但是當實際業務可以接受的話, 此時的技術方案的複雜度變大大降低。也不需要服務層內存排序了。
4、二次查詢法
2 個數據庫,假設一頁只有5條數據,查詢第200頁的SQL語句爲
select * from T order by time limit 1000 5;
- 講sql改寫爲
select * from T order by time limit 500 5;
注意這裏的500=1000/分表數量,並將這個sql下發至每個分庫分表中執行,每個分庫返回這個sql執行的結果。
- 找到所有分庫返回結果的time的最小值
第一個庫,5條數據的time最小值是1487501123
第二個庫,5條數據的time最小值是1487501223
故,三頁數據中,time最小值來自第一個庫,time_min=1487501123,這個過程只需要比較各個分庫第一條數據,時間複雜度很低
- 查詢二次改寫,第二次要改寫成一個between語句,between的起點是time_min,between的終點是原來每個分庫各自返回數據的最大值:
第一個分庫,第一次返回數據的最大值是1487501523
所以查詢改寫爲select * from T order by time where time between time_min and 1487501523
第二個分庫,第一次返回數據的最大值是1487501699
所以查詢改寫爲select * from T order by time where time between time_min and 1487501699
從上面圖片可以看出,DB1比第一次查出來的數據多了兩行,應爲查詢的範圍擴大了
- 計算time_min這條記錄在全局的偏移量
從而我們得知time_min這條記錄在全局的偏移量值=500+497=997,其實也就是說,我們的第1000條記錄的終點是time=1487501128
- 獲取最終結果,講第二次查詢出的進行排序,最終獲得結果
**優點:可以精確的返回業務所需數據,每次返回的數據量都非常小,不會隨着翻頁增加數據的返回量。 **
缺點:需要進行兩次數據庫查詢
2.2.3、Join問題
互聯網公司的業務,往往是併發場景多,DB查詢頻繁,有一定用戶規模後,往往要做分庫分表。 分庫分表Join肯定是不行的
2.2.3.1、不使用join的原因:
join
的話,是走嵌套查詢的。小表驅動大表,且通過索引字段進行關聯。如果表記錄比較少的話,還是OK的當表處於百萬級別後,join導致性能下降;- 分佈式的分庫分表。這種時候是不建議跨庫join的。目前mysql的分佈式中間件,跨庫join表現不良。
join
寫的sql語句要修改,不容易發現,成本比較大,當系統比較大時,不好維護。- 數據庫是最底層的,一個系統性能好壞的瓶頸往往是數據庫。建議數據庫只是作爲數據存儲的工具,而不要添加業務上去。
2.2.3.2、不使用join的解決方法:
應用層面解決 :可以更容易對數據進行分庫,更容易做到高性能和可擴展。(記得在小米金融供應鏈關聯查詢賣方,賣方 核心企業,授信企業的使用,就是這樣,本來其實是兩個企業,但是卻有4個子段表示join查詢肯定是不好的)
-
緩存的效率更高。許多應用程序可以方便地緩存單表查詢對應的結果對象。,如果某個表很少改變,那麼基於該表的查詢就可以重複利用查詢緩存結果了。單表查詢出數據後,作爲條件給下一個單表查詢
-
查詢本身效率也可能會有所提升。查詢id集的時候,使用IN()代替關聯查詢,可以讓MySQL按照ID順序進行查詢,這可能比隨機的關聯要更高效。mysql對in的數量沒有限制,mysql限制整條sql語句的大小。通過調整參數max_allowed_packet ,可以修改一條sql的最大值。建議在業務上做好處理,限制一次查詢出來的結果集是能接受的,但是最好不要超過500條(小米規範)
-
可以減少多次重複查詢。在應用層做關聯查詢,意味着對於某條記錄應用只需要查詢一次,而在數據庫中做關聯查詢,則可能需要重複地訪問一部分數據。從這點看,這樣的重構還可能會減少網絡和內存的消豔。