Mysql大數據量問題與解決

今日格言:了解了為什麼,問題就解決了一半。

Mysql 單表適合的最大數據量是多少?

我們說 Mysql 單表適合存儲的最大數據量,自然不是說能夠存儲的最大數據量,如果是說能夠存儲的最大量,那麼,如果你使用自增 ID,最大就可以存儲 2^32 或 2^64 條記錄了,這是按自增 ID 的數據類型 int 或 bigint 來計算的;如果你不使用自增 id,且沒有 id 最大值的限制,如使用足夠長度的隨機字元串,那麼能夠限制單表最大數據量的就只剩磁碟空間了。顯然我們不是在討論這個問題。

影響 Mysql 單表的最優最大數量的一個重要因素其實是索引。

我們知道 Mysql 的主要存儲引擎 InnoDB 採用 B+樹結構索引。(至於為什麼 Mysql 選擇 b+樹而不是其他數據結構來組織索引,不是本文討論的話題,之後的文章會講到。)那麼 B+樹索引是如何影響 Mysql 單表數據量的呢?

B+樹

一棵 B+樹如下所示:

B+樹

Mysql 的 B+樹索引存儲在磁碟上,Mysql 每次讀取磁碟 Page 的大小是 16KB,為了保證每次查詢的效率,需要保證每次查詢訪問磁碟的次數,一般設計為 2-3 次磁碟訪問,再多性能將嚴重不足。Mysql B+樹索引的每個節點需要存儲一個指針(8Byte)和一個鍵值(8Byte)。因此計算16KB/(8B+8B)=1K 16KB 可以存儲 1K 個節點,3 次磁碟訪問(即 B+樹 3 的深度)可以存儲 1K _ 1K _ 1K 即 10 億數據。

如果查詢依賴非主鍵索引,那麼還涉及二級索引。這樣數據量將更小。

拆分

分而治之——沒有什麼問題不能通過拆分一次來解決,不行就拆多次。

Mysql 單表存儲的數據量有限。一個解決大數據量存儲的辦法就是分庫分表。說白了就是一個資料庫一張表放不下那麼多數據,那就分多個資料庫多張表存儲。

拆分可分為垂直拆分水平拆分

垂直拆分是按照不同的表(或者 Schema)來切分到不同的資料庫(主機)之上,水平拆分則是根據表中的數據的邏輯關係,將同一個表中的數據按照某種條件拆分到多台資料庫(主機)上面或多張相同 Schema 的不同表中。

垂直拆分的最大特點就是規則簡單,實施也更為方便,尤其適合各業務之間的耦合度非常低,相互影響很小,業務邏輯非常清晰的系統。在這種系統中,可以很容易做到將不同業務模組所使用的表分拆到不同的資料庫中。根據不同的表來進行拆分,對應用程式的影響也更小,拆分規則也會比較簡單清晰。

水平拆分垂直切分相比,相對來說稍微複雜一些。因為要將同一個表中的不同數據拆分到不同的資料庫中,對於應用程式來說,拆分規則本身就較根據表名來拆分更為複雜,後期的數據維護也會更為複雜一些。

垂直拆分最直接的就是按領域拆分服務,隔離領域資料庫。如此每個庫所承擔的數據壓力就減少了。

水平拆分就是將同一個 Schema 的數據拆分到不同的庫或不同的表中,這樣每個表的數據量也將減小,查詢效率將更高效。水平拆分就涉及到表的分片規則問題。

幾種典型的分片規則包括:

  • 按照用戶 ID 求模,將數據分散到不同的資料庫,具有相同數據用戶的數據都被分散到一個庫中。
  • 按照日期,將不同月甚至日的數據分散到不同的庫中。
  • 按照某個特定的欄位求摸,或者根據特定範圍段分散到不同的庫中。

實現

門面模式——沒有什麼問題不能通過添加一個中間層來解決。

垂直拆分的一個方案就是在應用層使用多個數據源,按業務訪問不同的數據源。另外更好方案其實就是微服務化。按不同的業務領域來拆分微服務,明確領域邊界,隔離領域資料庫。這樣將對數據的存取內聚到獨立的服務之中,對外提供統一的介面。在需要同時依賴多個服務時,我們可以通過添加門面應用來組合底層服務的數據,以提供更符合上層業務需求的介面,這些服務往往更接近真實的業務。而底層的服務則是更加內聚的資源服務。

代理模式——沒有什麼問題不能通過添加一個中間層來解決。

對於水平拆分應該盡量屏蔽拆分帶來的數據訪問困惱,為了讓上層業務無需關心下層數據組織方式。水平拆分往往通過添加一個代理層來做這些事情,代理層對上提供虛擬表,這些虛擬表就像我們在單庫上設計的單表一樣;代理層對下解析和拆分執行 sql,然後按相應規則在不同的庫和表執行相應的 sql 請求,再合併數據,並將合併後的結果返回給上層調用者。

一般代理方式分為如下兩種:

  • 進程內代理

    進程內代理即將代理層嵌入到業務服務內部,攔截 sql 請求並做相應的處理。這樣的好處是簡單,但是侵入性大,且不夠靈活。

    進程內代理

  • 進程外代理

    進程外代理即將代理獨立成服務,代理真實業務服務和資料庫之間的請求。這樣是比較複雜的,需要高可用的代理服務架構。但是這樣對業務的侵入性低,且易於升級擴展。

    進程外代理

問題

分散式事務問題

什麼是分散式事務?本地事務的定義就是一系列相關的資料庫操作完成後要滿足 ACID 四大特性,而分散式事務就是將同一進程的操作放到不同的微服務進程中,即不同微服務應用進程的資料庫操作滿足事務要求,或者對不同資料庫的一系列操作需滿足事務要求。

這裡就有兩個問題需要解決。一個是因為應用的分散式造成的,一個是因為資料庫本身的分散式造成的。資料庫本身的分散式事務問題一般由資料庫自身解決,大多數分散式資料庫都可以做到一定的數據一致性保證,如 HBase 保證的強一致性,Cassandra 保證的最終一致性。

應用數據的一致性事務方案我們也可以參考分散式資料庫的實現原理來實現。業界也有很多分散式事務的解決思路,如:

  • XA 方案
  • TCC 方案
  • 本地消息表
  • 可靠消息最終一致性方案
  • 最大努力通知方案

多表 Join 問題

通過分析 Join sql,將 sql 拆分成獨立的查詢請求,然後分別執行,並將結果合併計算返回給調用者。這個地方會涉及到很多執行優化的問題。

數據統計問題

當數據被分片到不同的資料庫或不同的表中時,要對數據做一些全局的或涉及大量數據的統計時便會遇到一些問題。如求 Max,Min,Sum 等聚合問題。如果統計的數據有一定的業務規則,如只會按用戶維度去統計,如統計某個用戶的訂單量,那麼對訂單表的分片,其實可以採用按用戶 id 來分片,如此就可以解決這類統計問題。但是這種方案不通用。很多分片代理服務都需要將 sql 分片到不同的節點上去執行,然後再合併結果返回。

ID 問題

使用分庫分表之後,就無法使用 Mysql 的表自增作為 id,因為不同庫和表的自增將出現衝突的 id。解決這個問題就需要引入分散式 id 生成技術(將在以後的文章中講到)。

推薦系列:

列式存儲
時間序列資料庫(TSDB)初識與選擇
十分鐘了解 Apache Druid
Apache Druid 底層存儲設計
Apache Druid 的集群設計與工作流程

想了解更多數據存儲相關知識,請關注我的公眾號。

MageByte