天天看點

大廠原來都這麼對MySQL分庫分表!(下)選擇分片鍵5 分庫分表後的難題總結

選擇分片鍵

分庫分表的問題就是引入了分庫分表鍵,即分片鍵,也就是我們對資料庫做分庫分表所依據的字段。

無論是哈希拆分還是區間拆分,都要選取一個字段,這帶來一個問題:之後所有查詢都需要帶上該字段,才能找到資料所在的庫和表,否則就隻能向所有的資料庫和資料表發送查詢指令。如果拆成了 16 個庫和 64 張表,那麼一次資料的查詢會變成 16*64=1024 次查詢,查詢的性能肯定是極差的。

解決方案

比如在使用者庫中使用 ID 作為分區鍵,這時若需按昵稱查詢使用者,可按昵稱作為分區鍵再做次拆分,但這樣會極大增加存儲成本,若以後還需要按注冊時間查詢,怎麼辦呢,再做次拆分?

是以最合适的是建立一個昵稱和 ID 的映射表,在查詢時先通過昵稱查詢到 ID,再通過 ID 查詢完整資料,這個表也可是分庫分表的,也需占用一定存儲空間,但因為表中隻有兩個字段,是以相比重新做次拆分還是省不少空間。

盡量避免跨分區查詢的發生(無法完全避免)

盡量使各個分片中的資料平均

如何存儲無需分片的表

  • 每個分片中存儲一份相同的資料

    對于資料量不大且并不經常被更新的字典類表,經常需要和分區表一起關聯查詢,每個分片中存儲一份備援的資料可以更好提高查詢效率,維護其一緻性就很重要了。

  • 使用額外的節點統一存儲

    沒有備援問題,但是查詢效率較差,需要彙總

在節點上部署分片

  • 每個分片使用單一資料庫,并且資料庫名也相同

    結構也保持相同,和單一節點時的一緻

  • 将多個分片表存儲在一個資料庫中,并在表名上加入分片号字尾
  • 在一個節點中部署多個資料庫,每個資料庫包含一個切片

5 分庫分表後的難題

5.1 全局唯一ID生成方案

資料庫自增ID

使用

auto_increment_increment

auto_increment_offset

系統變量讓MySQL以期望的值和偏移量來增加auto_increment列的值。

優點

最簡單,不依賴于某節點,較普遍采用,但需要非常仔細的配置伺服器哦!

缺點

單點風險、單機性能瓶頸。不适用于一個節點包含多個分區表的場景。

資料庫叢集并設定相應步長(Flickr方案)

在一個全局資料庫節點中建立一個包含

auto_increment

列的表,應用通過該表生成唯一數字。

  • 高可用、ID較簡潔。
  • 需要單獨的資料庫叢集。

Redis緩存

避免了MySQL性能低的問題。

Snowflake(雪花算法)

  • 高性能高可用、易拓展
  • 需要獨立的叢集以及ZK

各種GUID、Random算法

  • 簡單
  • 生成ID較長,且有重複幾率

業務字段

為減少營運成本并減少額外風險,排除所有需要獨立叢集的方案,采用了帶有業務屬性的方案:

時間戳+使用者辨別碼+随機數

  • 友善、成本低
  • 基本無重複的可能
  • 自帶分庫規則,這裡的使用者辨別碼即為

    userID的後四位

    ,在查詢場景,隻需訂單号即可比對到相應庫表而無需使用者ID,隻取四位是希望訂單号盡可能短,評估後四位已足。
  • 可排序,因為時間戳在最前

  • 長度稍長,性能要比int/bigint的稍差。

事務

分庫分表後,由于資料存到了不同庫,資料庫事務管理出現困難。如果依賴資料庫本身的分布式事務管理功能去執行事務,将付出高昂的性能代價;如果由應用程式去協助控制,形成程式邏輯上的事務,又會造成程式設計方面的負擔。

比如美團,是将整個訂單領域聚合體切分,次元一緻,是以對聚合體的事務是支援的。

資料庫特性

多表的 join 在單庫時可通過一個 SQL 完成,但拆分到多個資料庫後就無法跨庫執行 SQL,好在 join 文法一般都被禁止使用,都是把兩個表的資料取出後在業務代碼裡做篩選。

分庫分表後,難免會将原本邏輯關聯性很強的資料劃分到不同的表、不同的庫上,這時,表的關聯操作将受到限制,我們無法join位于不同分庫的表,也無法join分表粒度不同的表,結果原本一次詢能夠完成的業務,可能需要多次查詢才能完成。

垂直切分後,就跟join說拜拜了;水準切分後,查詢的條件一定要在切分的次元内。

比如查詢具體某個使用者下的訂單等;

禁止不帶切分的次元的查詢,即使中間件可以支援這種查詢,可以在記憶體中組裝,但是這種需求往往不應該在線上庫查詢,或者可以通過其他方法轉換到切分的次元來實作。

在未分庫分表前,查詢資料總數時隻需 SQL 執行 count(),現在資料被分散到多個庫表,就要考慮其他方案,比方說将計數的資料單獨存儲在一張表或記錄在 Redis。

額外的資料管理和運算壓力

額外的資料管理負擔,最顯而易見的就是資料的定位問題和資料的增删改查的重複執行問題,這些都可以通過應用程式解決,但必然引起額外的邏輯運算。

例如,對于一個記錄使用者成績的使用者資料表userTable,業務要求查出成績最好的100位,在進行分表前,隻需一個order by即可。但分表後,将需要n個order by語句,分别查出每一個分表前100名使用者資料,然後再對這些資料進行合并計算,才能得出結果。

總結

并非所有表都需要水準拆分,要看增長的類型、速度,水準拆分是大招,拆分後會增加開發複雜度,不到萬不得已不用。

拆分次元的選擇很重要,要盡可能在解決拆分前問題的基礎上,便于開發。

參考

https://tech.meituan.com/2016/11/18/dianping-order-db-sharding.html

《Java工程師面試突擊第1季》