1. 程式人生 > >理論結合實踐總結分庫、分表

理論結合實踐總結分庫、分表

垂直拆分

垂直分表

把一張列比較多的表拆分成多張表,又稱為「大表拆小表」,拆分是基於關係型資料庫中的「列」(欄位)進行的。通常情況,某個表中的欄位比較多,可以新建立一種「擴充套件表」,可以按照如下原則進行垂直拆分:

  • 把不經常使用的欄位單獨放在一張表
  • 把 text, blob 等大欄位拆分出來放在附表中
  • 經常組合查詢的列放在一張表中

資料庫設計的一般建議遵循三大正規化,垂直拆分更多的時候應該在資料表設計之初就執行,但是實際使用的的場景中,等級越高的正規化設計出來的表越多,會大大的增加查詢的時間。所以當我們業務所涉及的表非常多,可以考慮使用「反正規化」即用空間換取時間,把資料冗餘在多個表中,避免多表 join 查詢

垂直分庫

垂直分庫在「微服務」盛行的今天已經變的非常普及。基本思路就是按照業務模組來劃分出不同的資料庫,而不是將所有的資料表都放在同一個資料庫中。垂直分庫的好處:

  • 系統層面「服務化」 拆分操作,能夠解決業務系統層面的耦合和效能瓶頸,有利於系統的擴充套件維護。
  • 在高併發場景下,垂直分庫一定程度上能夠突破單機的 IO、連線數以及單機硬體資源的瓶頸。

水平拆分

水平分表

水平拆分是指資料錶行的拆分,表中的行資料超過百萬級時,查詢就會變慢,此時可以通過將一張表的資料拆分成多張表來存放。簡單來說,就是將表中的不同資料行按照一定規律分佈到不同的資料表中,這樣可以降低單表資料量,優化查詢效能。常見的方式&好處:

  • 通過主鍵或者時間等欄位進行 Hash 和取模後拆分
  • 水平分表,一定程度上可以緩解查詢效能的瓶頸。但本質上這些表還是儲存在同一庫中,所以庫級別還是會有IO 瓶頸。一般很少使用。

水平分庫分表

水平分庫分表與水平分表的核心思想相同,唯一的不同就是將這些拆分出來的表儲存在不同的資料中。很多大型的網際網路公司都是採用這種做法。水平分庫分表的好處:

  • 可以進行「冷熱資料分離」
  • 有效緩解單機和單庫的效能瓶頸和壓力,突破 IO、連線數、硬體資源的瓶頸。

分庫分表的難點

不論是垂直拆分還是水平拆分,在拆分後都會帶來一些複雜的技術問題和挑戰,例如跨分片的複雜查詢,跨分片的事務等。

跨庫 join 查詢

拆分後,資料庫可能是分散式的在不同例項和不同主機上的,基於架構規範、效能、安全性等方面的考慮, 一般是禁止跨庫 join 的。首先考慮下垂直分庫的設計問題,如果不能調整,可以結合實際場景從如下方案中選擇一種:

1.全域性表

將系統只能夠所有模組都依賴到的一些表,在每個資料庫中均儲存一份。但這類資訊應該是很少發生修改的,所以幾乎不用擔心「一致性」 的問題。

2.欄位冗餘

欄位冗餘通常是為了避免 join 查詢。在電商業務中有個很常見的場景:

「訂單表」中儲存「賣家 ID」的同時,將賣家「name」作為冗餘欄位,這樣查詢訂單詳情的時候就不再需要查詢「賣家使用者表」

  • 冗餘欄位是一種「空間換時間」的體現,適用依賴欄位比較少的情況下
  • 冗餘欄位會帶來的另一個問題就是資料一致性的問題,賣家修改名稱之後訂單表中資訊是否要同步,當然是可以通過觸發器或者業務程式碼層面來保證,但只要有人為干預就導致問題更復雜

3.資料同步

資料庫 A 中表 a 和資料庫 B 中表 b 有關聯,需要定時指定表同步。但同步會對資料庫帶來一定的影響,需要在效能影響和資料實效性中取得一個平衡。

4.系統層組裝

在系統層面上,通過呼叫不同的模組的元件或者服務,獲取到資料並進行欄位拼接。但在實際使用中,通常會遇到查詢效率的問題。

  • 欄位組裝的情況下,需要先獲取「主表」資料,然後再根據關聯關係,呼叫其他模組的元件或服務來獲取依賴的其他欄位,最後將資料進行組裝。
  • 關聯關係的查詢的複雜度即使在系統層進行組裝也是能夠接受的,但是對於連線查詢並且還帶有過濾條件的情況下,想在程式碼層面進行組裝資料,程式碼會變得異常複雜
  • 通常情況下可以通過快取來避免頻繁 RPC 通訊和資料庫查詢的開銷

5.跨庫事務

將業務拆分資料庫之後,不可避免的就是「分散式事務」的問題

實踐

以上的內容都是參考網上的文章進行總結的,以下的內容是在上海 gopher meeting 上聽許老師分享學來的。

券碼設計

假如產品經理提了一個需求是,讓我們設計一個滿足如下特性的物品券碼:不可重複、不可預測、不能太長、數字碼,你會這麼設計券碼?

  • UUID/GUID X
  • MongoDB ObjectId X
  • Twitter Snowflake X

線性同餘

在這裡插入圖片描述

  • 線性同餘容易實現,生產速度快,但是弊端也很明顯,32 位的數週期最長只能到 2322^{32}
  • 精心挑選的引數,可保證在整個週期 (m) 內隨機數不重複

對於不可預測的問題,需要怎麼解決?

券碼生成的功能需要支援能夠被多個程序同時訪問,並且實現不可預測的功能,所以可以按照如下步驟執行(seed 指的是 Nj):

  • 從資料庫獲取 seed ,並持有寫鎖
  • 生成一批隨機數並 shuffle (洗牌,破壞可預測性)
  • 更新 seed

分庫分表的場景下,如何選擇 sharding key?

在分庫分表的情況下, sharding key 只有一個,但需要支援兩個獨立的查詢條件:

  • 根據 UID 查詢,比如錢包應用中檢視我的券
  • 根據券碼查,比如商戶核銷券碼

按照上面的介紹,你可能會想到,維護 A、 B 兩張內容一樣的表,一張為 UID 索引,一張為 code 核銷索引,通過冗餘能夠提高查詢效能。但維護第二份資料的伺服器成本,以及兩張表中的資料一致性,這些都使得問題更加複雜。

更簡單的實踐:

  • 引入新的欄位作為 sharding key : shardID = HASH(UID) % M
  • 券碼再包含 shardID,因為券碼不能太長,M 此處選擇的是 10000

分散式事務之使用者間轉增

分散式事務可以通過佇列來實現,這張圖是直接在現場照的,原諒我實在是不想自己再畫一遍。
在這裡插入圖片描述

這裡有兩個點需要注意:

  • 訊息服務不是事務的:訊息存了,資料庫更新可能失敗
  • 訊息服務不是事務的:訊息清理可能失敗
  • 所以分散式事務在寫程式碼的時候有很多細節需要注意

參考資料