mysql 分区表详解
項(xiàng)目中要一張庫(kù)表實(shí)現(xiàn) list分區(qū)。并且支持多種數(shù)據(jù)庫(kù)。 oracle 很順利,只是mysql 聽(tīng)說(shuō)5.1版本就已經(jīng)支持了, 可是試了很多個(gè)版本,都不行,后來(lái)查到原因是要5.5 以上版本 分區(qū)才支持 varchar, 可是試到 5.6 都一直報(bào)錯(cuò):?"VALUES value for partition 'P_XX' must have type INT"
后來(lái)才發(fā)現(xiàn)、原來(lái)沒(méi)有查對(duì)資料,走了彎路。是LIST COLUMNS才支持 varchar類(lèi)型的分區(qū)字段。?
?mysql 官方文檔? http://dev.mysql.com/tech-resources/articles/mysql_55_partitioning.html
譯文:
The intuitive part: partition by non-integer columns:
任何使用過(guò)分區(qū)的人應(yīng)該都遇到過(guò)不少問(wèn)題,特別是面對(duì)非整數(shù)列分區(qū)時(shí),MySQL 5.1只能處理整數(shù)列分區(qū),如果你想在日期或字符串列上進(jìn)行分區(qū),你不得不使用函數(shù)對(duì)其進(jìn)行轉(zhuǎn)換。
MySQL 5.5中新增了兩類(lèi)分區(qū)方法,RANG和LIST分區(qū)法,同時(shí)在新的函數(shù)中增加了一個(gè)COLUMNS關(guān)鍵詞。我們假設(shè)有這樣一個(gè)表:
?
CREATE TABLE expenses ( expense_date DATE NOT NULL, category VARCHAR(30), amount DECIMAL (10,3) );如果你想使用MySQL 5.1中的分區(qū)類(lèi)型,那你必須將類(lèi)型轉(zhuǎn)換成整數(shù),需要使用一個(gè)額外的查找表,到了MySQL 5.5中,你可以不用再進(jìn)行類(lèi)型轉(zhuǎn)換了,如:
?
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') );
?
這樣的分區(qū)語(yǔ)句除了更加易讀外,對(duì)數(shù)據(jù)的組織和管理也非常清晰,上面的例子只對(duì)category列進(jìn)行分區(qū)。
在MySQL 5.1中使用分區(qū)另一個(gè)讓人頭痛的問(wèn)題是date類(lèi)型(即日期列),你不能直接使用它們,必須使用YEAR或TO_DAYS轉(zhuǎn)換這些列,如:
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) */
?
看上去非常糟糕,當(dāng)然也有變通辦法,但麻煩確實(shí)不少。使用YEAR或TO_DAYS定義一個(gè)分區(qū)的確讓人費(fèi)解,查詢(xún)時(shí)不得不使用赤裸列,因?yàn)榧恿撕瘮?shù)的查詢(xún)不能識(shí)別分區(qū)。
但在MySQL 5.5中情況發(fā)生了很大的變化,現(xiàn)在在日期列上可以直接分區(qū),并且方法也很簡(jiǎn)單。
/*在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) */?
在這里,通過(guò)函數(shù)定義和通過(guò)列查詢(xún)之間沒(méi)有沖突,因?yàn)槭前戳卸x的,我們?cè)诙x中插入的值是保留的。
多列分區(qū)
COLUMNS關(guān)鍵字現(xiàn)在允許字符串和日期列作為分區(qū)定義列,同時(shí)還允許使用多個(gè)列定義一個(gè)分區(qū),你可能在官方文檔中已經(jīng)看到了一些例子,如:?
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)等其它例子。由于我很長(zhǎng)時(shí)間都在使用MySQL 5.1的分區(qū),我對(duì)多列分區(qū)的含義不太了解,LESS THAN (10,10)是什么意思?如果下一個(gè)分區(qū)是LESS THAN (10,20)會(huì)發(fā)生什么?相反,如果是(20,30)又會(huì)如何?
所有這些問(wèn)題都需要一個(gè)答案,在回答之前,他們需要更好地理解我們?cè)谧鍪裁础?/p>
開(kāi)始時(shí)可能有些混亂,當(dāng)所有分區(qū)有一個(gè)不同范圍的值時(shí),實(shí)際上,它只是在表的一個(gè)列上進(jìn)行了分區(qū),但事實(shí)并非如此,在下面的例子中:
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),它將會(huì)進(jìn)入第一個(gè)分區(qū),相反,在表p1_single中,它將會(huì)進(jìn)入第二個(gè)分區(qū),其原因是(10,1)小于(10,10),如果你僅僅關(guān)注第一個(gè)值,你還沒(méi)有意識(shí)到你在比較一個(gè)元組,而不是一個(gè)單一的值。
現(xiàn)在我們來(lái)分析一下最難懂的地方,當(dāng)你需要確定某一行應(yīng)該放在哪里時(shí)會(huì)發(fā)生什么?你是如何確定類(lèi)似(10,9) < (10,10)這種運(yùn)算的值的?答案其實(shí)很簡(jiǎn)單,當(dāng)你對(duì)它們進(jìn)行排序時(shí),使用相同的方法計(jì)算兩條記錄的值。
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))?
如果有三列,表達(dá)式會(huì)更長(zhǎng),但不會(huì)更復(fù)雜。你首先在第一個(gè)項(xiàng)目上測(cè)試小于運(yùn)算,如果有兩個(gè)或更多的分區(qū)與之匹配,接著就測(cè)試第二個(gè)項(xiàng)目,如果不止一個(gè)候選分區(qū),那還需要測(cè)試第三個(gè)項(xiàng)目。
下圖所顯示的內(nèi)容表示將遍歷三條記錄插入到使用以下代碼定義的分區(qū)中:
(10,10),
(10,20),
(10,30),
(10, MAXVALUE)
?
?
圖 2 元組比較。當(dāng)?shù)谝粋€(gè)值小于分區(qū)定義的第一個(gè)范圍時(shí),那么該行將屬于這里了。
?
?
圖 3 元組比較。當(dāng)?shù)谝粋€(gè)值等于分區(qū)定義的第一個(gè)范圍,我們需要比較第二個(gè)項(xiàng)目,如果它小于第二個(gè)范圍,那么該行將屬于這里了。
?
?
圖 4 元組比較。當(dāng)?shù)谝粋€(gè)值和第二個(gè)值等于他們對(duì)應(yīng)的范圍時(shí),如果元組不小于定義的范圍,那么它就不屬于這里,繼續(xù)下一步。
?
?
圖 5 元組比較。在下一個(gè)范圍時(shí),第一個(gè)項(xiàng)目是等于,第二個(gè)項(xiàng)目是小于,因此元組更小,那么該行就屬于這里了。
在這些圖的幫助下,我們對(duì)插入一條記錄到多列分區(qū)表的步驟有了更深的了解,這些都是理論上的,為了幫助你更好地掌握新功能,我們?cè)賮?lái)看一個(gè)更高級(jí)一點(diǎn)的例子,對(duì)于比較務(wù)實(shí)的讀者更有意義,下面是表的定義腳本:
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)?
和上面的例子不同,這個(gè)例子更好理解,第一個(gè)分區(qū)用來(lái)存儲(chǔ)雇傭于1990年以前的女職員,第二個(gè)分區(qū)存儲(chǔ)股用于1990-2000年之間的女職員,第三個(gè)分區(qū)存儲(chǔ)所有剩下的女職員。對(duì)于分區(qū)p04到p06,我們策略是一樣的,只不過(guò)存儲(chǔ)的是男職員。最后一個(gè)分區(qū)是控制情況。
看完后你可能要問(wèn),我怎么知道某一行存儲(chǔ)在那個(gè)分區(qū)中的?有兩個(gè)辦法,第一個(gè)辦法是使用與分區(qū)定義相同的條件作為查詢(xún)條件進(jìn)行查詢(xún)。
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提供的統(tǒng)計(jì)信息。?
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 | +------+------------------+-------------------+------------+?
如果存儲(chǔ)引擎是InnoDB,上面的值就是一個(gè)近似值,如果你需要確切的值,那你就不能信任它們。
另一個(gè)問(wèn)題是它的性能,這些增強(qiáng)觸發(fā)了分區(qū)修整嗎?答案毫不含糊,是的。與MySQL 5.1有所不同,在5.1中日期分區(qū)只能與兩個(gè)函數(shù)工作,在MySQL 5.5中,任何使用了COLUMNS關(guān)鍵字定義的分區(qū)都可以使用分區(qū)修整,下面還是測(cè)試一下吧。
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?
使用定義第一個(gè)分區(qū)的條件,我們獲得了一個(gè)非常優(yōu)化的查詢(xún),不僅如此,部分條件也將從分區(qū)修整中受益。
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?
它和復(fù)合索引的算法一樣,如果你的條件指的是索引最左邊的部分,MySQL將會(huì)使用它。與此類(lèi)似,如果你的條件指的是分區(qū)定義最左邊的部分,MySQL將會(huì)盡可能修整。它和復(fù)合索引一起出現(xiàn),如果你只使用最右邊的條件,分區(qū)修整不會(huì)工作。
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?
如果不用分區(qū)定義的第一部分,使用分區(qū)定義的第二部分,那么將會(huì)發(fā)生全表掃描,在設(shè)計(jì)分區(qū)和編寫(xiě)查詢(xún)時(shí)要緊記這一條。
可用性增強(qiáng):truncate分區(qū)
分區(qū)最吸引人的一個(gè)功能是瞬間移除大量記錄的能力,DBA都喜歡將歷史記錄存儲(chǔ)到按日期分區(qū)的分區(qū)表中,這樣可以定期刪除過(guò)時(shí)的歷史數(shù)據(jù),這種方法相當(dāng)管用,假設(shè)第一個(gè)分區(qū)存儲(chǔ)的是最舊的歷史記錄,那么你可以直接刪除第一個(gè)分區(qū),然后再在末尾建立一個(gè)新分區(qū)保存最近的歷史記錄,這樣循環(huán)下去就可以實(shí)現(xiàn)歷史記錄的快速清除。
但當(dāng)你需要移除分區(qū)中的部分?jǐn)?shù)據(jù)時(shí),事情就不是那么簡(jiǎn)單了,刪除分區(qū)沒(méi)有問(wèn)題,但如果是清空分區(qū),就很頭痛了,要移除分區(qū)中的所有數(shù)據(jù),但需要保留分區(qū)本身,你可以:
使用DELETE語(yǔ)句,但我們知道DELETE語(yǔ)句的性能都很差。
使用DROP PARTITION語(yǔ)句,緊跟著一個(gè)EORGANIZE PARTITIONS語(yǔ)句重新創(chuàng)建分區(qū),但這樣做比前一個(gè)方法的成本要高出許多。
MySQL 5.5引入了TRUNCATE PARTITION,它和DROP PARTITION語(yǔ)句有些類(lèi)似,但它保留了分區(qū)本身,也就是說(shuō)分區(qū)還可以重復(fù)利用。TRUNCATE PARTITION應(yīng)該是DBA工具箱中的必備工具。
更多微調(diào)功能:TO_SECONDS
分區(qū)增強(qiáng)包有一個(gè)新的函數(shù)處理DATE和DATETIME列,使用TO_SECONDS函數(shù),你可以將日期/時(shí)間列轉(zhuǎn)換成自0年以來(lái)的秒數(shù),如果你想使用小于1天的間隔進(jìn)行分區(qū),那么這個(gè)函數(shù)就可以幫到你。
TO_SECONDS會(huì)觸發(fā)分區(qū)修整,與TO_DAYS不同,它可以反過(guò)來(lái)使用,就是FROM_DAYS,對(duì)于TO_SECONDS就沒(méi)有這樣的反向函數(shù)了,但要自己動(dòng)手DIY一個(gè)也不是難事。
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 ;?
有了這些新武器,我們可以有把握地創(chuàng)建一個(gè)小于1天的臨時(shí)分區(qū),如:
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) */?
因?yàn)槲覀儧](méi)有使用COLUMNS關(guān)鍵字,我們也不能使用它,因?yàn)樗恢С只旌狭泻秃瘮?shù),表定義中的記錄值就是TO_SECONDS函數(shù)的計(jì)算結(jié)果。
但我們還是要感謝新的函數(shù),我們可以反推這個(gè)值,換算成一個(gè)更容易讀懂的日期。
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 | +------+----------------+---------------------+------------+?
總結(jié)
MySQL 5.5對(duì)分區(qū)用戶(hù)絕對(duì)是個(gè)好消息,雖然沒(méi)有提供直接的性能增強(qiáng)的方法(如果你按響應(yīng)時(shí)間評(píng)估性能),但更易于使用的增強(qiáng)功能,以及TRUNCATE PARTITION命令都可以為DBA節(jié)省大量的時(shí)間,有時(shí)對(duì)最終用戶(hù)亦如此。
?
轉(zhuǎn)載于:https://www.cnblogs.com/javaMan/p/4065977.html
總結(jié)
以上是生活随笔為你收集整理的mysql 分区表详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: tp数据库表大写命名的一些问题
- 下一篇: shell scripts 之 代码量统