MySQL實戰45講學習筆記----分區表

分區表

分區表的組織形式,創建一個表t:

CREATE TABLE `t` (
  `ftime` datetime NOT NULL,
  `c` int(11) DEFAULT NULL,
  KEY (`ftime`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
PARTITION BY RANGE (YEAR(ftime))
(PARTITION p_2017 VALUES LESS THAN (2017) ENGINE = InnoDB,
 PARTITION p_2018 VALUES LESS THAN (2018) ENGINE = InnoDB,
 PARTITION p_2019 VALUES LESS THAN (2019) ENGINE = InnoDB,
PARTITION p_others VALUES LESS THAN MAXVALUE ENGINE = InnoDB);
insert into t values('2017-4-1',1),('2018-4-1',1);

圖1 表t的磁盤文件

在表t中初始化插入了兩行記錄,按照定義的分區規則,這兩行記錄分別落在p_2018和p_2019這兩個分區上。

可以看到,這個表包含了一個.frm文件和4個.ibd文件,每個分區對應一個.ibd文件。也就是說:

  • 對於引擎層來說,這是4個表;
  • 對於Server層來說,這是1個表。

你可能會覺得這兩句都是廢話。其實不然,這兩句話非常重要,可以幫我們理解分區表的執行邏輯。

分區表的引擎層行爲

舉個在分區表加間隙鎖的例子,目的是說明對於InnoDB來說,這是4個表。

圖2 分區表間隙鎖示例

初始化表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_2017、t_2018、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層行爲

如果從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語句刪除數據相比,優勢是速度快、對系統影響小。

實際上,MySQL還支持hash分區、list分區等分區方法。

實際使用時,分區表跟用戶分表比起來,有兩個繞不開的問題:一個是第一次訪問的時候需要訪問所有分區,另一個是共用MDL鎖。

因此,如果要使用分區表,就不要創建太多的分區。我見過一個用戶做了按天分區策略,然後預先創建了10年的分區。這種情況下,訪問分區表的性能自然是不好的。這裏有兩個問題需要注意:

  1. 分區並不是越細越好。實際上,單表或者單分區的數據一千萬行,只要沒有特別大的索引,對於現在的硬件能力來說都已經是小表了。

  2. 分區也不要提前預留太多,在使用之前預先創建即可。比如,如果是按月分區,每年年底時再把下一年度的12個新分區創建上即可。對於沒有數據的歷史分區,要及時的drop掉。

至於分區表的其他問題,比如查詢需要跨多個分區取數據,查詢性能就會比較慢,基本上就不是分區表本身的問題,而是數據量的問題或者說是使用方式的問題了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章