分庫(kù)分表的難點(diǎn)
垂直分庫(kù)帶來(lái)的問(wèn)題和解決思路:
跨庫(kù)join的問(wèn)題
在拆分之前,系統(tǒng)中很多列表和詳情頁(yè)所需的數(shù)據(jù)是可以通過(guò)sql join來(lái)完成的。而拆分后,數(shù)據(jù)庫(kù)可能是分布式在不同實(shí)例和不同的主機(jī)上,join將變得非常麻煩。而且基于架構(gòu)規(guī)范,性能,安全性等方面考慮,一般是禁止跨庫(kù)join的。那該怎么辦呢?首先要考慮下垂直分庫(kù)的設(shè)計(jì)問(wèn)題,如果可以調(diào)整,那就優(yōu)先調(diào)整。如果無(wú)法調(diào)整的情況,下面筆者將結(jié)合以往的實(shí)際經(jīng)驗(yàn),總結(jié)幾種常見的解決思路,并分析其適用場(chǎng)景。
跨庫(kù)Join的幾種解決思路
全局表
所謂全局表,就是有可能系統(tǒng)中所有模塊都可能會(huì)依賴到的一些表。比較類似我們理解的“數(shù)據(jù)字典”。為了避免跨庫(kù)join查詢,我們可以將這類表在其他每個(gè)數(shù)據(jù)庫(kù)中均保存一份。同時(shí),這類數(shù)據(jù)通常也很少發(fā)生修改(甚至幾乎不會(huì)),所以也不用太擔(dān)心“一致性”問(wèn)題。
字段冗余
這是一種典型的反范式設(shè)計(jì),在互聯(lián)網(wǎng)行業(yè)中比較常見,通常是為了性能來(lái)避免join查詢。
舉個(gè)電商業(yè)務(wù)中很簡(jiǎn)單的場(chǎng)景:
“訂單表”中保存“賣家Id”的同時(shí),將賣家的“Name”字段也冗余,這樣查詢訂單詳情的時(shí)候就不需要再去查詢“賣家用戶表”。
字段冗余能帶來(lái)便利,是一種“空間換時(shí)間”的體現(xiàn)。但其適用場(chǎng)景也比較有限,比較適合依賴字段較少的情況。最復(fù)雜的還是數(shù)據(jù)一致性問(wèn)題,這點(diǎn)很難保證,可以借助數(shù)據(jù)庫(kù)中的觸發(fā)器或者在業(yè)務(wù)代碼層面去保證。當(dāng)然,也需要結(jié)合實(shí)際業(yè)務(wù)場(chǎng)景來(lái)看一致性的要求。就像上面例子,如果賣家修改了Name之后,是否需要在訂單信息中同步更新呢?
數(shù)據(jù)同步
定時(shí)A庫(kù)中的tab_a表和B庫(kù)中tbl_b有關(guān)聯(lián),可以定時(shí)將指定的表做同步。當(dāng)然,同步本來(lái)會(huì)對(duì)數(shù)據(jù)庫(kù)帶來(lái)一定的影響,需要性能影響和數(shù)據(jù)時(shí)效性中取得一個(gè)平衡。這樣來(lái)避免復(fù)雜的跨庫(kù)查詢。筆者曾經(jīng)在項(xiàng)目中是通過(guò)ETL工具來(lái)實(shí)施的。
系統(tǒng)層組裝
在系統(tǒng)層面,通過(guò)調(diào)用不同模塊的組件或者服務(wù),獲取到數(shù)據(jù)并進(jìn)行字段拼裝。說(shuō)起來(lái)很容易,但實(shí)踐起來(lái)可真沒有這么簡(jiǎn)單,尤其是數(shù)據(jù)庫(kù)設(shè)計(jì)上存在問(wèn)題但又無(wú)法輕易調(diào)整的時(shí)候。
具體情況通常會(huì)比較復(fù)雜。下面筆者結(jié)合以往實(shí)際經(jīng)驗(yàn),并通過(guò)偽代碼方式來(lái)描述。
簡(jiǎn)單的列表查詢的情況

偽代碼很容易理解,先獲取“我的提問(wèn)列表”數(shù)據(jù),然后再根據(jù)列表中的UserId去循環(huán)調(diào)用依賴的用戶服務(wù)獲取到用戶的RealName,拼裝結(jié)果并返回。
有經(jīng)驗(yàn)的讀者一眼就能看出上訴偽代碼存在效率問(wèn)題。循環(huán)調(diào)用服務(wù),可能會(huì)有循環(huán)RPC,循環(huán)查詢數(shù)據(jù)庫(kù)…不推薦使用。再看看改進(jìn)后的:

這種實(shí)現(xiàn)方式,看起來(lái)要優(yōu)雅一點(diǎn),其實(shí)就是把循環(huán)調(diào)用改成一次調(diào)用。當(dāng)然,用戶服務(wù)的數(shù)據(jù)庫(kù)查詢中很可能是In查詢,效率方面比上一種方式更高。(坊間流傳In查詢會(huì)全表掃描,存在性能問(wèn)題,傳聞不可全信。其實(shí)查詢優(yōu)化器都是基本成本估算的,經(jīng)過(guò)測(cè)試,在In語(yǔ)句中條件字段有索引的時(shí)候,條件較少的情況是會(huì)走索引的。這里不細(xì)展開說(shuō)明,感興趣的朋友請(qǐng)自行測(cè)試)。
小結(jié)
簡(jiǎn)單字段組裝的情況下,我們只需要先獲取“主表”數(shù)據(jù),然后再根據(jù)關(guān)聯(lián)關(guān)系,調(diào)用其他模塊的組件或服務(wù)來(lái)獲取依賴的其他字段(如例中依賴的用戶信息),最后將數(shù)據(jù)進(jìn)行組裝。
通常,我們都會(huì)通過(guò)緩存來(lái)避免頻繁RPC通信和數(shù)據(jù)庫(kù)查詢的開銷。
列表查詢帶條件過(guò)濾的情況
在上述例子中,都是簡(jiǎn)單的字段組裝,而不存在條件過(guò)濾。看拆分前的SQL:

這種連接查詢并且還帶條件過(guò)濾的情況,想在代碼層面組裝數(shù)據(jù)其實(shí)是非常復(fù)雜的(尤其是左表和右表都帶條件過(guò)濾的情況會(huì)更復(fù)雜),不能像之前例子中那樣簡(jiǎn)單的進(jìn)行組裝了。試想一下,如果像上面那樣簡(jiǎn)單的進(jìn)行組裝,造成的結(jié)果就是返回的數(shù)據(jù)不完整,不準(zhǔn)確。