正文開始
上一篇文章中,咱們介紹了資料庫層的分散的第一個起手式『 讀寫分離 』,這個方案是將寫與讀分散在不同的機器上,正常情況下,大部份的系統使用這種方案就已經可以處理很好了。
但 !
如果你已經將資料庫層與緩存層的架構都已經建立好,但還是發現有性能貧頸,那接下來才會建議使用幾個方案,因為這些方案沒用好,會衍生出非常多的問題。
本篇文章分為以下幾個章節,這些就是接下來咱們要來學的擴展法。
- 分庫
- 分表
- 分庫與分表的問題
重要 : 使用前注意事項
要使用以下的擴展方法時,先確認你的資料庫是否以下的問題是否有發生。
- 問題 1 : 單庫太大,導致硬碟空間不夠囉。
- 問題 2 : 單庫寫入量太大,導致每一次新增或更新性能非常的吃緊,感覺隨時都會上天堂。
- 問題 3 : 單表資料量太太,導致每一次操作時都非常的慢。
有以上事情發生才開始往接下來的擴展走。
沒事真的別用它們
分庫
它可以解決 問題 1 : 單庫太大,導致硬碟空間不夠囉 與 問題 2 : 單庫寫入量太大,導致每一次新增或更新性能非常的吃緊
首先第一個要介紹的就是分庫,它的基本定義如下 :
將一個大大的資料庫,依據『 規則 』拆分成小的資料庫
其中上述說的規則,在傳統上可以分為以下幾種 :
- 垂直切分 : 根據『 業務 』來拆分成多個小資料庫 ( 圖 1 所示 )。
- 水平切分 : 根據『 特性 』來拆分成多個小資料庫,例如地區 ( 圖 2 所示 )。
圖 1 : 垂直切分範例
圖 2 : 水平切分範例
這個方案基本上在業界應該算是非常常見的方案,尤其是以業務的垂直切分這個方案,整體而言還是有不少的優點,如下幾個 :
- 減少單庫的性能壓力
- 減少備份復原的時間
- 增加維護性,例如下修某個資料庫,不會影響到所有的功能。
業界的確很常使用這個方案,它們會根據業務需求來分成多個庫,可是上面不是建議沒必要,不要這樣搞嗎 ? 嗯對沒錯,但是如果是下面這種情況下,那的確可以切分 :
假設在業務擴展初出,所有資料都寫到同一個資料庫中,但後其長大後,就將不同『 單位業務 』的資料分不同資料庫,它們彼此間很獨立,非常少會一起使用
這種情況下,我覺得切分合理,而且這很常見。就算是咱們某個資料庫中還是有一堆奇奇怪怪不知道是幹啥的表,或是其它業務的表,這些都是一間公司從 0 至 1+ 時都會有的過程。
但如果是一堆需要共要在一起的業務,硬是要把他切分成不同資料庫,那我想打爆那個提出這樣切的人的頭。
分表
這種方法可以解決以下的問題 :
它可以解決問題 3 : 單表資料量太太,導致每一次操作時都非常的慢。
簡單它們的概念都 :
將一張很大的表,根據『 key 』來分成小張的表
它本質上是應用層自已手動實現,根據規則將一個表,分成多個表,這表可能在同一台機器上,或是不同機器上,應用層 sql 代碼需修改。
開始前的小提醒
分表這種擴展法是用在單表太大的情境。
假設你們的系統是聊天室這種類型的系統,然後你們會將用戶說的每一句話儲存在某一張表,那你們遲早會碰到表太大,影響查詢這種問題的。
不過先說一下,並不是一定要用這種方法來處理此情境,像以咱們公司是這樣處理的。
- 根據業務規則,來定義資料表中要儲多久的資料,例如 3 個月左右。
- 將 3 個月之前的資料全部打包壓縮丟到 aws s3 或啥的。
- 寫一個每天的排程,來定時的砍資料與移資料。
如果真的有需要去 s3 找資料,你也可以用 aws athena 去查詢資料的,雖然有點貴,但是久久才用一次,沒毛病。
上述是題外話,假設咱們真的都需要儲放在資料表中,那咱們就只能選擇『 分表與分區表 』這個方案囉。
不過說句真心話,沒必要真的不要用它,因此之後會很多問題。
拉回分表
分表的基本概念就是如下,咱們假設以 id 為 key,將這張表拆成如下圖 3 所示。分區表下篇文章會說明。
圖 3 : 分表範例
問個問題,分表有需要分到不同的機器嗎 ? 例如下圖 4 所示呢 ?
除非,你的單表真的大到,就算進行分表後,單機仍然無法負荷它的資料量,那這樣才可以考慮,不然雷會很多,因為這就代表分庫 + 分表的問題你都需要一同考慮。
圖 4 : 分表不同機器圖
要使用什麼當切分鍵 ?
分片( Sharding )就是咱們要用什麼規則來去分割資料的意思。以我們這裡來看就是咱們要用什麼欄位,來去分割資料。
先說一下,我們會很難到完美的分片,一個完美的分片可以讓我們做到以下的事情 :
- 可以平均將查詢操作,分配到每一張表。
- 可以平均的將新增、更新等操作,分配到每一張表上。
咱們很難找到一個完美的選擇,但是可以先定一個最低的標準選項 :
高基數欄位,這是最低標準
為啥 ? 因為這樣才好分割,你想想如果選擇性別男與女來當分割,那這樣你不是只能分成兩份嗎 ? 如果又滿了,你要如何處理 ?
然後這裡列一下一些常見的分片欄位選項 :
選擇 1 : 升序特性欄位
『 自動產生編號 』 或是 『 日期 』
根據 id 或日期來分區的這種都算是升序特性欄位。以 id 為例,它們的分割方式為 :
id_1_1000 : 儲放 id 編號 1 至 1000 的資料
id_1001_2000 : 儲放 id 編號 1001 至 2000 的資料
但是這事實上不能說是好選項,主要的原因在於 :
操作會壓在一張表上
因為通常新的資料者是會放在同一張表,所以大部份的操作都會壓往他身上。
選擇 2 : Hash 編號 Id
根據 id + hash 來進行切分,假設咱們有個 hash 函數如下 :
hashId = id % 10
這種做法比上述的直接編號來分割還優質點,因為它可以平均的分散到不同的表上。先說一下上述只是 hash 的簡單範例,實際上不一定是用上述方式分割。
選擇 3 : 隨機特性的欄位
像是一些沒有規則的欄位,例如信箱或是一些亂數編號這種,這種欄位分片可以平均的分散到每一張表,但是比較大的問題在於 :
查詢
由於分配是隨機的,所以查詢時不知道要去那張表找,所以只能一個一個慢慢找。
選擇 4 : 組合型的欄位
假設咱們有個資料表是專門存放聊天訊息的,有以下欄位 :
id, type , text, user_id, created_at
type : 它有 5 種類型 ( 可能是表示不同的服務 )
那這時如果我們只選擇 type,會發現它的基數太小,怎麼分就可能只有五張表,這樣還是會碰到貧頸的。
但這時咱們可以轉個想,如果一次用兩個欄位呢 ?
{ type, created_at }
例如咱們可以根據 type 與 created_at 來當分片欄位組合,這樣的話資料表會分為如下圖 5 所示。
圖 5 : 組合型範例
這樣就有幾種好處 :
- 查詢方便,如果要查某種類型的,只要去那種類型的表查就好,如果要查時間,就去該段時間查詢。
- 有將壓力分散,雖然 type 基數小,但是我們可以同過細分時間段,來減少壓力。
上面這個只是組合型的範例,不代表一定要這樣使用,這裡只是要點醒一下 :
分片欄位不一定是要一個欄位組成
分表與分庫可能會碰到的問題
問題 1 : 事務如何處理呢 ?
咱們之前的這兩篇文章中,有談到單機一致性的問題。
30-15 之資料庫層的難題 - 單機『 故障 』一致性難題 30-16 之資料庫層的難題 - 單機『 並行 』一致性難題 ( 1 ) 30-17 之資料庫層的難題 - 單機『 並行 』一致性難題 ( 2 )
而它們都是依賴 mysql 所提供的 redo log 與 undo log 還有鎖之類的功能,來達成事務的 acid 特性。
那如果這個換成『 多台 』資料庫的話,你要如何處理呢 ?
這個問題咱們會在之後的文章『 分散式事務 』的主是中來慢慢的談談。
問題 2 : Join 如何處理呢 ?
在分庫以後,基本上有以下幾種方法解決 :
- 不要用 : 自行在應用層抓一抓你要的資料,然後拼裝超來。
- 一點點的反正規化 : 也就是說在設計表時,會多加一些欄位,例如訂單與用戶這樣的組合,有可能會在訂單這個分庫的資訊上,多儲放一些用戶的資訊,這樣就不需要使用到 join。
- 統一透過中間件處理 : 有一些中間件軟體會提供多庫 join 的操作,以 mycat 來看它有提供,不過只提供兩層的 join,這個地方在之後 mycat 的文章中會提。
這裡是比較建議分庫後儘量不要用 join,因為坑會有點大,而且你儘然分庫了,你還很常需要 join,那是不是要想想,分庫的規則有問題了呢 ?
問題 3 : 用戶端要如何處理呢 ?
通常咱們在分表以後,都需要自已去指定要去那一張表來處理,但是這樣非常的麻煩,所以我們通常會使用 mysql 中間件來處理,變成如下圖 6 所示,這之後會開一篇文章來介紹中間件。
正常分表後,SQL 指定修改
SELECT * FROM table_2019_01
圖 6 : 中間件。
問題 4 : 唯一編號如何處理呢 ?
在分表或水平分庫以後,你可能還要考慮每一張表的唯一編號要如何產生,正常只有一張表時,咱們可以使用自動產生編號來建立順序的編號,但多張時就不能這樣用,因為會衝突。
基本上有以下幾種方案 :
方案 1 : UUID
簡單,但是缺點在於沒順序、空間大,這兩個都會影響性能。
在順序的編號,在查詢時可能增加不少效率。而 uuid 所佔空間比較大,會影響到所建立的 b+ 樹每個索引節點的大小,進而影響到操作性能。
方案 2 : 自動編號 + 偏移
假設你有 4 張表,那他們的生成編號為 :
A 表 : 1,5,9,13
B 表 : 2,6,10,14
C 表 : 3,7,11,15
D 表 : 4,8,12,16
但缺點就在於不好擴展。
方案 3 : redis 生成
redis 有提供一個方法 :
incr
可以使用它來產生全局的順序編號,但缺點就在於需要連 redis 處理,還要考慮它是否活這。
問題 5 : 分表分頁如何處理呢 ?
像咱們在單一表進行分頁時,通常是會下如下的指令,說要拿第 6 至 15 的資料 :
SELECT * FROM table ORDER BY time LIMIT 5,10;
但是現在一個大表變成如下 :
table = tableA + tableB + tableC
在分表的情境中,要如何處理呢 ?
首先假設如果咱們的分表是使用『 區間 』來拆分例如日期,那這種情況下,應該還是到指定的表去尋找就夠了,沒啥毛病。
但如果是用 hash id 這種來分的表呢 ?
有人會想說,那就每個表的資料都抓出來,在排序然後再抓取前 n 個值不就好呢 ? 服務會炸裂的,分表的原因就是單表太大,你還要進行排序 ( nlogn ) ?
說實話,我到還沒碰到分表然後又要分頁的情況,這時只能給點參考文件,目前我覺得寫的最完整的是這一篇文章,這個作者也是我之前有提過的『 架構師之路 』這一系列優質文的筆者,他的文章真的只能說優質上等來評價。
而至於分表分頁這問題,等未來我在這個問題有更深入的理解後,在開篇文章來談談。
結論與心得
本篇文章中,咱們討論了另外兩種資料庫層的擴展 :
- 分庫
- 分表
基本上我覺得這兩種都是沒必要,不要用的東西,因為如果你在還沒到性能貧頸時就用這幾招,你會面臨到的問題是一個大大的坑,請注意。
分庫這種場境,可以解決以下幾個問題 :
- 問題 1 : 單庫太大,導致硬碟空間不夠囉。
- 問題 2 : 單庫寫入量太大,導致每一次新增或更新性能非常的吃緊,感覺隨時都會上天堂。
而分表方面可以解決以下幾個問題 :
- 問題 3 : 單表資料量太太,導致每一次操作時都非常的慢。
雖然可以解決,但是同時也會帶來以下這些鬼問題,請注意。
- 事務如何處理 ?
- Join 如何處理呢 ?
- 用戶端要如何處理呢 ?
- 唯一編號如何處理呢 ?
- 分表分頁如何處理呢 ?
這些問題 :
都是在分庫或分表『 前 』你就要思考好
分了後,才思考,你一定死。
參考資料
- MySQL分库分表,写得太好了!
- MySql从一窍不通到入门(五)Sharding:分表、分库、分片和分区
- MySQL 分库分表方案,总结的非常好!
- MYSQL分表时需要考虑那些问题?
- 数据库水平分片心得
- 学会数据库读写分离、分表分库——用Mycat,这一篇就够了!
- [MySQL]MySQL分区与传统的分库分表
- mysql 分区的真正作用?
- 分库分表的几种常见形式以及可能遇到的难
- 分库分表时唯一ID如何生成
- mysql分区表之一:分区原理和优缺点【转】
- 业界难题-“跨库分页”的四种方案
- 为了解决分库分表后,跨库查询和分页查询的难题,基于CQRS思想,采用es作为查询的存储中心。
- 不要为了“分库分表”而“分库分表”