1. 程式人生 > >分庫分表的幾種常見形式以及可能遇到的難題

分庫分表的幾種常見形式以及可能遇到的難題

在談論資料庫架構和資料庫優化的時候,我們經常會聽到“分庫分表”、“分片”、“Sharding”…這樣的關鍵詞。讓人感到高興的是,這些朋友所服務的公司業務量正在(或者即將面臨)高速增長,技術方面也面臨著一些挑戰。讓人感到擔憂的是,他們系統真的就需要“分庫分表”了嗎?“分庫分表”有那麼容易實踐嗎?為此,筆者整理了分庫分表中可能遇到的一些問題,並結合以往經驗介紹了對應的解決思路和建議。

垂直分表

垂直分表在日常開發和設計中比較常見,通俗的說法叫做“大表拆小表”,拆分是基於關係型資料庫中的“列”(欄位)進行的。通常情況,某個表中的欄位比較多,可以新建立一張“擴充套件表”,將不經常使用或者長度較大的欄位拆分出去放到“擴充套件表”中,如下圖所示:

小結

在欄位很多的情況下,拆分開確實更便於開發和維護(筆者曾見過某個遺留系統中,一個大表中包含100多列的)。某種意義上也能避免“跨頁”的問題(MySQL、MSSQL底層都是通過“資料頁”來儲存的,“跨頁”問題可能會造成額外的效能開銷,這裡不展開,感興趣的朋友可以自行查閱相關資料進行研究)。

拆分欄位的操作建議在資料庫設計階段就做好。如果是在發展過程中拆分,則需要改寫以前的查詢語句,會額外帶來一定的成本和風險,建議謹慎。

垂直分庫

垂直分庫在“微服務”盛行的今天已經非常普及了。基本的思路就是按照業務模組來劃分出不同的資料庫,而不是像早期一樣將所有的資料表都放到同一個資料庫中。如下圖:

小結

系統層面的“服務化”拆分操作,能夠解決業務系統層面的耦合和效能瓶頸,有利於系統的擴充套件維護。而資料庫層面的拆分,道理也是相通的。與服務的“治理”和“降級”機制類似,我們也能對不同業務型別的資料進行“分級”管理、維護、監控、擴充套件等。

眾所周知,資料庫往往最容易成為應用系統的瓶頸,而資料庫本身屬於“有狀態”的,相對於Web和應用伺服器來講,是比較難實現“橫向擴充套件”的。資料庫的連線資源比較寶貴且單機處理能力也有限,在高併發場景下,垂直分庫一定程度上能夠突破IO、連線數及單機硬體資源的瓶頸,是大型分散式系統中優化資料庫架構的重要手段。

然後,很多人並沒有從根本上搞清楚為什麼要拆分,也沒有掌握拆分的原則和技巧,只是一味的模仿大廠的做法。導致拆分後遇到很多問題(例如:跨庫join,分散式事務等)。

水平分表 

水平分表也稱為橫向分表,比較容易理解,就是將表中不同的資料行按照一定規律分佈到不同的資料庫表中(這些表儲存在同一個資料庫中),這樣來降低單表資料量,優化查詢效能。最常見的方式就是通過主鍵或者時間等欄位進行Hash和取模後拆分。如下圖所示:

小結

水平分表,能夠降低單表的資料量,一定程度上可以緩解查詢效能瓶頸。但本質上這些表還儲存在同一個庫中,所以庫級別還是會有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進行過濾,排序,分頁等。過濾出有效的問答資料後,再呼叫使用者服務獲取資料進行組裝。

    這種方式明顯更優雅點。筆者之前在某個專案的特殊場景中就是採用過這種方式實現。

跨庫事務(分散式事務)的問題

按業務拆分資料庫之後,不可避免的就是“分散式事務”的問題。以往在程式碼中通過spring註解簡單配置就能實現事務的,現在則需要花很大的成本去保證一致性。這裡不展開介紹, 
感興趣的讀者可以自行參考《分散式事務一致性解決方案》,連結地址: 
http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency

垂直分庫總結和實踐建議

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

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

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

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

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

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

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

3. 上面舉例的都太簡單了,我們的後臺報表系統中join的表都有n個了, 
分庫後該怎麼查?

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

由於篇幅關係,下篇中我們再繼續細聊“水平分庫分表”相關的話題。

作者介紹

丁浪,技術架構師。關注高併發、高可用的架構設計,對系統服務化、分庫分表、效能調優等方面有深入研究和豐富實踐經驗。熱衷於技術研究和分享。