1. 程式人生 > >分庫分表的幾種常見玩法及如何解決跨庫查詢等問題

分庫分表的幾種常見玩法及如何解決跨庫查詢等問題

時間 擔憂 idt web 分布 rpc sharding har 安全性

在談論數據庫架構和數據庫優化的時候,我們經常會聽到“分庫分表”、“分片”、“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和統計。

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

作者介紹

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

分庫分表的幾種常見玩法及如何解決跨庫查詢等問題