一.分庫分表原因
前文介紹MySQL主從模式,將讀寫分離以提高性能。
主從模式對於寫少讀多的場景確實非常大的優勢,但是總會寫操作達到瓶頸的時候,導致性能提不上去。
總的來說就是數據庫出現性能瓶頸,對外表現有幾個方面:
- 大量請求阻塞:
在高並發場景下,大量請求都需要操作數據庫,導致連接數不夠了,請求處於阻塞狀態。
- SQL 操作變慢:
如果數據庫中存在一張上億數據量的表,一條 SQL 沒有命中索引會全表掃描,這個查詢耗時會非常久。
- 存儲出現問題:
業務量劇增,單庫數據量越來越大,給存儲造成巨大壓力。
如果系統處於高速發展階段,拿商城系統來說,一天下單量可能幾十萬,那數據庫中的訂單表增長就特別快,增長到一定階段數據庫查詢效率就會出現明顯下降。
這時候可以在設計上進行解決:
- 采用分庫分表的形式,對於業務數據比較大的數據庫可以采用分表,使得數據表的存儲的數據量達到一個合理的狀態。
- 也可以采用分庫,按照業務進行划分,這樣對於單點的寫,就會分成多點的寫,性能方面也就會大大提高。
分庫分表方案更多的是對關系型數據庫數據存儲和訪問機制的一種補充,而不是顛覆。
二.分庫分表拆分思路
1.什么時候進行分庫
MySQL 的高可用架構大多都是一主多從,所有寫入操作都發生在 Master 上,隨着業務的增長,數據量的增加,很多接口響應時間變得很長,經常出現 Timeout,而且通過升級 MySQL 實例配置已經無法解決問題了,這時候就要分庫。
2.什么時候進行分表
分表的應用場景是單表數據量增長速度過快,影響了業務接口的響應時間,但是 MySQL 實例的負載並不高,這時候只需要分表,不需要分庫(拆分實例)。
三.垂直拆分
垂直分庫

垂直分庫是按業務分庫,例如一個電商系統shop庫按業務分有訂單表,會員表,商品表,按業務拆分后,響應的shop庫被拆分到三個RDS實例中,數據庫寫入能力提升,服務的接口響應時間變短,提供穩定性。
垂直分表

例如登錄系統只需要userid,username和password,如果不分表,則每次登錄都需要把整張user表加載進內存進行判斷,sex,address,age和nick_name這些無用到的字段也會占內存。
垂直拆分特點
基於表或字段划分,表結構不同
垂直拆分優點
- 拆分后業務清晰,方便針對業務進行優化(專庫專用按業務拆分);
- 數據維護簡單,按業務不同將業務放到不同機器上。
垂直拆分缺點
跨庫關聯查詢
在單庫未拆分表之前,我們可以很方便使用 join 操作關聯多張表查詢數據,但是經過分庫分表后兩張表可能都不在一個數據庫中,如何使用 join 呢?
有幾種方案可以解決:
- 字段冗余:把需要關聯的字段放入主表中,避免 join 操作;
- 數據抽象:通過ETL等將數據匯合聚集,生成新的表;
- 全局表:比如一些基礎表可以在每個數據庫中都放一份;
- 應用層組裝:將基礎數據查出來,通過應用程序計算組裝;
四.水平拆分
業務量比較大的時候,即使做了垂直拆分,依然會存在以下問題:
- 如果單表的數據量大,讀寫壓力依然很大;
- 受某種業務來決定,或者被限制。 也就是說一個業務往往會影響到數據庫的瓶頸(性能問題)。例如電商系統訂單庫的讀寫會遠遠大於其他功能;
水平分庫

根據一定的邏輯,例如將userid取模,將數據放到不同的庫上。
舉個例子,交易數據庫的訂單表 orders 有2億多數據,RDS 實例遇到了寫入瓶頸,普通的 insert 都需要50ms,時常也會收到 CPU 使用率告警,這時就要考慮分庫了。
根據業務量增長趨勢,計划擴容一台同配置的RDS實例,將訂單表 orders 拆分20個子表,每個 RDS 實例10個。
這樣解決了訂單表 orders 太大的問題,查詢的時候要先通過分區鍵 user_id 定位是哪個 RDS 實例,再定位到具體的子表,然后做 DML操作,
問題是代碼改造的工作量大,而且服務調用鏈路變長了,對系統的穩定性有一定的影響。
其實已經有些數據庫中間件實現了分庫分表的功能,例如常見的 mycat,阿里雲的 DRDS 等。
水平分表

根據一定的邏輯,例如將userid取模,將數據放到不同的表上。
水平拆分的方式也很多,除了上面說的按照 id 拆表,還可以按照時間維度取拆分,比如訂單表,可以按每日、每月等進行拆分。
- 每日表:只存儲當天的數據。
- 每月表:可以起一個定時任務將前一天的數據全部遷移到當月表。
- 歷史表:同樣可以用定時任務把時間超過 30 天的數據遷移到 history表。
水平拆分的特點
基於數據划分,表結構相同,數據不同。
水平拆分優點
- 單庫(表)的數據保持在一定的量(減少),提高了系統的穩定性和負載能力;
- 切分表的結構相同,程序改造較少。
水平拆分缺點
- 數據擴容有難度,維護量大
例如上面會員庫一分為二,根據userid % 2將數據分庫或分表存儲存儲,但隨着業務量快速提升,兩個庫已經不夠用,需要分成更多,例如10個,那么分庫分表邏輯也會改成 userid % 10,原有的數據在新的邏輯后需要進行數據遷移。 - 拆分規則很難抽象出來
- 分布式事務的一致性問題,部分業務無法關聯join,只能通過程序接口調用。
匯總分庫分表帶來的問題
- 跨庫關聯查詢,上面提到解決辦法;
- 分布式事務,單數據庫可以用本地事務搞定,使用多數據庫就只能通過分布式事務解決了。
常用解決方案有:基於可靠消息(MQ)的解決方案、兩階段事務提交、柔性事務等。 - 排序、分頁、函數計算問題
在使用 SQL 時 order by, limit 等關鍵字需要特殊處理,一般來說采用分片的思路:
先在每個分片上執行相應的函數,然后將各個分片的結果集進行匯總和再次計算,最終得到結果。 - 分布式 ID
如果使用 Mysql 數據庫在單庫單表可以使用 id 自增作為主鍵,分庫分表了之后就不行了,會出現id 重復。
常用的分布式 ID 解決方案有:
UUID
基於數據庫自增單獨維護一張 ID表
號段模式
Redis 緩存
雪花算法(Snowflake)
百度uid-generator
美團Leaf
滴滴Tinyid - 多數據源
分庫分表之后可能會面臨從多個數據庫或多個子表中獲取數據,一般的解決思路有:客戶端適配和代理層適配。
業界常用的中間件有:
shardingsphere(前身 sharding-jdbc)
Mycat
分庫分表現成方案
- 代碼改造,入數據庫中間件mycat,sharding-sphere;
- 分布式數據庫,實際業務中使用比較多的有 PingCAP TiDB,阿里雲 DRDS,可以優先使用分布式數據庫方案,雖然成本會有所增加,但對應用程序沒有侵入性,同時也可以比較好的支撐業務增長和系統快速迭代。