我常常被問到這樣一個問題:分區表有什麼問題,爲何公司規範不讓使用分區表呢?今
天,咱們就來聊聊分區表的使用行爲,而後再一塊兒回答這個問題。session
爲了說明分區表的組織形式,我先建立一個表 t:函數
圖 1 表 t 的磁盤文件性能
我在表 t 中初始化插入了兩行記錄,按照定義的分區規則,這兩行記錄分別落在 p_2018
和 p_2019 這兩個分區上。server
能夠看到,這個表包含了一個.frm 文件和 4 個.ibd 文件,每一個分區對應一個.ibd 文件。也
就是說:中間件
對於引擎層來講,這是 4 個表;
對於 Server 層來講,這是 1 個表。索引
你可能會以爲這兩句都是廢話。其實否則,這兩句話很是重要,能夠幫咱們理解分區表的
執行邏輯。ssl
我先給你舉個在分區表加間隙鎖的例子,目的是說明對於 InnoDB 來講,這是 4 個表。開發
圖 2 分區表間隙鎖示例rem
這裏順便複習一下,我在第 21 篇文章和你介紹的間隙鎖加鎖規則。字符串
咱們初始化表 t 的時候,只插入了兩行數據, ftime 的值分別是,‘2017-4-1’
和’2018-4-1’ 。session A 的 select 語句對索引 ftime 上這兩個記錄之間的間隙加了
鎖。若是是一個普通表的話,那麼 T1 時刻,在表 t 的 ftime 索引上,間隙和加鎖狀態應
該是圖 3 這樣的。
圖 3 普通表的加鎖範圍
也就是說,‘2017-4-1’ 和’2018-4-1’ 這兩個記錄之間的間隙是會被鎖住的。那麼,
sesion B 的兩條插入語句應該都要進入鎖等待狀態。
可是,從上面的實驗效果能夠看出,session B 的第一個 insert 語句是能夠執行成功的。
這是由於,對於引擎來講,p_2018 和 p_2019 是兩個不一樣的表,也就是說 2017-4-1 的
下一個記錄並非 2018-4-1,而是 p_2018 分區的 supremum。因此 T1 時刻,在表 t
的 ftime 索引上,間隙和加鎖的狀態實際上是圖 4 這樣的:
圖 4 分區表 t 的加鎖範圍
因爲分區表的規則,session A 的 select 語句其實只操做了分區 p_2018,所以加鎖範圍
就是圖 4 中深綠色的部分。
因此,session B 要寫入一行 ftime 是 2018-2-1 的時候是能夠成功的,而要寫入 2017-
12-1 這個記錄,就要等 session A 的間隙鎖。
圖 5 就是這時候的 show engine innodb status 的部分結果。
圖 5 session B 被鎖住信息
看完 InnoDB 引擎的例子,咱們再來一個 MyISAM 分區表的例子。
我首先用 alter table t engine=myisam,把表 t 改爲 MyISAM 表;而後,我再用下面這
個例子說明,對於 MyISAM 引擎來講,這是 4 個表。
圖 6 用 MyISAM 表鎖驗證
在 session A 裏面,我用 sleep(100) 將這條語句的執行時間設置爲 100 秒。因爲
MyISAM 引擎只支持表鎖,因此這條 update 語句會鎖住整個表 t 上的讀。
但咱們看到的結果是,session B 的第一條查詢語句是能夠正常執行的,第二條語句才進
入鎖等待狀態。
這正是由於 MyISAM 的表鎖是在引擎層實現的,session A 加的表鎖,實際上是鎖在分區
p_2018 上。所以,只會堵住在這個分區上執行的查詢,落到其餘分區的查詢是不受影響
的。
看到這裏,你可能會說,分區表看來還不錯嘛,爲何不讓用呢?咱們使用分區表的一個
重要緣由就是單表過大。那麼,若是不使用分區表的話,咱們就是要使用手動分表的方
式。
好比,按照年份來劃分,咱們就分別建立普通表 t_201七、t_201八、t_2019 等等。手工分
表的邏輯,也是找到須要更新的全部分表,而後依次執行更新。在性能上,這和分區表並
沒有實質的差異。
分區表和手工分表,一個是由 server 層來決定使用哪一個分區,一個是由應用層代碼來決定
使用哪一個分表。所以,從引擎層看,這兩種方式也是沒有差異的。
其實這兩個方案的區別,主要是在 server 層上。從 server 層看,咱們就不得不提到分區
表一個被廣爲詬病的問題:打開表的行爲。
每當第一次訪問一個分區表的時候,MySQL 須要把全部的分區都訪問一遍。一個典型的
報錯狀況是這樣的:若是一個分區表的分區不少,好比超過了 1000 個,而 MySQL 啓動
的時候,open_files_limit 參數使用的是默認值 1024,那麼就會在訪問這個表的時候,由
於須要打開全部的文件,致使打開表文件的個數超過了上限而報錯。
下圖就是我建立的一個包含了不少分區的表 t_myisam,執行一條插入語句後報錯的情
況。
圖 7 insert 語句報錯
能夠看到,這條 insert 語句,明顯只須要訪問一個分區,但語句卻沒法執行。
這時,你必定從表名猜到了,這個表我用的是 MyISAM 引擎。是的,由於使用 InnoDB
引擎的話,並不會出現這個問題。
MyISAM 分區表使用的分區策略,咱們稱爲通用分區策略(generic partitioning),每
次訪問分區都由 server 層控制。通用分區策略,是 MySQL 一開始支持分區表的時候就存
在的代碼,在文件管理、表管理的實現上很粗糙,所以有比較嚴重的性能問題。
從 MySQL 5.7.9 開始,InnoDB 引擎引入了本地分區策略(native partitioning)。這個
策略是在 InnoDB 內部本身管理打開分區的行爲。
MySQL 從 5.7.17 開始,將 MyISAM 分區表標記爲即將棄用 (deprecated),意思是「從
這個版本開始不建議這麼使用,請使用替代方案。在未來的版本中會廢棄這個功能」。
從 MySQL 8.0 版本開始,就不容許建立 MyISAM 分區表了,只容許建立已經實現了本地
分區策略的引擎。目前來看,只有 InnoDB 和 NDB 這兩個引擎支持了本地分區策略。
接下來,咱們再看一下分區表在 server 層的行爲。
若是從 server 層看的話,一個分區表就只是一個表。
這句話是什麼意思呢?接下來,我就用下面這個例子來和你說明。如圖 8 和圖 9 所示,分
別是這個例子的操做序列和執行結果圖。
圖 8 分區表的 MDL 鎖
圖 9 show processlist 結果
能夠看到,雖然 session B 只須要操做 p_2107 這個分區,可是因爲 session A 持有整個
表 t 的 MDL 鎖,就致使了 session B 的 alter 語句被堵住。
這也是 DBA 同窗常常說的,分區表,在作 DDL 的時候,影響會更大。若是你使用的是普
通分表,那麼當你在 truncate 一個分表的時候,確定不會跟另一個分表上的查詢語句,
出現 MDL 鎖衝突。
到這裏咱們小結一下:
1. MySQL 在第一次打開分區表的時候,須要訪問全部的分區;
2. 在 server 層,認爲這是同一張表,所以全部分區共用同一個 MDL 鎖;
3. 在引擎層,認爲這是不一樣的表,所以 MDL 鎖以後的執行過程,會根據分區表規則,只
訪問必要的分區。
而關於「必要的分區」的判斷,就是根據 SQL 語句中的 where 條件,結合分區規則來實
現的。好比咱們上面的例子中,where ftime=‘2018-4-1’,根據分區規則 year 函數算
出來的值是 2018,那麼就會落在 p_2019 這個分區。
可是,若是這個 where 條件改爲 where ftime>=‘2018-4-1’,雖然查詢結果相同,但
是這時候根據 where 條件,就要訪問 p_2019 和 p_others 這兩個分區。
若是查詢語句的 where 條件中沒有分區 key,那就只能訪問全部分區了。固然,這並非
分區表的問題。即便是使用業務分表的方式,where 條件中沒有使用分表的 key,也必須
訪問全部的分表。
咱們已經理解了分區表的概念,那麼什麼場景下適合使用分區表呢?
分區表的一個顯而易見的優點是對業務透明,相對於用戶分表來講,使用分區表的業務代
碼更簡潔。還有,分區表能夠很方便的清理歷史數據。
若是一項業務跑的時間足夠長,每每就會有根據時間刪除歷史數據的需求。這時候,按照
時間分區的分區表,就能夠直接經過 alter table t drop partition …這個語法刪掉分區,
從而刪掉過時的歷史數據。
這個 alter table t drop partition …操做是直接刪除分區文件,效果跟 drop 普通表類
似。與使用 delete 語句刪除數據相比,優點是速度快、對系統影響小。
這篇文章,我主要和你介紹的是 server 層和引擎層對分區表的處理方式。我但願經過這些
介紹,你可以對是否選擇使用分區表,有更清晰的想法。
須要注意的是,我是以範圍分區(range)爲例和你介紹的。實際上,MySQL 還支持
hash 分區、list 分區等分區方法。你能夠在須要用到的時候,再翻翻手冊。
實際使用時,分區表跟用戶分表比起來,有兩個繞不開的問題:一個是第一次訪問的時候
須要訪問全部分區,另外一個是共用 MDL 鎖。
所以,若是要使用分區表,就不要建立太多的分區。我見過一個用戶作了按天分區策略,
而後預先建立了 10 年的分區。這種狀況下,訪問分區表的性能天然是很差的。這裏有兩
個問題須要注意:
1. 分區並非越細越好。實際上,單表或者單分區的數據一千萬行,只要沒有特別大的索
引,對於如今的硬件能力來講都已是小表了。
2. 分區也不要提早預留太多,在使用以前預先建立便可。好比,若是是按月分區,每一年年
底時再把下一年度的 12 個新分區建立上便可。對於沒有數據的歷史分區,要及時的
drop 掉。
至於分區表的其餘問題,好比查詢須要跨多個分區取數據,查詢性能就會比較慢,基本上
就不是分區表自己的問題,而是數據量的問題或者說是使用方式的問題了。
固然,若是你的團隊已經維護了成熟的分庫分表中間件,用業務分表,對業務開發同窗沒
有額外的複雜性,對 DBA 也更直觀,天然是更好的。
最後,我給你留下一個思考題吧。
咱們舉例的表中沒有用到自增主鍵,假設如今要建立一個自增字段 id。MySQL 要求分區
表中的主鍵必須包含分區字段。若是要在表 t 的基礎上作修改,你會怎麼定義這個表的主
鍵呢?爲何這麼定義呢?
你能夠把你的結論和分析寫在留言區,我會在下一篇文章的末尾和你討論這個問題。感謝
你的收聽,也歡迎你把這篇文章分享給更多的朋友一塊兒閱讀。
上篇文章後面還不夠多,可能不少同窗還沒來記得看吧,咱們就等後續有更多留言的時
候,再補充本期的「上期問題時間」吧。
@夾心麪包 提到了在 grant 的時候是支持通配符的:"_"表示一個任意字符,「%」表示任意字符串。這個技巧在一個分庫分表方案裏面,同一個分庫上有多個 db 的時候,是挺方便的。不過我我的認爲,權限賦值的時候,控制的精確性仍是要優先考慮的。