1. 程式人生 > 資訊 >全球 5G 消費者調查:中國主要城市 51% 的智慧機使用者希望使用 5G

全球 5G 消費者調查:中國主要城市 51% 的智慧機使用者希望使用 5G

在文章開頭先拋幾個問題:

(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 = '愛笑的架構師';

返回有一列叫“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到處找(騙)投資人。

一旦拿到投資人的錢,業務就要開始大規模推廣,同時系統架構也要匹配業務的快速發展。

多應用多資料庫

隨著業務推廣力度加大,資料庫終於成為了瓶頸,這個時候多個服務共享一個數據庫基本不可行了。我們需要將每個服務相關的表拆出來單獨建立一個數據庫,這其實就是“分庫”了。

單資料庫的能夠支撐的併發量是有限的,拆成多個庫可以使服務間不用競爭,提升服務的效能

分表

說完了分庫,那什麼時候分表呢?

如果系統處於高速發展階段,拿商城系統來說,一天下單量可能幾十萬,那資料庫中的訂單表增長就特別快,增長到一定階段資料庫查詢效率就會出現明顯下降。

因此,當單表資料增量過快,業界流傳是超過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

這些方案後面會寫文章專門介紹,這裡不再展開。