我們爲什麼要分庫分表?
在文章開頭先拋幾個問題:
(1)什麼時候才需要分庫分表呢?我們的評判標準是什麼?
(2)一張表存儲了多少數據的時候,才需要考慮分庫分表?
(3)數據增長速度很快,每天產生多少數據,才需要考慮做分庫分表?
這些問題你都搞清楚了嗎?相信看完這篇文章會有答案。
爲什麼要分庫分表?
首先回答一下爲什麼要分庫分表,答案很簡單:數據庫出現性能瓶頸
。用大白話來說就是數據庫快扛不住了。
數據庫出現性能瓶頸,對外表現有幾個方面:
-
大量請求阻塞
在高併發場景下,大量請求都需要操作數據庫,導致連接數不夠了,請求處於阻塞狀態。
-
SQL 操作變慢
如果數據庫中存在一張上億數據量的表,一條 SQL 沒有命中索引會全表掃描,這個查詢耗時會非常久。
-
存儲出現問題
業務量劇增,單庫數據量越來越大,給存儲造成巨大壓力。
從機器的角度看,性能瓶頸無非就是 CPU、內存、磁盤、網絡這些,要解決性能瓶頸最簡單粗暴的辦法就是提升機器性能,但是通過這種方法成本和收益投入比往往又太高了,不划算,所以重點還是要從軟件角度入手。
數據庫相關優化方案
數據庫優化方案很多,主要分爲兩大類:軟件層面、硬件層面。
軟件層面包括:SQL 調優、表結構優化、讀寫分離、數據庫集羣、分庫分表等;
硬件層面主要是增加機器性能。
SQL 調優
SQL 調優往往是解決數據庫問題的第一步,往往投入少部分精力就能獲得較大的收益。
SQL 調優主要目的是儘可能的讓那些慢 SQL 變快,手段其實也很簡單就是讓 SQL 執行儘量命中索引。
開啓慢 SQL 記錄
如果你使用的是 Mysql,需要在 Mysql 配置文件中配置幾個參數即可。
slow_query_log=on
long_query_time=1
slow_query_log_file=/path/to/log
調優的工具
常常會用到 explain 這個命令來查看 SQL 語句的執行計劃,通過觀察執行結果很容易就知道該 SQL 語句是不是全表掃描、有沒有命中索引。
select id, age, gender from user where name = 'AAA';
返回有一列叫 “type”,常見取值有:
ALL、index、range、 ref、eq_ref、const、system、NULL(從左到右,性能從差到好)
ALL 代表這條 SQL 語句全表掃描了,需要優化。一般來說需要達到 range 級別及以上。
表結構優化
以一個場景舉例說明:
“user” 表中有 user_id、nickname 等字段,“order” 表中有 order_id、user_id 等字段,如果想拿到用戶暱稱怎麼辦?一般情況是通過 join 關聯表操作,在查詢訂單表時關聯查詢用戶表,從而獲取導用戶暱稱。
但是隨着業務量增加,訂單表和用戶表肯定也是暴增,這時候通過兩個表關聯數據就比較費力了,爲了取一個暱稱字段而不得不關聯查詢幾十上百萬的用戶表,其速度可想而知。
這個時候可以嘗試將 nickname 這個字段加到 order 表中(order_id、user_id、nickname),這種做法通常叫做數據庫表冗餘字段。這樣做的好處展示訂單列表時不需要再關聯查詢用戶表了。
冗餘字段的做法也有一個弊端,如果這個字段更新會同時涉及到多個表的更新,因此在選擇冗餘字段時要儘量選擇不經常更新的字段。
架構優化
當單臺數據庫實例扛不住,我們可以增加實例組成集羣對外服務。
當發現讀請求明顯多於寫請求時,我們可以讓主實例負責寫,從實例對外提供讀的能力;
如果讀實例壓力依然很大,可以在數據庫前面加入緩存如 redis,讓請求優先從緩存取數據減少數據庫訪問。
緩存分擔了部分壓力後,數據庫依然是瓶頸,這個時候就可以考慮分庫分表的方案了,後面會詳細介紹。
硬件優化
硬件成本非常高,一般來說不可能遇到數據庫性能瓶頸就去升級硬件。
在前期業務量比較小的時候,升級硬件數據庫性能可以得到較大提升;但是在後期,升級硬件得到的收益就不那麼明顯了。
分庫分表詳解
下面我們以一個商城系統爲例逐步講解數據庫是如何一步步演進。
單應用單數據庫
在早期創業階段想做一個商城系統,基本就是一個系統包含多個基礎功能模塊,最後打包成一個 war 包部署,這就是典型的單體架構應用。
商城項目使用單數據庫
如上圖,商城系統包括主頁 Portal 模板、用戶模塊、訂單模塊、庫存模塊等,所有的模塊都共有一個數據庫,通常數據庫中有非常多的表。
因爲用戶量不大,這樣的架構在早期完全適用,開發者可以拿着 demo 到處找(騙)投資人。
一旦拿到投資人的錢,業務就要開始大規模推廣,同時系統架構也要匹配業務的快速發展。
多應用單數據庫
在前期爲了搶佔市場,這一套系統不停地迭代更新,代碼量越來越大,架構也變得越來越臃腫,現在隨着系統訪問壓力逐漸增加,系統拆分就勢在必行了。
爲了保證業務平滑,系統架構重構也是分了幾個階段進行。
第一個階段將商城系統單體架構按照功能模塊拆分爲子服務,比如:Portal 服務、用戶服務、訂單服務、庫存服務等。
多應用單數據庫
如上圖,多個服務共享一個數據庫,這樣做的目的是底層數據庫訪問邏輯可以不用動,將影響降到最低。
多應用多數據庫
隨着業務推廣力度加大,數據庫終於成爲了瓶頸,這個時候多個服務共享一個數據庫基本不可行了。我們需要將每個服務相關的表拆出來單獨建立一個數據庫,這其實就是 “分庫” 了。
單數據庫的能夠支撐的併發量是有限的,拆成多個庫可以使服務間不用競爭,提升服務的性能。
多應用多數據庫
如上圖,從一個大的數據中分出多個小的數據庫,每個服務都對應一個數據庫,這就是系統發展到一定階段必要要做的 “分庫” 操作。
現在非常火的微服務架構也是一樣的,如果只拆分應用不拆分數據庫,不能解決根本問題,整個系統也很容易達到瓶頸。
分表
說完了分庫,那什麼時候分表呢?
如果系統處於高速發展階段,拿商城系統來說,一天下單量可能幾十萬,那數據庫中的訂單表增長就特別快,增長到一定階段數據庫查詢效率就會出現明顯下降。
因此,當單表數據增量過快,業界流傳是超過 500 萬的數據量就要考慮分表了。當然 500 萬隻是一個經驗值,大家可以根據實際情況做出決策。
那如何分表呢?
分表有幾個維度,一是水平切分和垂直切分,二是單庫內分表和多庫內分表。
水平拆分和垂直拆分
就拿用戶表(user)來說,表中有 7 個字段:id,name,age,sex,nickname,description,如果 nickname 和 description 不常用,我們可以將其拆分爲另外一張表:用戶詳細信息表,這樣就由一張用戶表拆分爲了用戶基本信息表 + 用戶詳細信息表,兩張表結構不一樣相互獨立。但是從這個角度來看垂直拆分並沒有從根本上解決單表數據量過大的問題,因此我們還是需要做一次水平拆分。
拆分表
還有一種拆分方法,比如表中有一萬條數據,我們拆分爲兩張表,id 爲奇數的:1,3,5,7…… 放在 user1, id 爲偶數的:2,4,6,8…… 放在 user2 中,這樣的拆分辦法就是水平拆分了。
水平拆分的方式也很多,除了上面說的按照 id 拆表,還可以按照時間維度取拆分,比如訂單表,可以按每日、每月等進行拆分。
-
每日表:只存儲當天的數據。
-
每月表:可以起一個定時任務將前一天的數據全部遷移到當月表。
-
歷史表:同樣可以用定時任務把時間超過 30 天的數據遷移到 history 表。
總結一下水平拆分和垂直拆分的特點:
-
垂直切分:基於表或字段劃分,表結構不同。
-
水平切分:基於數據劃分,表結構相同,數據不同。
單庫內拆分和多庫拆分
拿水平拆分爲例,每張表都拆分爲了多個子表,多個子表存在於同一數據庫中。比如下面用戶表拆分爲用戶 1 表、用戶 2 表。
單庫拆分
在一個數據庫中將一張表拆分爲幾個子表在一定程度上可以解決單表查詢性能的問題,但是也會遇到一個問題:單數據庫存儲瓶頸。
所以在業界用的更多的還是將子表拆分到多個數據庫中。比如下圖中,用戶表拆分爲兩個子表,兩個子表分別存在於不同的數據庫中。
多庫拆分
一句話總結:分表主要是爲了減少單張表的大小,解決單表數據量帶來的性能問題。
分庫分錶帶來的複雜性
既然分庫分表這麼好,那我們是不是在項目初期就應該採用這種方案呢?不要激動,冷靜一下,分庫分表的確解決了很多問題,但是也給系統帶來了很多複雜性,下面簡要說一說。
(1)跨庫關聯查詢
在單庫未拆分表之前,我們可以很方便使用 join 操作關聯多張表查詢數據,但是經過分庫分表後兩張表可能都不在一個數據庫中,如何使用 join 呢?
有幾種方案可以解決:
-
字段冗餘:把需要關聯的字段放入主表中,避免 join 操作;
-
數據抽象:通過 ETL 等將數據匯合聚集,生成新的表;
-
全局表:比如一些基礎表可以在每個數據庫中都放一份;
-
應用層組裝:將基礎數據查出來,通過應用程序計算組裝;
(2)分佈式事務
單數據庫可以用本地事務搞定,使用多數據庫就只能通過分佈式事務解決了。
常用解決方案有:基於可靠消息(MQ)的解決方案、兩階段事務提交、柔性事務等。
(3)排序、分頁、函數計算問題
在使用 SQL 時 order by, limit 等關鍵字需要特殊處理,一般來說採用分片的思路:
先在每個分片上執行相應的函數,然後將各個分片的結果集進行彙總和再次計算,最終得到結果。
(4)分佈式 ID
如果使用 Mysql 數據庫在單庫單表可以使用 id 自增作爲主鍵,分庫分表了之後就不行了,會出現 id 重複。
常用的分佈式 ID 解決方案有:
-
UUID
-
基於數據庫自增單獨維護一張 ID 表
-
號段模式
-
Redis 緩存
-
雪花算法(Snowflake)
-
百度 uid-generator
-
美團 Leaf
-
滴滴 Tinyid
這些方案後面會寫文章專門介紹,這裏不再展開。
(5)多數據源
分庫分表之後可能會面臨從多個數據庫或多個子表中獲取數據,一般的解決思路有:客戶端適配和代理層適配。
業界常用的中間件有:
-
shardingsphere(前身 sharding-jdbc)
-
Mycat
總結
如果出現數據庫問題不要着急分庫分表,先看一下使用常規手段是否能夠解決。
分庫分表會給系統帶來巨大的複雜性,不是萬不得已建議不要提前使用。作爲系統架構師可以讓系統靈活性和可擴展性強,但是不要過度設計和超前設計。在這一點上,架構師一定要有前瞻性,提前做好預判。大家學會了嗎?
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/bY60tUI0ZOVcQOYbNLAhyw