Oracle在併購Sun後對於MySQL的態度使人尋味。在新發布的MySQL 5.5中帶來了許多加強的功能,在這篇文章中,咱們將解釋一下MySQL 5.5分區功能的加強特性。html
MySQL 5.5的發佈帶來了許多加強的功能,雖然已經報道了不少加強功能,如半同步複製,但你們卻忽略了分區方面的加強,有時甚至還對其真正意義產生了誤解,在這篇文章中,咱們但願解釋一下這些很酷的加強,特別是咱們大多數人尚未徹底理解的地方。51CTO向您推薦《MySQL數據庫入門與精通教程》。mysql
圖 1 你們還沒注意到我MySQL的分區功能也很強了哦算法
非整數列分區sql
任何使用過度區的人應該都遇到過很多問題,特別是面對非整數列分區時,MySQL 5.1只能處理整數列分區,若是你想在日期或字符串列上進行分區,你不得不使用函數對其進行轉換。數據庫
MySQL 5.5中新增了兩類分區方法,RANG和LIST分區法,同時在新的函數中增長了一個COLUMNS關鍵詞。咱們假設有這樣一個表:express
CREATE TABLE expenses ( expense_date DATE NOT NULL, category VARCHAR(30), amount DECIMAL (10,3) );
若是你想使用MySQL 5.1中的分區類型,那你必須將類型轉換成整數,須要使用一個額外的查找表,到了MySQL 5.5中,你能夠不用再進行類型轉換了,如:函數
ALTER TABLE expenses PARTITION BY LIST COLUMNS (category) ( PARTITION p01 VALUES IN ( 'lodging', 'food'), PARTITION p02 VALUES IN ( 'flights', 'ground transportation'), PARTITION p03 VALUES IN ( 'leisure', 'customer entertainment'), PARTITION p04 VALUES IN ( 'communications'), PARTITION p05 VALUES IN ( 'fees') );
這樣的分區語句除了更加易讀外,對數據的組織和管理也很是清晰,上面的例子只對category列進行分區。工具
在MySQL 5.1中使用分區另外一個讓人頭痛的問題是date類型(即日期列),你不能直接使用它們,必須使用YEAR或TO_DAYS轉換這些列,如:性能
/* 在MySQL 5.1中*/ CREATE TABLE t2 ( dt DATE ) PARTITION BY RANGE (TO_DAYS(dt)) ( PARTITION p01 VALUES LESS THAN (TO_DAYS('2007-01-01')), PARTITION p02 VALUES LESS THAN (TO_DAYS('2008-01-01')), PARTITION p03 VALUES LESS THAN (TO_DAYS('2009-01-01')), PARTITION p04 VALUES LESS THAN (MAXVALUE)); SHOW CREATE TABLE t2 \G *************************** 1. row *************************** Table: t2 Create Table: CREATE TABLE `t2` ( `dt` date DEFAULT NULL ) ENGINE=MyISAM DEFAULT CHARSET=latin1 /*!50100 PARTITION BY RANGE (TO_DAYS(dt)) (PARTITION p01 VALUES LESS THAN (733042) ENGINE = MyISAM, PARTITION p02 VALUES LESS THAN (733407) ENGINE = MyISAM, PARTITION p03 VALUES LESS THAN (733773) ENGINE = MyISAM, PARTITION p04 VALUES LESS THAN MAXVALUE ENGINE = MyISAM) */
看上去很是糟糕,固然也有變通辦法,但麻煩確實很多。使用YEAR或TO_DAYS定義一個分區的確讓人費解,查詢時不得不使用赤裸列,由於加了函數的查詢不能識別分區。測試
但在MySQL 5.5中狀況發生了很大的變化,如今在日期列上能夠直接分區,而且方法也很簡單。
/*在MySQL 5.5中*/ CREATE TABLE t2 ( dt DATE ) PARTITION BY RANGE COLUMNS (dt) ( PARTITION p01 VALUES LESS THAN ('2007-01-01'), PARTITION p02 VALUES LESS THAN ('2008-01-01'), PARTITION p03 VALUES LESS THAN ('2009-01-01'), PARTITION p04 VALUES LESS THAN (MAXVALUE)); SHOW CREATE TABLE t2 \G *************************** 1. row *************************** Table: t2 Create Table: CREATE TABLE `t2` ( `dt` date DEFAULT NULL ) ENGINE=MyISAM DEFAULT CHARSET=latin1 /*!50500 PARTITION BY RANGE COLUMNS(dt) (PARTITION p01 VALUES LESS THAN ('2007-01-01') ENGINE = MyISAM, PARTITION p02 VALUES LESS THAN ('2008-01-01') ENGINE = MyISAM, PARTITION p03 VALUES LESS THAN ('2009-01-01') ENGINE = MyISAM, PARTITION p04 VALUES LESS THAN (MAXVALUE) ENGINE = MyISAM) */
在這裏,經過函數定義和經過列查詢之間沒有衝突,由於是按列定義的,咱們在定義中插入的值是保留的。
多列分區
COLUMNS關鍵字如今容許字符串和日期列做爲分區定義列,同時還容許使用多個列定義一個分區,你可能在官方文檔中已經看到了一些例子,如:
CREATE TABLE p1 ( a INT, b INT, c INT ) PARTITION BY RANGE COLUMNS (a,b) ( PARTITION p01 VALUES LESS THAN (10,20), PARTITION p02 VALUES LESS THAN (20,30), PARTITION p03 VALUES LESS THAN (30,40), PARTITION p04 VALUES LESS THAN (40,MAXVALUE), PARTITION p05 VALUES LESS THAN (MAXVALUE,MAXVALUE) ); CREATE TABLE p2 ( a INT, b INT, c INT ) PARTITION BY RANGE COLUMNS (a,b) ( PARTITION p01 VALUES LESS THAN (10,10), PARTITION p02 VALUES LESS THAN (10,20), PARTITION p03 VALUES LESS THAN (10,30), PARTITION p04 VALUES LESS THAN (10,MAXVALUE), PARTITION p05 VALUES LESS THAN (MAXVALUE,MAXVALUE) )
一樣還有PARTITION BY RANGE COLUMNS (a,b,c)等其它例子。因爲我很長時間都在使用MySQL 5.1的分區,我對多列分區的含義不太瞭解,LESS THAN (10,10)是什麼意思?若是下一個分區是LESS THAN (10,20)會發生什麼?相反,若是是(20,30)又會如何?
全部這些問題都須要一個答案,在回答以前,他們須要更好地理解咱們在作什麼。
開始時可能有些混亂,當全部分區有一個不一樣範圍的值時,實際上,它只是在表的一個列上進行了分區,但事實並不是如此,在下面的例子中:
CREATE TABLE p1_single ( a INT, b INT, c INT ) PARTITION BY RANGE COLUMNS (a) ( PARTITION p01 VALUES LESS THAN (10), PARTITION p02 VALUES LESS THAN (20), PARTITION p03 VALUES LESS THAN (30), PARTITION p04 VALUES LESS THAN (40), PARTITION p05 VALUES LESS THAN (MAXVALUE) );
它和前面的表p1不同,若是你在表p1中插入(10,1,1),它將會進入第一個分區,相反,在表p1_single中,它將會進入第二個分區,其緣由是(10,1)小於(10,10),若是你僅僅關注第一個值,你尚未意識到你在比較一個元組,而不是一個單一的值。
如今咱們來分析一下最難懂的地方,當你須要肯定某一行應該放在哪裏時會發生什麼?你是如何肯定相似(10,9) < (10,10)這種運算的值的?答案其實很簡單,當你對它們進行排序時,使用相同的方法計算兩條記錄的值。
a=10 b=9 (a,b) < (10,10) ? # evaluates to: (a < 10) OR ((a = 10) AND ( b < 10)) # which translates to: (10 < 10) OR ((10 = 10) AND ( 9 < 10))
若是有三列,表達式會更長,但不會更復雜。你首先在第一個項目上測試小於運算,若是有兩個或更多的分區與之匹配,接着就測試第二個項目,若是不止一個候選分區,那還須要測試第三個項目。
下圖所顯示的內容表示將遍歷三條記錄插入到使用如下代碼定義的分區中:
(10,10),
(10,20),
(10,30),
(10, MAXVALUE)
圖 2 元組比較。當第一個值小於分區定義的第一個範圍時,那麼該行將屬於這裏了。
圖 3 元組比較。當第一個值等於分區定義的第一個範圍,咱們須要比較第二個項目,若是它小於第二個範圍,那麼該行將屬於這裏了。
圖 4 元組比較。當第一個值和第二個值等於他們對應的範圍時,若是元組不小於定義的範圍,那麼它就不屬於這裏,繼續下一步。
圖 5 元組比較。在下一個範圍時,第一個項目是等於,第二個項目是小於,所以元組更小,那麼該行就屬於這裏了。
在這些圖的幫助下,咱們對插入一條記錄到多列分區表的步驟有了更深的瞭解,這些都是理論上的,爲了幫助你更好地掌握新功能,咱們再來看一個更高級一點的例子,對於比較務實的讀者更有意義,下面是表的定義腳本:
CREATE TABLE employees ( emp_no int(11) NOT NULL, birth_date date NOT NULL, first_name varchar(14) NOT NULL, last_name varchar(16) NOT NULL, gender char(1) DEFAULT NULL, hire_date date NOT NULL ) ENGINE=MyISAM PARTITION BY RANGE COLUMNS(gender,hire_date) (PARTITION p01 VALUES LESS THAN ('F','1990-01-01') , PARTITION p02 VALUES LESS THAN ('F','2000-01-01') , PARTITION p03 VALUES LESS THAN ('F',MAXVALUE) , PARTITION p04 VALUES LESS THAN ('M','1990-01-01') , PARTITION p05 VALUES LESS THAN ('M','2000-01-01') , PARTITION p06 VALUES LESS THAN ('M',MAXVALUE) , PARTITION p07 VALUES LESS THAN (MAXVALUE,MAXVALUE)
和上面的例子不一樣,這個例子更好理解,第一個分區用來存儲僱傭於1990年之前的女職員,第二個分區存儲股用於1990-2000年之間的女職員,第三個分區存儲全部剩下的女職員。對於分區p04到p06,咱們策略是同樣的,只不過存儲的是男職員。最後一個分區是控制狀況。
看完後你可能要問,我怎麼知道某一行存儲在那個分區中的?有兩個辦法,第一個辦法是使用與分區定義相同的條件做爲查詢條件進行查詢。
SELECT CASE WHEN gender = 'F' AND hire_date < '1990-01-01' THEN 'p1' WHEN gender = 'F' AND hire_date < '2000-01-01' THEN 'p2' WHEN gender = 'F' AND hire_date < '2999-01-01' THEN 'p3' WHEN gender = 'M' AND hire_date < '1990-01-01' THEN 'p4' WHEN gender = 'M' AND hire_date < '2000-01-01' THEN 'p5' WHEN gender = 'M' AND hire_date < '2999-01-01' THEN 'p6' ELSE 'p7' END as p, COUNT(*) AS rows FROM employees GROUP BY p; +------+-------+ | p | rows | +------+-------+ | p1 | 66212 | | p2 | 53832 | | p3 | 7 | | p4 | 98585 | | p5 | 81382 | | p6 | 6 | +------+-------+
若是表是MyISAM或ARCHIVE,你能夠信任由INFORMATION_SCHEMA提供的統計信息。
SELECT partition_name part, partition_expression expr, partition_description descr, table_rows FROM INFORMATION_SCHEMA.partitions WHERE TABLE_SCHEMA = schema() AND TABLE_NAME='employees'; +------+------------------+-------------------+------------+ | part | expr | descr | table_rows | +------+------------------+-------------------+------------+ | p01 | gender,hire_date | 'F','1990-01-01' | 66212 | | p02 | gender,hire_date | 'F','2000-01-01' | 53832 | | p03 | gender,hire_date | 'F',MAXVALUE | 7 | | p04 | gender,hire_date | 'M','1990-01-01' | 98585 | | p05 | gender,hire_date | 'M','2000-01-01' | 81382 | | p06 | gender,hire_date | 'M',MAXVALUE | 6 | | p07 | gender,hire_date | MAXVALUE,MAXVALUE | 0 | +------+------------------+-------------------+------------+
若是存儲引擎是InnoDB,上面的值就是一個近似值,若是你須要確切的值,那你就不能信任它們。
另外一個問題是它的性能,這些加強觸發了分區修整嗎?答案絕不含糊,是的。與MySQL 5.1有所不一樣,在5.1中日期分區只能與兩個函數工做,在MySQL 5.5中,任何使用了COLUMNS關鍵字定義的分區均可以使用分區修整,下面仍是測試一下吧。
select count(*) from employees where gender='F' and hire_date < '1990-01-01'; +----------+ | count(*) | +----------+ | 66212 | +----------+ 1 row in set (0.05 sec) explain partitions select count(*) from employees where gender='F' and hire_date < '1990-01-01'\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: employees partitions: p01 type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 300024 Extra: Using where
使用定義第一個分區的條件,咱們得到了一個很是優化的查詢,不只如此,部分條件也將從分區修整中受益。
select count(*) from employees where gender='F'; +----------+ | count(*) | +----------+ | 120051 | +----------+ 1 row in set (0.12 sec) explain partitions select count(*) from employees where gender='F'\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: employees partitions: p01,p02,p03,p04 type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 300024 Extra: Using where
它和複合索引的算法同樣,若是你的條件指的是索引最左邊的部分,MySQL將會使用它。與此相似,若是你的條件指的是分區定義最左邊的部分,MySQL將會盡量修整。它和複合索引一塊兒出現,若是你只使用最右邊的條件,分區修整不會工做。
select count(*) from employees where hire_date < '1990-01-01'; +----------+ | count(*) | +----------+ | 164797 | +----------+ 1 row in set (0.18 sec) explain partitions select count(*) from employees where hire_date < '1990-01-01'\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: employees partitions: p01,p02,p03,p04,p05,p06,p07 type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 300024 Extra: Using where
若是不用分區定義的第一部分,使用分區定義的第二部分,那麼將會發生全表掃描,在設計分區和編寫查詢時要緊記這一條。
可用性加強:truncate分區
分區最吸引人的一個功能是瞬間移除大量記錄的能力,DBA都喜歡將歷史記錄存儲到按日期分區的分區表中,這樣能夠按期刪除過期的歷史數據,這種方法至關管用,假設第一個分區存儲的是最舊的歷史記錄,那麼你能夠直接刪除第一個分區,而後再在末尾創建一個新分區保存最近的歷史記錄,這樣循環下去就能夠實現歷史記錄的快速清除。
但當你須要移除分區中的部分數據時,事情就不是那麼簡單了,刪除分區沒有問題,但若是是清空分區,就很頭痛了,要移除分區中的全部數據,但須要保留分區自己,你能夠:
使用DELETE語句,但咱們知道DELETE語句的性能都不好。
使用DROP PARTITION語句,緊跟着一個EORGANIZE PARTITIONS語句從新建立分區,但這樣作比前一個方法的成本要高出許多。
MySQL 5.5引入了TRUNCATE PARTITION,它和DROP PARTITION語句有些相似,但它保留了分區自己,也就是說分區還能夠重複利用。TRUNCATE PARTITION應該是DBA工具箱中的必備工具。
更多微調功能:TO_SECONDS
分區加強包有一個新的函數處理DATE和DATETIME列,使用TO_SECONDS函數,你能夠將日期/時間列轉換成自0年以來的秒數,若是你想使用小於1天的間隔進行分區,那麼這個函數就能夠幫到你。
TO_SECONDS會觸發分區修整,與TO_DAYS不一樣,它能夠反過來使用,就是FROM_DAYS,對於TO_SECONDS就沒有這樣的反向函數了,但要本身動手DIY一個也不是難事。
drop function if exists from_seconds; delimiter // create function from_seconds (secs bigint) returns DATETIME begin declare days INT; declare secs_per_day INT; DECLARE ZH INT; DECLARE ZM INT; DECLARE ZS INT; set secs_per_day = 60 * 60 * 24; set days = floor(secs / secs_per_day); set secs = secs - (secs_per_day * days); set ZH = floor(secs / 3600); set ZM = floor(secs / 60) - ZH * 60; set ZS = secs - (ZH * 3600 + ZM * 60); return CAST(CONCAT(FROM_DAYS(days), ' ', ZH, ':', ZM, ':', ZS) as DATETIME); end // delimiter ;
有了這些新武器,咱們能夠有把握地建立一個小於1天的臨時分區,如:
CREATE TABLE t2 ( dt datetime ) PARTITION BY RANGE (to_seconds(dt)) ( PARTITION p01 VALUES LESS THAN (to_seconds('2009-11-30 08:00:00')) , PARTITION p02 VALUES LESS THAN (to_seconds('2009-11-30 16:00:00')) , PARTITION p03 VALUES LESS THAN (to_seconds('2009-12-01 00:00:00')) , PARTITION p04 VALUES LESS THAN (to_seconds('2009-12-01 08:00:00')) , PARTITION p05 VALUES LESS THAN (to_seconds('2009-12-01 16:00:00')) , PARTITION p06 VALUES LESS THAN (MAXVALUE) ); show create table t2\G *************************** 1. row *************************** Table: t2 Create Table: CREATE TABLE `t2` ( `dt` datetime DEFAULT NULL ) ENGINE=MyISAM DEFAULT CHARSET=latin1 /*!50500 PARTITION BY RANGE (to_seconds(dt)) (PARTITION p01 VALUES LESS THAN (63426787200) ENGINE = MyISAM, PARTITION p02 VALUES LESS THAN (63426816000) ENGINE = MyISAM, PARTITION p03 VALUES LESS THAN (63426844800) ENGINE = MyISAM, PARTITION p04 VALUES LESS THAN (63426873600) ENGINE = MyISAM, PARTITION p05 VALUES LESS THAN (63426902400) ENGINE = MyISAM, PARTITION p06 VALUES LESS THAN MAXVALUE ENGINE = MyISAM) */
由於咱們沒有使用COLUMNS關鍵字,咱們也不能使用它,由於它不支持混合列和函數,表定義中的記錄值就是TO_SECONDS函數的計算結果。
但咱們仍是要感謝新的函數,咱們能夠反推這個值,換算成一個更容易讀懂的日期。
select partition_name part, partition_expression expr, from_seconds(partition_description) descr, table_rows FROM INFORMATION_SCHEMA.partitions WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME='t2'; +------+----------------+---------------------+------------+ | part | expr | descr | table_rows | +------+----------------+---------------------+------------+ | p01 | to_seconds(dt) | 2009-11-30 08:00:00 | 0 | | p02 | to_seconds(dt) | 2009-11-30 16:00:00 | 0 | | p03 | to_seconds(dt) | 2009-12-01 00:00:00 | 0 | | p04 | to_seconds(dt) | 2009-12-01 08:00:00 | 0 | | p05 | to_seconds(dt) | 2009-12-01 16:00:00 | 0 | | p06 | to_seconds(dt) | 0000-00-00 00:00:00 | 0 | +------+----------------+---------------------+------------+
總結
MySQL 5.5對分區用戶絕對是個好消息,雖然沒有提供直接的性能加強的方法(若是你按響應時間評估性能),但更易於使用的加強功能,以及TRUNCATE PARTITION命令均可覺得DBA節省大量的時間,有時對最終用戶亦如此。
這些加強的功能可能會在下一個里程碑發佈時獲得更新,最終版本預計會在2010年年中發佈,屆時全部分區用戶均可以嘗試一下!
原文出處:http://dev.mysql.com/tech-resources/articles/mysql_55_partitioning.html
原文名:A deep look at MySQL 5.5 partitioning enhancements