【MySQL】MySQL分库分表详解
目錄
一、前言
1.1 數據量
1.2 磁盤
1.3 數據庫連接
二、垂直拆分 or 水平拆分?
三、垂直拆分
3.1 垂直分庫
3.2 垂直分表
3.3 垂直拆分的優缺點
四、水平拆分
4.1 水平分表
4.2 水平分庫分表
4.3 水平拆分的優缺點
五、幾種常用的分庫分表的策略
5.1 根據數值范圍
5.2 根據數值取模
5.3 根據地理位置
六、分庫分表后帶來的問題
6.1 事務一致性問題
6.1.1 分布式事務
6.1.2 最終一致性
6.2 跨節點關聯查詢 join 問題
6.2.1 全局表
6.2.2 字段冗余
6.2.3 數據組裝
6.2.4 ER分片
6.3 跨節點分頁、排序、函數問題
6.4 全局主鍵避重問題
6.4.1 UUID
6.4.2 結合數據庫維護主鍵ID表
6.4.3 Snowflake分布式自增ID算法
6.5 數據遷移、擴容問題
6.6 外鍵約束問題
七. 什么時候考慮切分
7.1 能不切分盡量不要切分
7.2 數據量過大,正常運維影響業務訪問
7.3 隨著業務發展,需要對某些字段垂直拆分
7.4 數據量快速增長
7.5 安全性和可用性
八、案例分析
8.1 用戶中心業務場景
8.2 水平切分方法
8.3 非uid的查詢方法
8.3.1 建立非uid屬性到uid的映射關系
8.3.2 前臺與后臺分離
九、使用分庫分表中間件
一、前言
在互聯網還未崛起的時代,我們的傳統應用都有這樣一個特點:訪問量、數據量都比較小,單庫單表都完全可以支撐整個業務。隨著互聯網的發展和用戶規模的迅速擴大,對系統的要求也越來越高。因此傳統的MySQL單庫單表架構的性能問題就暴露出來了。而有下面幾個因素會影響數據庫性能:
1.1 數據量
MySQL單庫數據量在5000萬以內性能比較好,超過閾值后性能會隨著數據量的增大而變弱。MySQL單表數據量是500w-1000w之間性能比較好,超過1000w性能也會下降。
?
1.2 磁盤
因為單個服務的磁盤空間是有限制的,如果并發壓力下,所有的請求都訪問同一個節點,肯定會對磁盤IO造成非常大的影響。
?
1.3 數據庫連接
數據庫連接是非常稀少的資源,如果一個庫里既有用戶、商品、訂單相關的數據,當海量用戶同時操作時,數據庫連接就很可能成為瓶頸。
?
為了提升性能,所以我們必須要解決上述幾個問題,那就有必要引進分庫分表。
?
二、垂直拆分 or 水平拆分?
關系型數據庫本身比較容易成為系統瓶頸,單機存儲容量、連接數、處理能力都有限。當單表的數據量達到1000W或100G以后,由于查詢維度較多,即使添加從庫、優化索引,做很多操作時性能仍下降嚴重。此時就要考慮對其進行切分了,切分的目的就在于減少數據庫的負擔,縮短查詢時間。
數據庫分布式核心內容無非就是數據切分(Sharding),以及切分后對數據的定位、整合。數據切分就是將數據分散存儲到多個數據庫中,使得單一數據庫中的數據量變小,通過擴充主機的數量緩解單一數據庫的性能問題,從而達到提升數據庫操作性能的目的。
數據切分根據其切分類型,可以分為兩種方式:垂直(縱向)切分和水平(橫向)切分
當我們單個庫太大時,我們先要看一下是因為表太多還是數據量太大,如果是表太多,則應該將部分表進行遷移(可以按業務區分),這就是所謂的垂直切分。如果是數據量太大,則需要將表拆成更多的小表,來減少單表的數據量,這就是所謂的水平拆分。
?
三、垂直拆分
垂直切分常見有垂直分庫和垂直分表兩種。
?
3.1 垂直分庫
垂直分庫針對的是一個系統中的不同業務進行拆分,比如用戶一個庫,商品一個庫,訂單一個庫。 一個購物網站對外提供服務時,會同時對用戶、商品、訂單表進行操作。沒拆分之前, 全部都是落到單一的庫上的,這會讓數據庫的單庫處理能力成為瓶頸。如果垂直分庫后還是將用戶、商品、訂單放到同一個服務器上,只是分到了不同的庫,這樣雖然會減少單庫的壓力,但是隨著用戶量增大,這會讓整個數據庫的處理能力成為瓶頸,還有單個服務器的磁盤空間、內存也會受非常大的影響。 所以我們要將其拆分到多個服務器上,這樣上面的問題都解決了,以后也不會面對單機資源問題。這種做法與"微服務治理"的做法相似,每個微服務使用單獨的一個數據庫。
?
3.2 垂直分表
也就是“大表拆小表”,基于列字段進行的。一般是表中的字段較多,將不常用的, 數據較大,長度較長(比如text類型字段)的字段數據拆分到“擴展表“。一般是針對那種幾百列的大表,也避免查詢時,數據量太大造成的“跨頁”問題。MySQL底層是通過數據頁存儲的,一條記錄占用空間過大會導致跨頁(頁溢出),造成額外的性能開銷(IO操作變多)。另外數據庫以頁為單位將數據加載到內存中,而頁中存儲的是行數據,頁大小固定,一行數據占用空間越小,頁中存儲的行數據就越多。這樣表中字段長度較短且訪問頻率較高,內存能加載更多的數據,內存命中率更高,減少了磁盤IO,從而提升了數據庫性能。
?
3.3 垂直拆分的優缺點
優點:
- 解決業務系統層面的耦合,業務清晰
- 與微服務的治理類似,也能對不同業務的數據進行分級管理、維護、監控、擴展等
- 高并發場景下,垂直切分一定程度的提升IO、數據庫連接數、單機硬件資源的瓶頸
缺點:
- 部分表無法join,只能通過接口聚合方式解決,提升了開發的復雜度
- 單機的ACID被打破,需要引入分布式事務,而分布式事務處理復雜
- 依然存在單表數據量過大的問題(需要水平切分)
-
靠外鍵去進行約束的場景會受到影響
?
四、水平拆分
當一個應用難以再細粒度的垂直切分,或切分后數據量行數巨大,存在單庫讀寫、存儲性能瓶頸,這時候就需要進行水平切分了。
水平切分分為庫內分表和分庫分表,是根據表內數據內在的邏輯關系,將同一個表按不同的條件分散到多個數據庫或多個表中,每個表中只包含一部分數據,從而使得單個表的數據量變小,達到分布式的效果。如圖所示:
?
4.1 水平分表
和垂直分表有一點類似,不過垂直分表是基于列的,而水平分表是基于全表的。水平拆分可以大大減少單表數據量,提升查詢效率。這里的水平分表指的是在一個數據庫進行的庫內分表。
庫內分表只解決了單一表數據量過大的問題,但沒有將表分布到不同機器的庫上,因此對于減輕MySQL數據庫的壓力來說,幫助不是很大,大家還是競爭同一個物理機的CPU、內存、網絡IO,最好通過分庫分表來解決。
?
4.2 水平分庫分表
將單張表的數據切分到多個服務器上去,每個服務器具有相同的庫與表,只是表中數據集合不同。 水平分庫分表能夠有效的緩解單機和單庫的性能瓶頸和壓力,突破IO、連接數、硬件資源等的瓶頸。
?
4.3 水平拆分的優缺點
優點:
- 不存在單庫數據量過大、高并發的性能瓶頸,提升系統穩定性和負載能力
- 應用端改造較小,不需要拆分業務模塊
缺點:
- ACID被打破,跨分片的事務一致性難以保證
- 跨庫的join關聯查詢性能較差
- 數據多次擴展難度和維護量極大
-
靠外鍵去進行約束的場景會受到影響
- 依賴單庫的自增ID會受到影響
?
五、幾種常用的分庫分表的策略
5.1 根據數值范圍
按照時間區間或ID區間來切分。例如:按日期將不同月甚至是日的數據分散到不同的庫中;將userId為1~9999的記錄分到第一個庫,10000~20000的分到第二個庫,以此類推。某種意義上,某些系統中使用的"冷熱數據分離",將一些使用較少的歷史數據遷移到其他庫中,業務功能上只提供熱點數據的查詢,也是類似的實踐。
這樣的優點在于:
- 單表大小可控
- 天然便于水平擴展,后期如果想對整個分片集群擴容時,只需要添加節點即可,無需對其他分片的數據進行遷移
- 使用分片字段進行范圍查找時,連續分片可快速定位分片進行快速查詢,有效避免跨分片查詢的問題。
缺點:
- 熱點數據成為性能瓶頸。連續分片可能存在數據熱點,例如按時間字段分片,有些分片存儲最近時間段內的數據,可能會被頻繁的讀寫,而有些分片存儲的歷史數據,則很少被查詢
?
5.2 根據數值取模
一般采用hash取模mod的切分方式,例如:將 Customer 表根據 cusno 字段切分到4個庫中,余數為0的放到第一個庫,余數為1的放到第二個庫,以此類推。這樣同一個用戶的數據會分散到同一個庫中,如果查詢條件帶有cusno字段,則可明確定位到相應庫去查詢。再比如說有用戶表user,將其分成3個表user0,user1,user2.路由規則是對3取模,當uid=1時,對應到的是user1,uid=2時,對應的是user2.
優點:
- 數據分片相對比較均勻,不容易出現熱點和并發訪問的瓶頸
缺點:
- 后期分片集群擴容時,需要遷移舊的數據(使用一致性hash算法能較好的避免這個問題),否則會導致歷史數據失效。
- 容易面臨跨分片查詢的復雜問題。比如上例中,如果頻繁用到的查詢條件中不帶cusno時,將會導致無法定位數據庫,從而需要同時向4個庫發起查詢,再在內存中合并數據,取最小集返回給應用,分庫反而成為拖累。
?
5.3 根據地理位置
根據地理位置,將相同地區的放到一張表中,比如華南區一個表,華北一個表。
?
六、分庫分表后帶來的問題
分庫分表能有效的緩解單機和單庫帶來的性能瓶頸和壓力,突破網絡IO、硬件資源、連接數的瓶頸,同時也帶來了一些問題。下面將描述這些技術挑戰以及對應的解決思路。?
6.1 事務一致性問題
6.1.1 分布式事務
當更新內容同時分布在不同庫中,不可避免會帶來跨庫事務問題。跨分片事務也是分布式事務,沒有簡單的方案,一般可使用"XA協議"和"兩階段提交"處理。
分布式事務的幾種解決方案:
?
分布式事務能最大限度保證了數據庫操作的原子性。但在提交事務時需要協調多個節點,推后了提交事務的時間點,延長了事務的執行時間。導致事務在訪問共享資源時發生沖突或死鎖的概率增高。隨著數據庫節點的增多,這種趨勢會越來越嚴重,從而成為系統在數據庫層面上水平擴展的枷鎖。
6.1.2 最終一致性
對于那些性能要求很高,但對一致性要求不高的系統,往往不苛求系統的實時一致性,只要在允許的時間段內達到最終一致性即可,可采用事務補償的方式。與事務在執行中發生錯誤后立即回滾的方式不同,事務補償是一種事后檢查補救的措施,一些常見的實現方法有:對數據進行對賬檢查,基于日志進行對比,定期同標準數據來源進行同步等等。事務補償還要結合業務系統來考慮。
?
6.2 跨節點關聯查詢 join 問題
切分之前,系統中很多列表和詳情頁所需的數據可以通過sql join來完成。而切分之后,數據可能分布在不同的節點上,此時join帶來的問題就比較麻煩了,考慮到性能,盡量避免使用join查詢。
解決這個問題的一些方法:
6.2.1 全局表
全局表,也可看做是"數據字典表",就是系統中所有模塊都可能依賴的一些表,為了避免跨庫join查詢,可以將這類表在每個數據庫中都保存一份。這些數據通常很少會進行修改,所以也不擔心一致性的問題。
?
6.2.2 字段冗余
一種典型的反范式設計,利用空間換時間,為了性能而避免join查詢。例如:訂單表保存userId時候,也將userName冗余保存一份,這樣查詢訂單詳情時就不需要再去查詢"買家user表"了。
但這種方法適用場景也有限,比較適用于依賴字段比較少的情況。而冗余字段的數據一致性也較難保證,就像上面訂單表的例子,買家修改了userName后,是否需要在歷史訂單中同步更新呢?這也要結合實際業務場景進行考慮。
?
6.2.3 數據組裝
在系統層面,分兩次查詢,第一次查詢的結果集中找出關聯數據id,然后根據id發起第二次請求得到關聯數據。最后將獲得到的數據進行字段拼裝。
?
6.2.4 ER分片
關系型數據庫中,如果可以先確定表之間的關聯關系,并將那些存在關聯關系的表記錄存放在同一個分片上,那么就能較好的避免跨分片join問題。在1:1或1:n的情況下,通常按照主表的ID主鍵切分。如下圖所示:
這樣一來,Data Node1上面的order訂單表與orderdetail訂單詳情表就可以通過orderId進行局部的關聯查詢了,Data Node2上也一樣。
?
6.3 跨節點分頁、排序、函數問題
跨節點多庫進行查詢時,會出現limit分頁、order by排序等問題。分頁需要按照指定字段進行排序,當排序字段就是分片字段時,通過分片規則就比較容易定位到指定的分片;當排序字段非分片字段時,就變得比較復雜了。需要先在不同的分片節點中將數據進行排序并返回,然后將不同分片返回的結果集進行匯總和再次排序,最終返回給用戶。如圖所示:
上圖中只是取第一頁的數據,對性能影響還不是很大。但是如果取得頁數很大,情況則變得復雜很多,因為各分片節點中的數據可能是隨機的,為了排序的準確性,需要將所有節點的前N頁數據都排序好做合并,最后再進行整體的排序,這樣的操作時很耗費CPU和內存資源的,所以頁數越大,系統的性能也會越差。
在使用Max、Min、Sum、Count之類的函數進行計算的時候,也需要先在每個分片上執行相應的函數,然后將各個分片的結果集進行匯總和再次計算,最終將結果返回。如圖所示:
總之,因為我們是將數據分散存儲到不同的庫、表里的,當我們查詢指定數據列表時,數據來源于不同的子庫或者子表,就必然會引發結果集合并、排序的問題。如果每次查詢都需要排序、合并等操作,性能肯定會受非常大的影響。走緩存是一種解決方案。
?
6.4 全局主鍵避重問題
在分庫分表環境中,由于表中數據同時存在不同數據庫中,主鍵值平時使用的自增長將無用武之地,某個分區數據庫自生成的ID無法保證全局唯一。因此需要單獨設計全局主鍵,以避免跨庫主鍵重復問題。有一些常見的主鍵生成策略:
6.4.1 UUID
UUID標準形式包含32個16進制數字,分為5段,形式為8-4-4-4-12的36個字符,例如:550e8400-e29b-41d4-a716-446655440000
UUID是主鍵是最簡單的方案,本地生成,性能高,沒有網絡耗時。但缺點也很明顯,由于UUID非常長,會占用大量的存儲空間;另外,作為主鍵建立索引和基于索引進行查詢時都會存在性能問題,在InnoDB下,UUID的無序性會引起數據位置頻繁變動。
?
6.4.2 結合數據庫維護主鍵ID表
在數據庫中建立 sequence 表:
CREATE TABLE `sequence` (?`id` bigint(20) unsigned NOT NULL auto_increment,?`stub` char(1) NOT NULL default '',?PRIMARY KEY? (`id`),?UNIQUE KEY `stub` (`stub`)? ) ENGINE=MyISAM;stub字段設置為唯一索引,同一stub值在sequence表中只有一條記錄,可以同時為多張表生成全局ID。sequence表的內容,如下所示:
+-------------------+------+? | id??????????????? | stub |? +-------------------+------+? | 72157623227190423 |??? a |? +-------------------+------+?使用 MyISAM 存儲引擎而不是 InnoDB,以獲取更高的性能。MyISAM使用的是表級別的鎖,對表的讀寫是串行的,所以不用擔心在并發時兩次讀取同一個ID值。
當需要全局唯一的64位ID時,執行:
REPLACE INTO sequence (stub) VALUES ('a');? SELECT LAST_INSERT_ID();?這兩條語句是Connection級別的,select last_insert_id() 必須與 replace into 在同一數據庫連接下才能得到剛剛插入的新ID。
使用replace into代替insert into好處是避免了表行數過大,不需要另外定期清理。
此方案較為簡單,但缺點也明顯:存在單點問題,強依賴DB,當DB異常時,整個系統都不可用。配置主從可以增加可用性,但當主庫掛了,主從切換時,數據一致性在特殊情況下難以保證。另外性能瓶頸限制在單臺MySQL的讀寫性能。
flickr團隊使用的一種主鍵生成策略,與上面的sequence表方案類似,但更好的解決了單點和性能瓶頸的問題。
這一方案的整體思想是:建立2個以上的全局ID生成的服務器,每個服務器上只部署一個數據庫,每個庫有一張sequence表用于記錄當前全局ID。表中ID增長的步長是庫的數量,起始值依次錯開,這樣能將ID的生成散列到各個數據庫上。如下圖所示:
由兩個數據庫服務器生成ID,設置不同的auto_increment值。第一臺sequence的起始值為1,每次步長增長2,另一臺的sequence起始值為2,每次步長增長也是2。結果第一臺生成的ID都是奇數(1, 3, 5, 7 ...),第二臺生成的ID都是偶數(2, 4, 6, 8 ...)。
這種方案將生成ID的壓力均勻分布在兩臺機器上。同時提供了系統容錯,第一臺出現了錯誤,可以自動切換到第二臺機器上獲取ID。但有以下幾個缺點:系統添加機器,水平擴展時較復雜;每次獲取ID都要讀寫一次DB,DB的壓力還是很大,只能靠堆機器來提升性能。
可以基于flickr的方案繼續優化,使用批量的方式降低數據庫的寫壓力,每次獲取一段區間的ID號段,用完之后再去數據庫獲取,可以大大減輕數據庫的壓力。如下圖所示:
還是使用兩臺DB保證可用性,數據庫中只存儲當前的最大ID。ID生成服務每次批量拉取6個ID,先將max_id修改為5,當應用訪問ID生成服務時,就不需要訪問數據庫,從號段緩存中依次派發0~5的ID。當這些ID發完后,再將max_id修改為11,下次就能派發6~11的ID。于是,數據庫的壓力降低為原來的1/6。
?
6.4.3 Snowflake分布式自增ID算法
Twitter的snowflake算法解決了分布式系統生成全局ID的需求,生成64位的Long型數字,組成部分:
- 第一位未使用
- 接下來41位是毫秒級時間,41位的長度可以表示69年的時間
- 5位datacenterId,5位workerId。10位的長度最多支持部署1024個節點
- 最后12位是毫秒內的計數,12位的計數順序號支持每個節點每毫秒產生4096個ID序列
這樣的好處是:毫秒數在高位,生成的ID整體上按時間趨勢遞增;不依賴第三方系統,穩定性和效率較高,理論上QPS約為409.6w/s(1000*2^12),并且整個分布式系統內不會產生ID碰撞;可根據自身業務靈活分配bit位。
不足就在于:強依賴機器時鐘,如果時鐘回撥,則可能導致生成ID重復。
綜上
結合數據庫和snowflake的唯一ID方案,可以參考業界較為成熟的解法:Leaf——美團點評分布式ID生成系統,并考慮到了高可用、容災、分布式下時鐘等問題。
?
6.5 數據遷移、擴容問題
當業務高速發展,面臨性能和存儲的瓶頸時,才會考慮分片設計,此時就不可避免的需要考慮歷史數據遷移的問題。一般做法是先讀出歷史數據,然后按指定的分片規則再將數據寫入到各個分片節點中。此外還需要根據當前的數據量和QPS,以及業務發展的速度,進行容量規劃,推算出大概需要多少分片(一般建議單個分片上的單表數據量不超過1000W)
如果采用數值范圍分片,只需要添加節點就可以進行擴容了,不需要對分片數據遷移。如果采用的是數值取模分片,針對數據量的遞增,可能需要動態的增加表,此時因為reHash有可能導致數據遷移問題,則考慮后期的擴容問題就相對比較麻煩。
?
6.6 外鍵約束問題
外鍵約束問題比較難解決,不能完全依賴數據庫本身來完成之前的功能。如果需要對分庫后的單表做外鍵約束,就需要分庫后每個單庫的數據是內聚的,否則就只能靠應用層的判斷,容錯方式了。
?
七. 什么時候考慮切分
下面講述一下什么時候需要考慮做數據切分。
7.1 能不切分盡量不要切分
并不是所有表都需要進行切分,主要還是看數據的增長速度。切分后會在某種程度上提升業務的復雜度,數據庫除了承載數據的存儲和查詢外,協助業務更好的實現需求也是其重要工作之一。
不到萬不得已不用輕易使用分庫分表這個大招,避免"過度設計"和"過早優化"。分庫分表之前,不要為分而分,先盡力去做力所能及的事情,例如:升級硬件、升級網絡、讀寫分離、索引優化等等。當數據量達到單表的瓶頸時候,再考慮分庫分表。
?
7.2 數據量過大,正常運維影響業務訪問
這里說的運維,指:
?
7.3 隨著業務發展,需要對某些字段垂直拆分
舉個例子,假如項目一開始設計的用戶表如下:
id?????????????????? bigint???????????? #用戶的ID name???????????????? varchar??????????? #用戶的名字 last_login_time????? datetime?????????? #最近登錄時間 personal_info??????? text?????????????? #私人信息 .....?????????????????????????????????? #其他信息字段在項目初始階段,這種設計是滿足簡單的業務需求的,也方便快速迭代開發。而當業務快速發展時,用戶量從10w激增到10億,用戶非常的活躍,每次登錄會更新 last_login_name 字段,使得 user 表被不斷update,壓力很大。而其他字段:id, name, personal_info 是不變的或很少更新的,此時在業務角度,就要將 last_login_time 拆分出去,新建一個 user_time 表。
personal_info 屬性是更新和查詢頻率較低的,并且text字段占據了太多的空間。這時候,就要對此垂直拆分出 user_ext 表了。
?
7.4 數據量快速增長
隨著業務的快速發展,單表中的數據量會持續增長,當性能接近瓶頸時,就需要考慮水平切分,做分庫分表了。此時一定要選擇合適的切分規則,提前預估好數據容量
?
7.5 安全性和可用性
雞蛋不要放在一個籃子里。在業務層面上垂直切分,將不相關的業務的數據庫分隔,因為每個業務的數據量、訪問量都不同,不能因為一個業務把數據庫搞掛而牽連到其他業務。利用垂直切分,一個數據庫出現問題,只會影響到部分業務,不會使所有的業務都癱瘓。利用水平切分,當一個數據庫出現問題時,不會影響到100%的用戶,每個庫只承擔業務的一部分數據,這樣整體的可用性就能提高。
?
八、案例分析
8.1 用戶中心業務場景
用戶中心是一個非常常見的業務,主要提供用戶注冊、登錄、查詢/修改等功能,其核心表為:
User(uid, login_name, passwd, sex, age, nickname) uid為用戶ID,? 主鍵 login_name, passwd, sex, age, nickname,? 用戶屬性任何脫離業務的架構設計都是耍流氓,在進行分庫分表前,需要對業務場景需求進行梳理:
- 用戶側:前臺訪問,訪問量較大,需要保證高可用和高一致性。主要有兩類需求:
- 用戶登錄:通過login_name/phone/email查詢用戶信息,1%請求屬于這種類型
- 用戶信息查詢:登錄之后,通過uid來查詢用戶信息,99%請求屬這種類型
- 運營側:后臺訪問,支持運營需求,按照年齡、性別、登陸時間、注冊時間等進行分頁的查詢。是內部系統,訪問量較低,對可用性、一致性的要求不高。
?
8.2 水平切分方法
當數據量越來越大時,需要對數據庫進行水平切分,上文描述的切分方法有"根據數值范圍"和"根據數值取模"。
"根據數值范圍":以主鍵uid為劃分依據,按uid的范圍將數據水平切分到多個數據庫上。例如:user-db1存儲uid范圍為0~1000w的數據,user-db2存儲uid范圍為1000w~2000wuid數據。
- 優點是:擴容簡單,如果容量不夠,只要增加新db即可。
- 不足是:請求量不均勻,一般新注冊的用戶活躍度會比較高,所以新的user-db2會比user-db1負載高,導致服務器利用率不平衡
?
"根據數值取模":也是以主鍵uid為劃分依據,按uid取模的值將數據水平切分到多個數據庫上。例如:user-db1存儲uid取模得1的數據,user-db2存儲uid取模得0的uid數據。
- 優點是:數據量和請求量分布均均勻
- 不足是:擴容麻煩,當容量不夠時,新增加db,需要rehash。需要考慮對數據進行平滑的遷移。
?
8.3 非uid的查詢方法
水平切分后,對于按uid查詢的需求能很好的滿足,可以直接路由到具體數據庫。而按非uid的查詢,例如login_name,就不知道具體該訪問哪個庫了,此時需要遍歷所有庫,性能會降低很多。
對于用戶側,可以采用"建立非uid屬性到uid的映射關系"的方案;對于運營側,可以采用"前臺與后臺分離"的方案。
8.3.1 建立非uid屬性到uid的映射關系
1)映射關系
例如:login_name不能直接定位到數據庫,可以建立login_name→uid的映射關系,用索引表或緩存來存儲。當訪問login_name時,先通過映射表查詢出login_name對應的uid,再通過uid定位到具體的庫。
映射表只有兩列,可以承載很多數據,當數據量過大時,也可以對映射表再做水平切分。這類kv格式的索引結構,可以很好的使用cache來優化查詢性能,而且映射關系不會頻繁變更,緩存命中率會很高。
2)基因法
分庫基因:假如通過uid分庫,分為8個庫,采用uid%8的方式進行路由,此時是由uid的最后3bit來決定這行User數據具體落到哪個庫上,那么這3bit可以看為分庫基因。
上面的映射關系的方法需要額外存儲映射表,按非uid字段查詢時,還需要多一次數據庫或cache的訪問。如果想要消除多余的存儲和查詢,可以通過f函數取login_name的基因作為uid的分庫基因。生成uid時,參考上文所述的分布式唯一ID生成方案,再加上最后3位bit值=f(login_name)。當查詢login_name時,只需計算f(login_name)%8的值,就可以定位到具體的庫。不過這樣需要提前做好容量規劃,預估未來幾年的數據量需要分多少庫,要預留一定bit的分庫基因。
?
8.3.2 前臺與后臺分離
對于用戶側,主要需求是以單行查詢為主,需要建立login_name/phone/email到uid的映射關系,可以解決這些字段的查詢問題。
而對于運營側,很多批量分頁且條件多樣的查詢,這類查詢計算量大,返回數據量大,對數據庫的性能消耗較高。此時,如果和用戶側公用同一批服務或數據庫,可能因為后臺的少量請求,占用大量數據庫資源,而導致用戶側訪問性能降低或超時。
這類業務最好采用"前臺與后臺分離"的方案,運營側后臺業務抽取獨立的service和db,解決和前臺業務系統的耦合。由于運營側對可用性、一致性的要求不高,可以不訪問實時庫,而是通過binlog異步同步數據到運營庫進行訪問。在數據量很大的情況下,還可以使用ES搜索引擎或Hive來滿足后臺復雜的查詢方式。
?
九、使用分庫分表中間件
站在巨人的肩膀上能省力很多,目前分庫分表已經有一些較為成熟的開源解決方案:
- sharding-jdbc(當當)
- TSharding(蘑菇街)
- Atlas(奇虎360)
- Cobar(阿里巴巴)
- MyCAT(基于Cobar)
- Oceanus(58同城)
- Vitess(谷歌)
簡單介紹其中的兩個中簡介:
Mycat
Mycat發展到現在,適用的場景已經很豐富,而且不斷有新用戶給出新的創新性的方案,以下是幾個典型的應用場景:
- 單純的讀寫分離,此時配置最為簡單,支持讀寫分離,主從切換
- 分庫分表,對于超過1000萬的表進行分片,最大支持1000億的單表分片
- 多租戶應用,每個應用一個庫,但應用程序只連接Mycat,從而不改造程序本身,實現多租戶化報表系統,借助于Mycat的分表能力,處理大規模報表的統計
- 替代Hbase,分析大數據作為海量數據實時查詢的一種簡單有效方案,比如100億條頻繁查詢的記錄需要在3秒內查詢出來結果,除了基于主鍵的查詢,還可能存在范圍查詢或其他屬性查詢,此時Mycat可能是最簡單有效的選擇.
Sharding-JDBC
當當網開發的簡單易用、輕量級的中間件。
?
一些分庫分表中間件的簡介圖:
其他相關文章:【MySQL】主從復制實現原理詳解?
? ? ? ? ? ? ? ? ? ? ? ? ?【面試題】一致性 Hash 算法是什么?它帶來了哪些問題以及解決方案
? ? ? ? ? ? ? ? ? ? ? ? ?【MySQL】MySQL的鎖與事務隔離級別詳解
? ? ? ? ? ? ? ? ? ? ? ? ?【MySQL】InnoDB行格式、數據頁結構以及索引底層原理分析
? ? ? ? ? ? ? ? ? ? ? ? ?【MySQL】MySQL的存儲引擎和索引詳解(聚集索引和非聚集索引)
? ? ? ? ? ? ? ? ? ? ? ? ?【MySQL】InnoDB存儲引擎,MyISAM存儲引擎,聚集索引,非聚集索引,主鍵索引,二級索引他們之間的關系梳理
參考資料:https://www.cnblogs.com/butterfly100/p/9034281.html
總結
以上是生活随笔為你收集整理的【MySQL】MySQL分库分表详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 001-unity2D游戏随机生成地图
- 下一篇: 删除“打开方式”里的其他程序