背景
前幾天在流動(dòng)工位聽(tīng)到一同事在電話面試候選人,聽(tīng)他問(wèn):“分庫(kù)分表接觸過(guò)嗎,分別是為了解決什么問(wèn)題”,讓我聯(lián)想到曾經(jīng)有一個(gè)面試官問(wèn)沒(méi)接觸過(guò)分庫(kù)分表的我——如果由我來(lái)設(shè)計(jì),如何實(shí)現(xiàn)分庫(kù)分表?
是啊,分庫(kù)分表原理是什么,具體又應(yīng)該是怎么實(shí)現(xiàn)的,我當(dāng)時(shí)的想法對(duì)不對(duì)呢?
介紹
單庫(kù)表的問(wèn)題
通常在業(yè)務(wù)發(fā)展初期,一系列相關(guān)數(shù)據(jù)通常存儲(chǔ)在一張單表中,比如user表,article表等。隨著業(yè)務(wù)的發(fā)展,單表存儲(chǔ)的數(shù)據(jù)量可能十分巨大,達(dá)到幾千萬(wàn)甚至上億;同時(shí)一張表的字段可能也擴(kuò)展到幾十上百個(gè),此時(shí)可能帶來(lái)一系列問(wèn)題:
- 資源不足
- 單機(jī)的IO、連接數(shù)都是有限的,一旦并發(fā)量過(guò)高DB就會(huì)成為整個(gè)系統(tǒng)的瓶頸
- 讀寫(xiě)效率降低
- 寫(xiě)操作,鎖沖突現(xiàn)象一定會(huì)更明顯
- 讀操作,由于數(shù)據(jù)量過(guò)大及單行數(shù)據(jù)過(guò)多,某些批量查詢一定伴隨著頻繁IO,也難以命中緩存
- 冷熱數(shù)據(jù)混雜
- 一部分字段實(shí)際上并不需要經(jīng)常讀寫(xiě),導(dǎo)致單行數(shù)據(jù)也存在冷熱之分
- 索引膨脹
- 數(shù)據(jù)量的增加伴隨著索引量的增加,對(duì)索引的操作耗時(shí)也連帶著提高
分庫(kù)分表前的鋪墊工作
切勿過(guò)渡設(shè)計(jì),切勿過(guò)早優(yōu)化。
千萬(wàn)不要為了追求引入新技術(shù)而直接分庫(kù)分表,這會(huì)導(dǎo)致系統(tǒng)復(fù)雜性呈指數(shù)上升。在進(jìn)行分庫(kù)分表前,至少應(yīng)該做好如下鋪墊工作:
- SQL優(yōu)化 減少慢查詢,騰出連接資源
- 索引優(yōu)化 減少非索引查詢,新建索引的成本遠(yuǎn)低于架構(gòu)改變
- 增加緩存 減少DB壓力,改造成本低
- 主從分離 寫(xiě)操作以及事務(wù)中的讀操作走主庫(kù),其他讀操作走從庫(kù),需要注意主備延遲
對(duì)于大多數(shù)業(yè)務(wù)系統(tǒng)來(lái)說(shuō)讀遠(yuǎn)多于寫(xiě),增加緩存、主從分離后問(wèn)題應(yīng)該能有很大緩解。
但是即便做了上述工作,還是沒(méi)有解決根本性的單表數(shù)據(jù)量過(guò)大和字段過(guò)多的問(wèn)題,在業(yè)務(wù)繼續(xù)發(fā)展過(guò)程中問(wèn)題一定還會(huì)再次出現(xiàn)。此時(shí)除了分庫(kù)分表或修改業(yè)務(wù)邏輯使用NoSql數(shù)據(jù)庫(kù)外,幾乎沒(méi)有其他太好的方法。
水平/垂直分庫(kù)/分表是什么
- 分庫(kù) 即將原本存儲(chǔ)在單庫(kù)的表分散存儲(chǔ)在多個(gè)庫(kù)中
- 分表 將一張大表分散存儲(chǔ)在多個(gè)表中
垂直分表
根據(jù)關(guān)聯(lián)度或業(yè)務(wù)屬性按列將表拆分,如頻繁展示的“核心信息”與對(duì)應(yīng)的“詳情信息”拆分。
拆分后的表之間結(jié)構(gòu)不相同,數(shù)據(jù)存在相互關(guān)聯(lián),有小部分重疊,如user_id對(duì)應(yīng)。

水平分表
根據(jù)一定的策略將表中的數(shù)據(jù)行分別存儲(chǔ)在不同的表中,如按創(chuàng)建時(shí)間/id哈希等拆分。
拆分后的表結(jié)構(gòu)相同,但存儲(chǔ)的數(shù)據(jù)不重疊。

垂直分庫(kù)
將業(yè)務(wù)關(guān)聯(lián)度不高的表分離到不同庫(kù)中,如拆分為“用戶庫(kù)”、“商品庫(kù)”。

水平分庫(kù)
將水平分表后的多張表再次分散到不同庫(kù)中存儲(chǔ),減少單庫(kù)壓力。

分庫(kù)分表解決的問(wèn)題
分庫(kù)的核心目的:
- 將原本單庫(kù)的機(jī)器資源擴(kuò)充為多庫(kù),包括IO、連接數(shù)等
- 冷熱數(shù)據(jù)分離,將更多資源分配給頻繁訪問(wèn)的數(shù)據(jù)
分表的核心目的:
- 減少單表列數(shù),減小數(shù)據(jù)塊大小,減少I(mǎi)O次數(shù)及提高緩存利用率,減少單表IO壓力(垂直分表)
- 冷熱數(shù)據(jù)分離,可以將數(shù)據(jù)分為不常變更的數(shù)據(jù)和常變更的數(shù)據(jù),分配不同的訪問(wèn)策略(垂直分表)
- 減少單表行數(shù),將數(shù)據(jù)分散存儲(chǔ)到多張表中(水平分表)
- 冷熱數(shù)據(jù)分離,在一些時(shí)效性強(qiáng)的場(chǎng)景下將當(dāng)前數(shù)據(jù)和過(guò)時(shí)數(shù)據(jù)分開(kāi)處理(水平分表)
- 減少單表索引量,提升讀寫(xiě)效率(水平分表)
- 一些分庫(kù)操作的前提條件,即先做好分表設(shè)計(jì)后可以將表分布在不同庫(kù)中存儲(chǔ)
分庫(kù)分表我的思路
- 建立中間層,對(duì)應(yīng)用層和數(shù)據(jù)庫(kù)做到無(wú)侵入式對(duì)接,所有的配置和路由等問(wèn)題全部在中間層處理
- 設(shè)計(jì)路由規(guī)則,配置好需要的各個(gè)庫(kù)
- 接收并解析應(yīng)用層SQL語(yǔ)句,提取表名、字段名等關(guān)鍵信息
- 根據(jù)路由規(guī)則計(jì)算目標(biāo)庫(kù)表名,如果查詢目標(biāo)分布在多個(gè)庫(kù)表還需要將單條查詢拆成多條查詢
- 生成新的SQL語(yǔ)句或替換原有語(yǔ)句
- 在對(duì)應(yīng)庫(kù)表中進(jìn)行查詢
- 將返回結(jié)果根據(jù)查詢語(yǔ)句類型進(jìn)行融合,如COUNT、SUM、ORDER BY等
- 返回查詢結(jié)果
當(dāng)然在實(shí)際應(yīng)用中還需要考慮更多系統(tǒng)層面的保障,包括降級(jí)熔斷、讀寫(xiě)分離、宕機(jī)、擴(kuò)容等等。
分庫(kù)分表如何實(shí)現(xiàn)
常見(jiàn)的分庫(kù)分表中間件有:
-
ShardingSphere前身為Sharding-JDBC,目前已成為Apache基金會(huì)頂級(jí)項(xiàng)目 -
TDDL阿里開(kāi)源(開(kāi)源了嗎?) -
MyCat民間開(kāi)源
分庫(kù)分表中間件主要是處理水平分庫(kù)分表的問(wèn)題,而垂直分庫(kù)分表本身處理邏輯比較簡(jiǎn)單,在應(yīng)用層也容易實(shí)現(xiàn)和維護(hù)。
而分庫(kù)分表最重要的能力則是SQL的解析、路由與轉(zhuǎn)發(fā),以及相關(guān)的支撐能力包括配置、擴(kuò)容等。
以TDDL為例,處理流程為:

一般而言的業(yè)務(wù)演化改造流程:
-
單庫(kù)單表
- 重要業(yè)務(wù)數(shù)據(jù)可考慮直接主從分離,此時(shí)考慮的更多是數(shù)據(jù)容災(zāi)問(wèn)題
- 數(shù)據(jù)量千萬(wàn)以下完全撐得住
-
SQL優(yōu)化
- 避免慢查詢占用連接資源
-
索引優(yōu)化
- 優(yōu)化索引相對(duì)成本低,投入產(chǎn)出比高
-
增加緩存/讀寫(xiě)分離
- 針對(duì)大多數(shù)讀多寫(xiě)少的業(yè)務(wù)幫助較大
-
垂直分庫(kù)
- 增加機(jī)器資源,降低查詢壓力
- 開(kāi)始深入優(yōu)化,改造難度上升,維護(hù)也變得復(fù)雜,需要注意的包括但不限于:
- 數(shù)據(jù)遷移
- 分布式事務(wù)
- 全劇唯一id
- 冷熱數(shù)據(jù)分離,可以單獨(dú)給熱區(qū)數(shù)據(jù)做分庫(kù)分表
- 服務(wù)器資源規(guī)劃
-
垂直分表
- 改造成本更高,應(yīng)用層邏輯可能需要改造,如數(shù)據(jù)雙寫(xiě)等
-
水平分表
- 當(dāng)單表數(shù)據(jù)量過(guò)大時(shí),需要借助中間件進(jìn)行分表
-
水平分庫(kù)
- 擴(kuò)容,增加機(jī)器資源
- 如果還是撐不住,試試其他存儲(chǔ)方式吧,比如NoSql數(shù)據(jù)庫(kù)
分庫(kù)分表帶來(lái)的問(wèn)題
自增主鍵
分表后各表無(wú)法同步自增id,需要依賴全局唯一id生成方案實(shí)現(xiàn),如snowflake等。
數(shù)據(jù)更新
垂直分庫(kù)分表后,一些單表更新變成多表更新。
數(shù)據(jù)庫(kù)事務(wù)
由于跨庫(kù)的問(wèn)題,原本的數(shù)據(jù)庫(kù)事務(wù)難以支持,需要借助分布式事務(wù)實(shí)現(xiàn)。
關(guān)聯(lián)操作
由于跨庫(kù),join等操作無(wú)法實(shí)現(xiàn),關(guān)聯(lián)查詢很難實(shí)現(xiàn),大多數(shù)應(yīng)用通過(guò)冗余存儲(chǔ)或借助其他存儲(chǔ)方式實(shí)現(xiàn)。
外鍵約束/全文索引
分庫(kù)分表后無(wú)法支持全文索引或外鍵約束,正如一些參考資料所說(shuō),現(xiàn)今的業(yè)務(wù)本身最好就盡最大可能避免這樣的操作發(fā)生,以本人從業(yè)經(jīng)驗(yàn)來(lái)看也從未遇到應(yīng)用場(chǎng)景。
性能開(kāi)銷
原本的一些單表查詢?cè)诜謳?kù)分表后可能變?yōu)槎啾聿樵儯约邦~外的數(shù)據(jù)拼接處理開(kāi)銷。但相比單表海量數(shù)據(jù)的讀寫(xiě)壓力而言這些額外開(kāi)銷是可以接受的。
運(yùn)維管理
從單庫(kù)單表到分庫(kù)分表,維護(hù)難度呈指數(shù)增長(zhǎng),尤其是涉及到擴(kuò)容、宕機(jī)等等。
總結(jié)
分庫(kù)分表中間件本質(zhì)上就是將應(yīng)用層的多表查詢和結(jié)果處理能力分離,進(jìn)行抽象化和標(biāo)準(zhǔn)化。
雖然核心思路并不復(fù)雜,但整個(gè)服務(wù)的體系化,包括維護(hù)、容災(zāi)等才是最困難的部分。同時(shí),當(dāng)面試官問(wèn)過(guò)分庫(kù)分表的問(wèn)題后,緊接著可以展開(kāi)的內(nèi)容也非常豐富,包括全局唯一id、數(shù)據(jù)庫(kù)鎖、分布式事務(wù)、主從同步、負(fù)載均衡、一致性哈希、限流、緩存......能夠關(guān)聯(lián)到很多的知識(shí),是一個(gè)非常好的切入點(diǎn)。
參考資料
分布式數(shù)據(jù)庫(kù)中間件—TDDL的使用介紹 - 知乎