分庫分表實踐
1. 爲什麼要分庫分表?
在日常開發中,mysql中的表有大有小,通常msyql開發手冊會提示我們每張表不易存放太多數據,否則會造成單表IO劇增,導致併發量急劇下降,至於大表的查詢性能會降低到什麼程度,大家沒有一個明顯的認知,下面我拿實際場景中的一個大表爲大家解釋,這張表目前存儲了大概4000W條數據:
-- 後臺運營管理系統統計場景
select * from t_order order by Id asc limit 10,20; -- 0.028s
select * from t_order order by Id asc limit 100,20; -- 0.025s
select * from t_order order by Id asc limit 1000,20; -- 0.029s
select * from t_order order by Id asc limit 10000,20; -- 0.035s
select * from t_order order by Id asc limit 100000,20; -- 0.135s
select * from t_order order by Id asc limit 1000000,20; -- 1.154s
select * from t_order order by Id asc limit 10000000,20; -- 28.697s
-- 最後一次查詢比第一次查詢慢了1025倍
-- 用戶分頁查詢我的訂單場景
select * from t_order where UserId=1811091645161408 limit 1,20 ; -- 0.028s
select * from t_order where UserId=1811091645161408 limit 10,20 ; -- 0.025s
select * from t_order where UserId=1811091645161408 limit 100,20 ; -- 0.025s
select * from t_order where UserId=1811091645161408 limit 10000,20 ; -- 0.095s
select * from t_order where UserId=1811091645161408 limit 100000,20 ; -- 0.32s
select * from t_order where UserId=1811091645161408 limit 180000,20 ; -- 0.509s
-- 最後一次查詢比第一次查詢慢了18倍
可以看出隨着分頁頁數的增大,數據的查詢效率急劇下降,最後會導致表的併發路數被這些慢查詢佔滿。
數據庫的DQL/DML的效率下降有幾種可能:
-
數據庫服務器配置
如CPU或磁盤配置也會影響數據庫的性能。
-
msyql自身併發配置
mysql自身併發配置也會影響數據庫的性能。
-
表設計不合理
某些情況下,不合理的表結構會直接造成大量的連表查詢或其他的一些不合理的操作,也會影響數據庫的性能。
-
帶寬
服務器帶寬也會影響數據庫的性能。
-
大型事務操作
一些大型的事務,如批量DML、存儲過程、視圖等也會影響數據庫的性能。
-
B+樹存儲制約
表中的數據量過大,導致B+樹深度增加,也會一定程度上影響數據庫的性能。
以上幾種情況都會影響數據庫的性能,當數據庫存在性能問題時大家可以從以上方面進行診斷,今天主要講述B+樹存儲制約的解決方式。
我們知道B+樹的特性,mysql的索引是按照B+樹來存儲的,我們可以通過一些公式簡單的估算出我們的表的B+樹的深度,計算mysql索引B+樹深度方法如下:
- 假設主鍵ID爲bigint類型,佔8字節
- B+樹每層默認存儲大小16K
- B+樹葉子節點存儲指針大小默認6字節
- 計算我們業務表每條數據的平均大小,我舉例的業務表爲593B/條
則計算方法如:
則B+樹每層能存儲主鍵索引容量爲:
(16K*1024)/(8+6) = 1170
葉子層能存儲真實數據條數爲:
(16K*1024)/593 ≈ 28
則可以算出,一個3層的B+樹在這個表中的極限容量爲:
1170*1170*28 = 38329200條
如果這張表數據量已經超過38329200條,則這張表的B+樹深度爲4,B+樹增加一層索引,導致索引查詢時速度變慢。
我們應該怎麼優化呢?
2. 分庫分表的幾種常見策略
單表或單庫數據量過大,可以從這幾個維度來優化:
-
垂直分庫
將一張大數據庫實例垂直切分,分爲若干個較小的數據庫實例,降低單數據庫的數據量,從而提高單數據庫的性能。
-
水平分庫
將一個大表按照一定的規則拆分成若干個小表,每個小表放在不同的數據庫中,以水平切分方式拆分大表,降低但庫數據量,提高單庫性能。
-
垂直分表
將一張字段比較多的寬表垂直切分爲若干個字段比較少的小表,每個小表存儲大表的一部分字段,避免單表IO爭搶並減少鎖表機率,提高單表性能。
-
水平分表
在同一個數據庫中,按照一定規則拆分大表爲若干小表,降低單表的數據量,提高單表性能。
其實拆分策略主要體現在垂直和水平上面:
垂直拆分:就如一個大的西瓜,只能一個人去喫,累死累活也吃不了,把西瓜垂直切分爲幾小塊,每一小塊安排一個人去喫,垂直拆分造成的結果就是每個表裏面都不是一個訂單完整的屬性,要想查出訂單所有屬性,唯有每個表都查出來並拼接:
水平拆分:水平拆分則是把一個大的西瓜置換爲幾個小的西瓜,每個西瓜都是完整的,但是不是全部的數據。
我們在做分庫分表實踐時一定要搞清楚每一種拆分的特點和產生後果,一定要切合業務來選擇拆分方式:
-
垂直分庫
垂直分庫帶來的提升:
-
按照業務領域模型分庫,解決業務耦合,爲服務拆分和微服務的無狀態設計提供領域模型方面的支撐。
-
不同業務的數據庫分而治之,可按需爲不同的數據庫提供不同的管理、維護、監控、擴展等策略。
-
高併發場景下,垂直分庫並部署在不同的服務器上,可有效提升單服務器IO、併發量,降低單機硬件資源的瓶頸。
-
降低部分表異常對其他無關表的影響。
垂直分庫一般方式:
-
按照獨立產品維度拆分大庫。
-
特定大數據量的業務場景,獨立拆分。
-
-
水平分庫
水平分庫就是把同一張表中的數據,按照一定的規則拆分到不同的數據庫中,每個庫存放一部分數據,理論上我們把表分成幾分,單庫的數據量就少幾倍,這種水平分庫的方式也可以大幅度提升數據庫的性能。
但是水平分庫比垂直分庫要複雜的多,它改變了數據的存放位置,意味着編碼、運維、數據管理都要隨之改變,
並且第一次水平分庫也在一定程度上直接影響數據庫未來的可擴展性和可用性,當然,水平分庫也有一定的套路,找出數據的特點,才能正確的進行水平分庫,我用這幾個場景來說明水平分庫的方法。
-
垂直分表
垂直分表的定義是:將一張大表按照字段分成多表,每個表存儲其中一部分數據。
垂直分表的原則是:
-
按照訪問熱度把常用字段和不常用字段分開。
-
儘量確保常見查詢場景能一次DQL搞定。
-
把text/blob等大字段拆分出來單獨建立附表。
-
-
水平分表
垂直分表的定義:在同一個數據庫內,將一張大表的數據按照一定規則拆分到多個表中,以降低單個表的數據量。
垂直分表的提升:
-
優化單表數據量過大而產生的性能問題。
-
避免IO爭搶並減少鎖表的機率。
垂直分表的幾種常見策略:
-
按照時間範圍。
-
按照ID區間。
-
按照ID哈希。
-
3. 一次分庫分表的落地實踐
-
大表分析
這次分庫分表目標表如下:
項 描述 表名 t_order 總數量 4500W 年增常量 2100W 日增常量 57000 3年後預計總數據量 1億條以上 數據特點 1.C端業務中絕大部分是用戶查詢自己的訂單信息,不會跨用戶查詢,也很少多用戶同時查詢<br>2.個體用戶數據量差異較小,數據分佈均勻<br>3.用戶會經常查詢歷史訂單 拆分前提 1.原則上每張表數據量控制在1000W*80% = 800W條以內<br>2.考慮分片必須滿足未來3~5年的業務需求<br>3.分表要考慮後續擴容和儘量業務無感知 拆分依據 1.總體方案:按照UserId垂直分表<br>2.補充方案:讀寫分離 分片建議 表中添加列UserId,按照UserId 32分片 -
分表過程步驟設計
-
階段一:(遷移前)優化t_order表以支持分表
-
階段二:(遷移中)表分片,數據遷移,優化程序代碼
-
優化程序代碼,添加分表操作邏輯,引入Sharding-JDBC分片中間件。
-
現網數據庫32分片,上線數據遷移工具,不停機遷移存量數據。
-
上線遷移工具,停機遷移增量數據,並同時上線新服務,下線老服務。
-
-
階段三:(遷移後)迴歸測試
-
迴歸測試。
-
下線舊錶。
-
-
異常及回滾
-
存量數據遷移過程中異常:停止遷移,還原現場,刪除新表,記錄異常堆棧,等待下次遷移。
-
增量數據遷移過程中異常:停止遷移,刪除已遷移的增量數據,記錄異常堆棧,等待下次增量遷移。
-
數據遷移完成後線上歸回異常:運行數據回遷工具,從新表正式上線開始,回遷新表新數據到老表中,服務回滾。
-
-
-
所使用到的中間件或組件
項 描述 數據遷移組件 1.pub/sub方式<br>2.一個生產者時序讀取AudioInfo數據<br>3.多個消費者併發消費數據,哈希並存入對應分片 數據回遷組件 1.pub/sub方式<br>2.一個生產者時序讀取t_order分片表中新產生的數據<br>3.一個消費者消費數據,存入t_order中 數據校驗組件 按照t_order.update_time>存量數據遷移完成時間,校驗新表和老表數據是否一致,不一致則修改新表數據 Sharding-JDBC 開源,支持java服務DML分片數據庫 Sharding-JDBC集成方案如下:
1. 項目引入JDBC組件 <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>4.1.1</version> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-core-common</artifactId> <version>4.1.1</version> </dependency> 2. 配置分表策略 spring.shardingsphere.props.sql.show=true # 配置數據源 spring.shardingsphere.datasource.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.shardingsphere.datasource.url=jdbc:mysql://127.0.0.1:3306/yc_test spring.shardingsphere.datasource.username=root spring.shardingsphere.datasource.password=123456 # 配置具體表的分表策略,舉例爲按UserId字段取模4分表 spring.shardingsphere.sharding.tables.ZmOrder.actual-data-nodes=master0.ZmOrder$->{0..3} spring.shardingsphere.sharding.tables.ZmOrder.table-strategy.inline.sharding-column=UserId spring.shardingsphere.sharding.tables.ZmOrder.table-strategy.inline.algorithm-expression=ZmOrder$->{UserId % 4}
4. 分庫分錶帶來的問題和解決思路
分庫分表之後,大表DQL和DML性能大幅度提升,滿足了現有業務持續增長的需求,但是分庫分表也引入了新的複雜度和新的問題:
-
跨庫join
跨庫join可以從兩個方面入手解決:
業務層面:
-
分析業務領域模型,識別主要模型,如ToC產品的用戶、ToB產品的組織等,根據主要模型ID(UserId/GroupId)拆分數據。
-
儘量避免另類的頁面設計,如用戶頁面只顯示用戶自己的數據,避免多用戶數據合併顯示等。
技術層面:
-
拒絕連表查詢,打散大join語句爲小的單表查詢,在代碼裏按需聚合數據,用多次小select代替一次大select。
-
增加數據庫字段冗餘,代替連表查詢,以空間換性能。
-
冗餘通用小表,如配置表、字典表等全量冗餘到每個數據庫中,保證程序儘量不跨庫查詢,以空間換性能。
-
-
多庫數據一致性
多庫數據一致性可以使用分佈式事務來解決。
-
排序/分頁查詢
排序/分頁查詢問題需要改造現有功能,條件添加分片鍵。
-
多表主鍵衝突:分佈式ID
-
全局數據統計