天天看點

【資料庫】分庫分表的難點

垂直分庫分表能分擔單庫的承擔能力,提高整個系統的承載處理能力、IO能力,有效緩解系統的性能瓶頸和壓力,突破IO、連接配接數、硬體資源的瓶頸。但是,與此同時,分庫分表也為分庫查詢等帶來了相應的痛點。

垂直分庫帶來的問題和解決思路:

跨庫join的問題

在拆分之前,系統中很多清單和詳情頁所需的資料是可以通過sql join來完成的。而拆分後,資料庫可能是分布式在不同執行個體和不同的主機上,join将變得非常麻煩。而且基于架構規範,性能,安全性等方面考慮,一般是禁止跨庫join的。那該怎麼辦呢?首先要考慮下垂直分庫的設計問題,如果可以調整,那就優先調整。如果無法調整的情況,總結幾種常見的解決思路,并分析其适用場景。

跨庫Join的幾種解決思路

全局表

所謂全局表,就是有可能系統中所有子產品都可能會依賴到的一些表。比較類似我們了解的“資料字典”。為了避免跨庫join查詢,我們可以将這類表在其他每個資料庫中均儲存一份。同時,這類資料通常也很少發生修改(甚至幾乎不會),是以也不用太擔心“一緻性”問題。

字段備援

這是一種典型的反範式設計,在網際網路行業中比較常見,通常是為了性能來避免join查詢。

舉個電商業務中很簡單的場景:

“訂單表”中儲存“賣家Id”的同時,将賣家的“Name”字段也備援,這樣查詢訂單詳情的時候就不需要再去查詢“賣家使用者表”。

字段備援能帶來便利,是一種“空間換時間”的展現。但其适用場景也比較有限,比較适合依賴字段較少的情況。最複雜的還是資料一緻性問題,這點很難保證,可以借助資料庫中的觸發器或者在業務代碼層面去保證。當然,也需要結合實際業務場景來看一緻性的要求。就像上面例子,如果賣家修改了Name之後,是否需要在訂單資訊中同步更新呢?

資料同步

A庫中的tab_a表和B庫中tbl_b有關聯,可以定時将指定的表做同步。當然,同步本來會對資料庫帶來一定的影響,需要性能影響和資料時效性中取得一個平衡。這樣來避免複雜的跨庫查詢。筆者曾經在項目中是通過ETL工具來實施的。

系統層組裝

在系統層面,通過調用不同子產品的元件或者服務,擷取到資料并進行字段拼裝。說起來很容易,但實踐起來可真沒有這麼簡單,尤其是資料庫設計上存在問題但又無法輕易調整的時候。

具體情況通常會比較複雜。下面筆者結合以往實際經驗,并通過僞代碼方式來描述。

簡單的清單查詢的情況

【資料庫】分庫分表的難點

僞代碼很容易了解,先擷取“我的提問清單”資料,然後再根據清單中的UserId去循環調用依賴的使用者服務擷取到使用者的RealName,拼裝結果并傳回。

有經驗的讀者一眼就能看出上訴僞代碼存在效率問題。循環調用服務,可能會有循環RPC,循環查詢資料庫…不推薦使用。再看看改進後的:

【資料庫】分庫分表的難點

這種實作方式,看起來要優雅一點,其實就是把循環調用改成一次調用。當然,使用者服務的資料庫查詢中很可能是In查詢,效率方面比上一種方式更高。(坊間流傳In查詢會全表掃描,存在性能問題,傳聞不可全信。其實查詢優化器都是基本成本估算的,經過測試,在In語句中條件字段有索引的時候,條件較少的情況是會走索引的。這裡不細展開說明,感興趣的朋友請自行測試)。

小結

簡單字段組裝的情況下,我們隻需要先擷取“主表”資料,然後再根據關聯關系,調用其他子產品的元件或服務來擷取依賴的其他字段(如例中依賴的使用者資訊),最後将資料進行組裝。

通常,我們都會通過緩存來避免頻繁RPC通信和資料庫查詢的開銷。

清單查詢帶條件過濾的情況

在上述例子中,都是簡單的字段組裝,而不存在條件過濾。看拆分前的SQL:

【資料庫】分庫分表的難點

這種連接配接查詢并且還帶條件過濾的情況,想在代碼層面組裝資料其實是非常複雜的(尤其是左表和右表都帶條件過濾的情況會更複雜),不能像之前例子中那樣簡單的進行組裝了。試想一下,如果像上面那樣簡單的進行組裝,造成的結果就是傳回的資料不完整,不準确。 

有如下幾種解決思路:

  1. 查出所有的問答資料,然後調用使用者服務進行拼裝資料,再根據過濾字段state字段進行過濾,最後進行排序和分頁并傳回。

    這種方式能夠保證資料的準确性和完整性,但是性能影響非常大,不建議使用。

  2. 查詢出state字段符合/不符合的UserId,在查詢問答資料的時候使用in/not in進行過濾,排序,分頁等。過濾出有效的問答資料後,再調用使用者服務擷取資料進行組裝。

    這種方式明顯更優雅點。筆者之前在某個項目的特殊場景中就是采用過這種方式實作。

垂直分庫總結和實踐建議

本篇中主要描述了幾種常見的拆分方式,并着重介紹了垂直分庫帶來的一些問題和解決思路。讀者朋友可能還有些問題和疑惑。

1. 我們目前的資料庫是否需要進行垂直分庫?

根據系統架構和公司實際情況來,如果你們的系統還是個簡單的單體應用,并且沒有什麼通路量和資料量,那就别着急折騰“垂直分庫”了,否則沒有任何收益,也很難有好結果。

切記,“過度設計”和“過早優化”是很多架構師和技術人員常犯的毛病。

2. 垂直拆分有沒有原則或者技巧?

沒有什麼黃金法則和标準答案。一般是參考系統的業務子產品拆分來進行資料庫的拆分。比如“使用者服務”,對應的可能就是“使用者資料庫”。但是也不一定嚴格一一對應。有些情況下,資料庫拆分的粒度可能會比系統拆分的粒度更粗。筆者也确實見過有些系統中的某些表原本應該放A庫中的,卻放在了B庫中。有些庫和表原本是可以合并的,卻單獨儲存着。還有些表,看起來放在A庫中也OK,放在B庫中也合理。

如何設計和權衡,這個就看實際情況和架構師/開發人員的水準了。

3. 上面舉例的都太簡單了,我們的背景報表系統中join的表都有n個了, 

分庫後該怎麼查?

有很多朋友跟我提過類似的問題。其實網際網路的業務系統中,本來就應該盡量避免join的,如果有多個join的,要麼是設計不合理,要麼是技術選型有誤。請自行科普下OLAP和OLTP,報表類的系統在傳統BI時代都是通過OLAP資料倉庫去實作的(現在則更多是借助離線分析、流式計算等手段實作),而不該向上面描述的那樣直接在業務庫中執行大量join和統計。