分庫分表原因
前文介紹MySQL主從模式,將讀寫分離以提高性能。 主從模式對(duì)于寫少讀多的場(chǎng)景確實(shí)非常大的優(yōu)勢(shì),但是總會(huì)寫操作達(dá)到瓶頸的時(shí)候,導(dǎo)致性能提不上去。
總的來說就是數(shù)據(jù)庫出現(xiàn)性能瓶頸,對(duì)外表現(xiàn)有幾個(gè)方面:
大量請(qǐng)求阻塞:
在高并發(fā)場(chǎng)景下,大量請(qǐng)求都需要操作數(shù)據(jù)庫,導(dǎo)致連接數(shù)不夠了,請(qǐng)求處于阻塞狀態(tài)。
SQL 操作變慢:
如果數(shù)據(jù)庫中存在一張上億數(shù)據(jù)量的表,一條 SQL 沒有命中索引會(huì)全表掃描,這個(gè)查詢耗時(shí)會(huì)非常久。
存儲(chǔ)出現(xiàn)問題:
業(yè)務(wù)量劇增,單庫數(shù)據(jù)量越來越大,給存儲(chǔ)造成巨大壓力。
如果系統(tǒng)處于高速發(fā)展階段,拿商城系統(tǒng)來說,一天下單量可能幾十萬,那數(shù)據(jù)庫中的訂單表增長就特別快,增長到一定階段數(shù)據(jù)庫查詢效率就會(huì)出現(xiàn)明顯下降。
這時(shí)候可以在設(shè)計(jì)上進(jìn)行解決:
- 采用分庫分表的形式,對(duì)于業(yè)務(wù)數(shù)據(jù)比較大的數(shù)據(jù)庫可以采用分表,使得數(shù)據(jù)表的存儲(chǔ)的數(shù)據(jù)量達(dá)到一個(gè)合理的狀態(tài)。
- 也可以采用分庫,按照業(yè)務(wù)進(jìn)行劃分,這樣對(duì)于單點(diǎn)的寫,就會(huì)分成多點(diǎn)的寫,性能方面也就會(huì)大大提高。
分庫分表方案更多的是對(duì)關(guān)系型數(shù)據(jù)庫數(shù)據(jù)存儲(chǔ)和訪問機(jī)制的一種補(bǔ)充,而不是顛覆。
分庫分表拆分思路

什么時(shí)候進(jìn)行分庫
MySQL 的高可用架構(gòu)大多都是一主多從,所有寫入操作都發(fā)生在 Master 上,隨著業(yè)務(wù)的增長,數(shù)據(jù)量的增加,很多接口響應(yīng)時(shí)間變得很長,經(jīng)常出現(xiàn) Timeout,而且通過升級(jí) MySQL 實(shí)例配置已經(jīng)無法解決問題了,這時(shí)候就要分庫。
什么時(shí)候進(jìn)行分表
分表的應(yīng)用場(chǎng)景是單表數(shù)據(jù)量增長速度過快,影響了業(yè)務(wù)接口的響應(yīng)時(shí)間,但是 MySQL 實(shí)例的負(fù)載并不高,這時(shí)候只需要分表,不需要分庫(拆分實(shí)例)。
垂直拆分
垂直分庫

垂直分庫是按業(yè)務(wù)分庫,例如一個(gè)電商系統(tǒng)shop庫按業(yè)務(wù)分有訂單表,會(huì)員表,商品表,按業(yè)務(wù)拆分后,響應(yīng)的shop庫被拆分到三個(gè)RDS實(shí)例中,數(shù)據(jù)庫寫入能力提升,服務(wù)的接口響應(yīng)時(shí)間變短,提供穩(wěn)定性。
垂直分表

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

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

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