mysql 分区表详解
項目中要一張庫表實現 list分區。并且支持多種數據庫。 oracle 很順利,只是mysql 聽說5.1版本就已經支持了, 可是試了很多個版本,都不行,后來查到原因是要5.5 以上版本 分區才支持 varchar, 可是試到 5.6 都一直報錯:?"VALUES value for partition 'P_XX' must have type INT"
后來才發現、原來沒有查對資料,走了彎路。是LIST COLUMNS才支持 varchar類型的分區字段。?
?mysql 官方文檔? http://dev.mysql.com/tech-resources/articles/mysql_55_partitioning.html
譯文:
The intuitive part: partition by non-integer columns:
任何使用過分區的人應該都遇到過不少問題,特別是面對非整數列分區時,MySQL 5.1只能處理整數列分區,如果你想在日期或字符串列上進行分區,你不得不使用函數對其進行轉換。
MySQL 5.5中新增了兩類分區方法,RANG和LIST分區法,同時在新的函數中增加了一個COLUMNS關鍵詞。我們假設有這樣一個表:
?
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轉換這些列,如:
1 /* 在MySQL 5.1中*/ 2 CREATE TABLE t2 3 ( 4 dt DATE 5 ) 6 PARTITION BY RANGE (TO_DAYS(dt)) 7 ( 8 PARTITION p01 VALUES LESS THAN (TO_DAYS('2007-01-01')), 9 PARTITION p02 VALUES LESS THAN (TO_DAYS('2008-01-01')), 10 PARTITION p03 VALUES LESS THAN (TO_DAYS('2009-01-01')), 11 PARTITION p04 VALUES LESS THAN (MAXVALUE)); 12 13 SHOW CREATE TABLE t2 \G 14 *************************** 1. row *************************** 15 Table: t2 16 Create Table: CREATE TABLE `t2` ( 17 `dt` date DEFAULT NULL 18 ) ENGINE=MyISAM DEFAULT CHARSET=latin1 19 /*!50100 PARTITION BY RANGE (TO_DAYS(dt)) 20 (PARTITION p01 VALUES LESS THAN (733042) ENGINE = MyISAM, 21 PARTITION p02 VALUES LESS THAN (733407) ENGINE = MyISAM, 22 PARTITION p03 VALUES LESS THAN (733773) ENGINE = MyISAM, 23 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節省大量的時間,有時對最終用戶亦如此。
?
轉載于:https://www.cnblogs.com/javaMan/p/4065977.html
總結
以上是生活随笔為你收集整理的mysql 分区表详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: tp数据库表大写命名的一些问题
- 下一篇: linux cmake编译源码,linu