轉載自https://blog.csdn.net/ynztpwl/article/details/53909760mysql
垂直分表:垂直分表在平常開發和設計中比較常見,通俗的說法叫作「大表拆小表」,拆分是基於關係型數據庫中的「列」(字段)進行的。一般狀況,某個表中的字段比較多,能夠新創建一張「擴展表」,將不常用或者長度較大的字段拆分出去放到「擴展表」中。算法
ps:在字段不少的狀況下,拆分開確實更便於開發和維護(曾見過某個遺留系統中,一個大表中包含100多列的)。某種意義上也能避免「跨頁」的問題(MySQL、MSSQL底層都是經過「數據頁」來存儲的,「跨頁」問題可能會形成額外的性能開銷)。拆分字段的操做建議在數據庫設計階段就作好。若是是在發展過程當中拆分,則須要改寫之前的查詢語句,會額外帶來必定的成本和風險,建議謹慎。sql
垂直分庫:基本的思路就是按照業務模塊來劃分出不一樣的數據庫,而不是像早期同樣將全部的數據表都放到同一個數據庫中。數據庫
ps:系統層面的「服務化」拆分操做,可以解決業務系統層面的耦合和性能瓶頸,有利於系統的擴展維護。而數據庫層面的拆分,道理也是相通的。與服務的「治理」和「降級」機制相似,咱們也能對不一樣業務類型的數據進行「分級」管理、維護、監控、擴展等。衆所周知,數據庫每每最容易成爲應用系統的瓶頸,而數據庫自己屬於「有狀態」的,相對於Web和應用服務器來說,是比較難實現「橫向擴展」的。數據庫的鏈接資源比較寶貴且單機處理能力也有限,在高併發場景下,垂直分庫必定程度上可以突破IO、鏈接數及單機硬件資源的瓶頸,是大型分佈式系統中優化數據庫架構的重要手段。編程
水平分表:水平分表也稱爲橫向分表,比較容易理解,就是將表中不一樣的數據行按照必定規律分佈到不一樣的數據庫表中(這些表保存在同一個數據庫中),這樣來下降單表數據量,優化查詢性能。最多見的方式就是經過主鍵或者時間等字段進行Hash和取模後拆分。後端
ps:水平分表,可以下降單表的數據量,必定程度上能夠緩解查詢性能瓶頸。但本質上這些表還保存在同一個庫中,因此庫級別仍是會有IO瓶頸。因此,通常不建議採用這種作法。安全
水平分庫分表:水平分庫分表與上面講到的水平分表的思想相同,惟一不一樣的就是將這些拆分出來的表保存在不一樣的數據庫中。這也是不少大型互聯網公司所選擇的作法。服務器
ps:某種意義上來說,有些系統中使用的「冷熱數據分離」(將一些使用較少的歷史數據遷移到其餘的數據庫中。而在業務功能上,一般默認只提供熱點數據的查詢),也是相似的實踐。在高併發和海量數據的場景下,分庫分表可以有效緩解單機和單庫的性能瓶頸和壓力,突破IO、鏈接數、硬件資源的瓶頸。固然,投入的硬件成本也會更高。同時,這也會帶來一些複雜的技術問題和挑戰(例如:跨分片的複雜查詢,跨分片事務等)網絡
在拆分以前,系統中不少列表和詳情頁所需的數據是能夠經過sql join來完成的。而拆分後,數據庫多是分佈式在不一樣實例和不一樣的主機上,join將變得很是麻煩。並且基於架構規範,性能,安全性等方面考慮,通常是禁止跨庫join的。那該怎麼辦呢?首先要考慮下垂直分庫的設計問題,若是能夠調整,那就優先調整。若是沒法調整的狀況,下面將結合以往的實際經驗,總結幾種常見的解決思路,並分析其適用場景。架構
全局表
所謂全局表,就是有可能系統中全部模塊均可能會依賴到的一些表。比較相似咱們理解的「數據字典」。爲了不跨庫join查詢,咱們能夠將這類表在其餘每一個數據庫中均保存一份。同時,這類數據一般也不多發生修改(甚至幾乎不會),因此也不用太擔憂「一致性」問題。
字段冗餘
這是一種典型的反範式設計,在互聯網行業中比較常見,一般是爲了性能來避免join查詢。
舉個電商業務中很簡單的場景:
「訂單表」中保存「賣家Id」的同時,將賣家的「Name」字段也冗餘,這樣查詢訂單詳情的時候就不須要再去查詢「賣家用戶表」。
字段冗餘能帶來便利,是一種「空間換時間」的體現。但其適用場景也比較有限,比較適合依賴字段較少的狀況。最複雜的仍是數據一致性問題,這點很難保證,能夠藉助數據庫中的觸發器或者在業務代碼層面去保證。固然,也須要結合實際業務場景來看一致性的要求。就像上面例子,若是賣家修改了Name以後,是否須要在訂單信息中同步更新呢?
數據同步
A庫中的tab_a表和B庫中tbl_b有關聯,能夠定時將指定的表作同步。固然,同步原本會對數據庫帶來必定的影響,須要性能影響和數據時效性中取得一個平衡。這樣來避免複雜的跨庫查詢。
系統層組裝
在系統層面,經過調用不一樣模塊的組件或者服務,獲取到數據並進行字段拼裝。提及來很容易,但實踐起來可真沒有這麼簡單,尤爲是數據庫設計上存在問題但又沒法輕易調整的時候。具體狀況一般會比較複雜。組裝的時候要避免循環調用服務,循環RPC,循環查詢數據庫,最好一次性返回全部信息,在代碼裏作組裝。
http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency
咱們每每直接使用數據庫自增特性來生成主鍵ID,這樣確實比較簡單。而在分庫分表的環境中,數據分佈在不一樣的分片上,不能再借助數據庫自增加特性直接生成,不然會形成不一樣分片上的數據表主鍵會重複。簡單介紹幾種ID生成算法。
Twitter的Snowflake(又名「雪花算法」)
UUID/GUID(通常應用程序和數據庫均支持)
MongoDB ObjectID(相似UUID的方式)
Ticket Server(數據庫生存方式,Flickr採用的就是這種方式)
其中,Twitter 的Snowflake算法生成的是64位惟一Id(由41位的timestamp+ 10位自定義的機器碼+ 13位累加計數器組成)。
在開始分片以前,咱們首先要肯定分片字段(也可稱爲「片鍵」)。不少常見的例子和場景中是採用ID或者時間字段進行拆分。這也並不絕對的,個人建議是結合實際業務,經過對系統中執行的sql語句進行統計分析,選擇出須要分片的那個表中最頻繁被使用,或者最重要的字段來做爲分片字段。
常見分片規則
常見的分片策略有隨機分片和連續分片這兩種,當須要使用分片字段進行範圍查找時,連續分片能夠快速定位分片進行高效查詢,大多數狀況下能夠有效避免跨分片查詢的問題。後期若是想對整個分片集羣擴容時,只須要添加節點便可,無需對其餘分片的數據進行遷移。可是,連續分片也有可能存在數據熱點的問題,有些節點可能會被頻繁查詢壓力較大,熱數據節點就成爲了整個集羣的瓶頸。而有些節點可能存的是歷史數據,不多須要被查詢到。隨機分片其實並非隨機的,也遵循必定規則。一般,咱們會採用Hash取模的方式進行分片拆分,因此有些時候也被稱爲離散分片。隨機分片的數據相對比較均勻,不容易出現熱點和併發訪問的瓶頸。可是,後期分片集羣擴容起來須要遷移舊的數據。使用一致性Hash算法可以很大程度的避免這個問題,因此不少中間件的分片集羣都會採用一致性Hash算法。離散分片也很容易面臨跨分片查詢的複雜問題。
不多有項目會在初期就開始考慮分片設計的,通常都是在業務高速發展面臨性能和存儲的瓶頸時纔會提早準備。所以,不可避免的就須要考慮歷史數據遷移的問題。通常作法就是經過程序先讀出歷史數據,而後按照指定的分片規則再將數據寫入到各個分片節點中。此外,咱們須要根據當前的數據量和QPS等進行容量規劃,綜合成本因素,推算出大概須要多少分片(通常建議單個分片上的單表數據量不要超過1000W)。若是是採用隨機分片,則須要考慮後期的擴容問題,相對會比較麻煩。若是是採用的範圍分片,只須要添加節點就能夠自動擴容。
分頁時須要按照指定字段進行排序。當排序字段就是分片字段的時候,咱們經過分片規則能夠比較容易定位到指定的分片,而當排序字段非分片字段的時候,狀況就會變得比較複雜了。爲了最終結果的準確性,咱們須要在不一樣的分片節點中將數據進行排序並返回,並將不一樣分片返回的結果集進行彙總和再次排序,最後再返回給用戶。
在使用Max、Min、Sum、Count之類的函數進行統計和計算的時候,須要先在每一個分片數據源上執行相應的函數處理,而後再將各個結果集進行二次處理,最終再將處理結果返回。
Join是關係型數據庫中最經常使用的特性,可是在分片集羣中,join也變得很是複雜。應該儘可能避免跨分片的join查詢(這種場景,比上面的跨分片分頁更加複雜,並且對性能的影響很大)。一般有如下幾種方式來避免:
全局表
全局表的概念以前在「垂直分庫」時提過。基本思想一致,就是把一些相似數據字典又可能會產生join查詢的表信息放到各分片中,從而避免跨分片的join。
ER分片
在關係型數據庫中,表之間每每存在一些關聯的關係。若是咱們能夠先肯定好關聯關係,並將那些存在關聯關係的表記錄存放在同一個分片上,那麼就能很好的避免跨分片join問題。在一對多關係的狀況下,咱們一般會選擇按照數據較多的那一方進行拆分。
內存計算
隨着spark內存計算的興起,理論上來說,不少跨數據源的操做問題看起來彷佛都可以獲得解決。能夠將數據丟給spark集羣進行內存計算,最後將計算結果返回。
其實這點沒有明確的判斷標準,比較依賴實際業務狀況和經驗判斷。通常MySQL單表1000W左右的數據是沒有問題的(前提是應用系統和數據庫等層面設計和優化的比較好)。
固然,除了考慮當前的數據量和性能狀況時,做爲架構師,咱們須要提早考慮系統半年到一年左右的業務增加狀況,對數據庫服務器的QPS、鏈接數、容量等作合理評估和規劃,並提早作好相應的準備工做。若是單機沒法知足,且很難再從其餘方面優化,那麼說明是須要考慮分片的。這種狀況能夠先去掉數據庫中自增ID,爲分片和後面的數據遷移工做提早作準備。
不少人以爲「分庫分表」是宜早不宜遲,應該儘早進行,由於擔憂越日後公司業務發展越快、系統愈來愈複雜、系統重構和擴展越困難…這種話聽起來是有那麼一點道理,但個人觀點剛好相反,對於關係型數據庫來說,我認爲「能不分片就別分片」,除非是系統真正須要,由於數據庫分片並不是低成本或者免費的。這裏推薦一個比較靠譜的過渡技術–「表分區」。主流的關係型數據庫中基本都支持。不一樣的分區在邏輯上還是一張表,可是物理上倒是分開的,能在必定程度上提升查詢性能,並且對應用程序透明,無需修改任何代碼。當時有一個系統,主業務表有大約8000W左右的數據,考慮到成本問題,當時就是採用「表分區」來作的,效果比較明顯,且系統運行的很穩定。
當前主要有兩類解決方案:
基於應用程序層面的DDAL(分佈式數據庫訪問層)
比較典型的就是淘寶半開源的TDDL,噹噹網開源的Sharding-JDBC等。分佈式數據訪問層無需硬件投入,技術能力較強的大公司一般會選擇自研或參照開源框架進行二次開發和定製。對應用程序的侵入性通常較大,會增長技術成本和複雜度。一般僅支持特定編程語言平臺(Java平臺的居多),或者僅支持特定的數據庫和特定數據訪問框架技術(通常支持MySQL數據庫,JDBC、MyBatis、Hibernate等框架技術)。
數據庫中間件,比較典型的像mycat(在阿里開源的cobar基礎上作了不少優化和改進,屬於後起之秀,也支持不少新特性),基於Go語言實現kingSharding,比較老牌的Atlas(由360開源)等。這些中間件在互聯網企業中大量被使用。另外,MySQL 5.x企業版中官方提供的Fabric組件也號稱支持分片技術,不過國內使用的企業較少。
中間件也能夠稱爲「透明網關」,大名鼎鼎的mysql_proxy大概是該領域的鼻祖(由MySQL官方提供,僅限於實現「讀寫分離」)。中間件通常實現了特定數據庫的網絡通訊協議,模擬一個真實的數據庫服務,屏蔽了後端真實的Server,應用程序一般直接鏈接中間件便可。而在執行SQL操做時,中間件會按照預先定義分片規則,對SQL語句進行解析、路由,並對結果集作二次計算再最終返回。
引入數據庫中間件的技術成本更低,對應用程序來說侵入性幾乎沒有,能夠知足大部分的業務。增長了額外的硬件投入和運維成本,同時,中間件自身也存在性能瓶頸和單點故障問題,須要可以保證中間件自身的高可用、可擴展。
總之,無論是使用分佈式數據訪問層仍是數據庫中間件,都會帶來必定的成本和複雜度,也會有必定的性能影響。因此,還需讀者根據實際狀況和業務發展須要慎重考慮和選擇。