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

本文轉自聊聊架構微信公衆號spring

做者:丁浪sql

原文地址:http://mp.weixin.qq.com/s?__biz=MzA5Nzc4OTA1Mw==&mid=2659598135&idx=1&sn=2f1daf51d92b9c5ed06d9422fdd19d49&scene=21#wechat_redirect數據庫

 

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

因爲篇幅關係,下篇中咱們再繼續細聊「水平分庫分表」相關的話題。

相關文章
相關標籤/搜索