mysql表分區詳解
1、什么是表分區?
分區是一種表的設計模式,正確的分區可以極大地提升數據庫的查詢效率,完成更高質量的SQL編程。但是如果錯誤地使用分區,那么分區可能帶來毀滅性的的結果。
分區功能並不是在存儲引擎層完成的,常見的存儲引擎InnoDB、MyISAM、NDB等都支持分區。但是並不是所有的存儲引擎都支持,如CSV、FEDORATED、MERGE等就不支持分區。在使用此分區功能前,應該對選擇的存儲引擎對分區的支持有所了解。
MySQL數據庫在5.1版本及以上時添加了對分區的支持,分區的過程是將一個表或索引分解為多個更小、更可管理的部分。就訪問數據庫的應用而言,從邏輯上講,只有一個表或一個索引,但是在物理上這個表或索引可能由數十個物理分區組成。每個分區都是獨立的對象,可以獨自處理,也可以作為一個更大對象的一部分進行處理。
MySQL數據庫支持的分區類型為水平分區(指將同一個表中不同行的記錄分配到不同的物理文件中),並不支持垂直分區(指將同一表中不同列的記錄分配到不同的物理文件中)。此外,MySQL數據庫的分區是局部分區索引,一個分區中既存放了數據又存放了索引。而全局分區是指,數據存放在各個分區中,但是所有數據的索引放在一個對象中。目前,MySQL數據庫還不支持全局分區。
2、表分區有什么好處?
分區的好處是:
- 可以讓單表存儲更多的數據
- 分區表的數據更容易維護,可以通過清楚整個分區批量刪除大量數據,也可以增加新的分區來支持新插入的數據。另外,還可以對一個獨立分區進行優化、檢查、修復等操作
- 部分查詢能夠從查詢條件確定只落在少數分區上,速度會很快(查詢條件盡量掃描少的分區)
- 分區表的數據還可以分布在不同的物理設備上,從而高效利用多個硬件設備
- 可以使用分區表來避免某些特殊瓶頸,例如InnoDB單個索引的互斥訪問、ext3文件系統的inode鎖競爭
- 可以備份和恢復單個分區
分區的限制和缺點:
- 一個表最多只能有1024個分區
- 如果分區字段中有主鍵或者唯一索引的列,那么所有主鍵列和唯一索引列都必須包含進來
- 分區表無法使用外鍵約束
- NULL值會使分區過濾無效
- 所有分區必須使用相同的存儲引擎
3、表分區與分表的區別?
分表:指的是通過一定規則,將一張表分解成多張不同的表。比如將用戶訂單記錄根據時間成多個表。 分表與分區的區別在於:分區從邏輯上來講只有一張表,而分表則是將一張表分解成多張表。
4、如何判斷當前MySQL是否支持分區?
mysql> show variables like '%partition%'; +-------------------+-------+ | Variable_name | Value | +-------------------+-------+ | have_partitioning | YES | +-------------------+-------+ row in set (0.00 sec)
have_partintioning 的值為YES,表示支持分區。
5:MySQL分區類型
注意:如果分區字段中有主鍵或者唯一索引的列,那么所有主鍵列和唯一索引列都必須包含進來(即分區的字段也必須是主鍵或者唯一索引)
1)RANGE分區
我們介紹的第一種分區類型是RANGE分區,也是最常用的一種分區類型,基於屬於一個給定連續區間的列值,把多行分配給分區。
這些區間要連續且不能相互重疊,使用VALUES LESS THAN操作符來進行定義。不使用COLUMNS
關鍵字時RANGE
括號內必須為整數字段名或返回確定整數的函數。
下面創建一個以id列為區間分區表,當id小於10時,數據插入p0分區;當id大於等於10小於20時,數據插入p1分區。
create table t( idint )engine=innodb partition by range(id)( partition p0 values less than(10), partition p1 values less than(20) );
查看表在磁盤上的物理文件,啟用分區之后,表不再由一個ibd文件組成了。而是由建立分區時的各個分區ibd文件組成。
如何查看 t 表的分區信息:
select * from information_schema.partitions where table_schema=database() and table_name='t'
注意:對於表t,由於我們定義了分區,因此對於插入的值應該嚴格遵守分區的定義,當插入一個不在分區中定義的值時,MySQL數據庫會拋出一個異常。如下所示:
mysql>insert into t select 30; ERROR1526(HY000):Table has no partition for value 30
對於上述問題,我們可以對分區添加一個MAXVALUE值的分區,MAXVALUE可以理解為正無窮,因此所有大於等於20且小於MAXVALUE的值被放入p2分區。
alter table t add partition(partition p2 values less than maxvalue);
RANGE分區主要用於日期列的分區,例如對於銷售類的表,可以根據年來分區存放銷售記錄,如下面的分區表sales。
create table sales( money int unsigned notnull, date datetime )engine=innodb partition by range(year(date))( partition p2014 values less than(2015), partition p2015 values less than(2016), partition p2016 values less than(2017) );
這樣創建的好處就是便於對sales這張表的管理。如果我們要刪除2015年的數據,不需要執行delete from sales where date>=’2015-01-01′ and dater<= ‘2016-01-01’,只需要刪除2015年所在的分區即可。
alter table sales drop partition p2015;
這樣創建的另一個好處就是可以加快某些查詢操作,如果我們只需要查詢2014年整年的銷售額,可以這樣:
explain partitions select * from sales where date BETWEEN '2014-01-01' and '2014-12-31'
通過explain partitions命令我們可以發現,在上述語句中,SQL優化只需要去搜索p2014這個分區,而不會去搜索所有的分區,稱為分區修剪(partition pruning),故查詢的速度得到了大幅度的提升。需要注意的是,如果執行下列語句,結果是一樣的,但是優化器的選擇可能又會不同了。partitions:p2014,p2015
explain partitions select * from sales where date BETWEEN '2014-01-01' and '2015-01-01'
在進行分區時,如果出現“This partition function is not allowed”的錯誤提示,則你可能使用了非支持函數。MySQL 5.6支持的partition函數:http://dev.mysql.com/doc/refman/5.6/en/partitioning-limitations-functions.html
2)LIST分區
LIST分區和RANGE分區類似,區別在於LIST分區是基於列值匹配一個離散值集合中的某個值來進行選擇,而非連續的。
LIST分區通過使用“PARTITION BY LIST(expr)”來實現,其中“expr” 是某列值或一個基於某個列值、並返回一個整數值的表達式,然后通過“VALUES IN (value_list)”的方式來定義每個分區,其中“value_list”是一個通過逗號分隔的整數列表。
假定有20個音像店,分布在4個有經銷權的地區,如下表所示:

不同於RANGE分區中定義的VALUES LESS THAN語句,LIST分區使用VALUES IN,因為每個分區的值是離散的,因此只能定義值。按照屬於同一個地區商店的行保存在同一個分區中的方式來分割表,可以使用下面的“CREATE TABLE”語句:
CREATE TABLE employees ( id INT NOT NULL, name VARCHAR(30), hired DATE NOT NULL DEFAULT '1970-01-01', separated DATE NOT NULL DEFAULT '9999-12-31', store_id INT ) PARTITION BY LIST(store_id) PARTITION pNorth VALUES IN (3,5,6,9,17), PARTITION pEast VALUES IN (1,2,10,11,19,20), PARTITION pWest VALUES IN (4,12,13,14,18), PARTITION pCentral VALUES IN (7,8,15,16) );
這使得在表中增加或刪除指定地區的雇員記錄變得容易起來。例如,假定西區的所有音像店都賣給了其他公司。那么與在西區音像店工作雇員相關的所有記錄(行)可以使用“ALTER TABLE employees DROP PARTITION pWest;”來進行刪除,它與具有同樣作用的DELETE (刪除)查詢“DELETE query DELETE FROM employees WHERE store_id IN (4,12,13,14,18);”比起來,要有效得多。
【要點】如果試圖插入列值(或分區表達式的返回值)不在分區值列表中的一行時,那么“INSERT”查詢將失敗並報錯。例如,假定LIST分區的采用上面的方案,下面的查詢將失敗:
INSERT INTO employees VALUES(224,'Linus','2015-05-01','2015-10-12',42,21);
這是因為“store_id”列值21不能在用於定義分區pNorth, pEast, pWest,或pCentral的值列表中找到。要重點注意的是,LIST分區沒有類似如“VALUES LESS THAN MAXVALUE”這樣的包含其他值在內的定義。將要匹配的任何值都必須在值列表中找到。
3)HASH分區
HASH分區的目的是將數據均勻地分布到預先定義的各個分區中,保證各分區的數據量大致都是一樣的。在RANGE和LIST分區中,必須明確指定一個給定的列值或列值集合應該保存在哪個分區中;而在HASH分區中,MySQL自動完成這些工作,用戶所要做的只是基於將要進行哈希分區的列值指定一個列值或表達式,以及指定被分區的表將要被分隔成的分區數量。
要使用HASH分區來分割一個表,要在CREATE TABLE 語句上添加一個“PARTITION BY HASH (expr)”子句,其中“expr”是一個返回一個整數的表達式。它可以僅僅是字段類型為MySQL 整型的一列的名字。此外,你很可能需要在后面再添加一個“PARTITIONS num”子句,其中num是一個非負的整數,它表示表將要被分割成分區的數量,如果沒有包括一個PARTITIONS子句,那么分區的數量將默認為1。
CREATE TABLE employees_h ( id INT NOT NULL, lname VARCHAR(30), store_id INT ) PARTITION BY HASH(store_id) PARTITIONS 4;
4)KEY分區
KEY分區和HASH分區相似,不同之處在於HASH分區使用用戶定義的函數進行分區,支持字符串HASH分區,KEY分區使用MySQL數據庫提供的函數進行分區,這些函數基於與PASSWORD()一樣的運算法則。
5)COLUMNS
在前面說了RANGE、LIST、HASH和KEY這四種分區中,分區的條件是:數據必須為整形(interger),如果不是整形,那應該需要通過函數將其轉化為整形,如YEAR(),TO_DAYS(),MONTH()等函數。MySQL5.5版本開始支持COLUMNS分區,可視為RANGE分區和LIST分區的一種進化。COLUMNS分區可以直接使用非整形的數據進行分區,分區根據類型直接比較而得,不需要轉化為整形。此外,RANGE COLUMNS分區可以對多個列的值進行分區。
COLUMNS分區支持以下的數據類型:
- 所有的整形類型,如INT、SMALLINT、TINYINT和BIGINT。而FLOAT和DECIMAL則不予支持。
- 日期類型,如DATE何DATETIME。其余的日期類型不予支持。
- 字符串類型,如CHAR、VARCHAR、BINARY和VARBINARY。而BLOB和TEXT類型不予支持。
對於日期類型的分區,我們不再需要YEAR()和TO_DATS()函數了,而直接可以使用COLUMNS,如:
CREATE TABLE `t_c` ( `key` varchar(50), `value` varchar(50), `create_time` datetime ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 PARTITION BY RANGE COLUMNS (create_time) ( PARTITION p0 VALUES LESS THAN ('2017-01-01 00:00:00'), PARTITION p1 VALUES LESS THAN ('2017-03-01 00:00:00') );
同樣可以使用字符串分區。
CREATE TABLE`monitor_2`( `key`varchar(15), `value`varchar(50), `create_time`datetime, `city`VARCHAR(15) )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 PARTITION BY LIST COLUMNS (city)( PARTITION p0 VALUES IN('shanghai','beijing','shenzhen'), PARTITION p1 VALUES IN('hubei','henan','hunan') );
對比RANGE分區和LIST分區,Columns分區的亮點除了支持數據類型增加之外,另外一大亮點是Columns分區還支持多列分區。如:
CREATE TABLE `monitor_3` ( `key` varchar(15), `value` varchar(50), `create_time` datetime, `test` VARCHAR(1) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 PARTITION BY RANGE COLUMNS(create_time,test) ( PARTITION p0 VALUES LESS THAN ('2017-01-01 00:00:00','yes'), PARTITION p1 VALUES LESS THAN ('2017-03-01 00:00:00','no'), PARTITION p2 VALUES LESS THAN (MAXVALUE,MAXVALUE) );
6)分區中的NULL值
MySQL數據庫允許對NULL值做分區,但是處理的方法與其他數據庫可能完全不同。MySQL數據庫的分區總是視NULL值小於任何的一個非NULL值,這和MySQL數據庫中處理NULL值的ORDER BY操作是一樣的。因此對於不同的分區類型,MySQL數據庫對於NULL值的處理也是各不相同。
1)對於RANGE分區,如果向分區列插入了NULL值,則MySQL數據庫會將該值放入最左邊的分區(第一個分區)。
2)對於LIST分區,如果向分區列插入了NULL值,則必須顯示地指出哪個分區放入NULL值,否則會報錯。
3)對於HASH和KEY分區,對於NULL值的處理方法和RANGE分區、LIST分區不一樣。任何分區函數都會將含有NULL值的記錄返回為0。
1:創建表分區: ALTER TABLE my_user PARTITION by RANGE COLUMNS (age) ( PARTITION p30 VALUES LESS THAN (30), PARTITION p40 VALUES LESS THAN (40), PARTITION p50 VALUES LESS THAN (50), PARTITION p60 VALUES LESS THAN (100) -- 剩下的放在一個分區中,當需要對這部分進行再次分區的時候,需要先刪除該分區,然后再添加多個分區 ); 2:創建分區后增加分區: alter table my_user add partition (partition p3 values less than (4000)); -- range 分區 alter table my_user add partition (partition p3 values in (40)); -- lists分區 3:刪除表分區(注意這種方式刪除分區,會刪除數據): alter table my_user drop partition p30; 4:刪除表的所有分區(不會丟失數據):
Alter table my_user remove partitioning; 5:查看創建分區表的create語句 show create table my_user 6:可以查看表是不是分區表(可以根據Create_options查看) show table status 7:explain partitions select 語句: 通過此語句來顯示掃描哪些分區,及他們是如何使用的 8:重新定義range分區表: ----不會丟失數據 Alter table my_user partition by range(salary)( partition p1 values less than (2000), partition p2 values less than (4000) ); 9:重新定義hash分區表: ----不會丟失數據 Alter table my_user partition by hash(salary) partitions 7; 10:合並分區: Merge分區:把2個分區合並為一個----不會丟失數據。 alter table my_user reorganize partition p1,p3 into (partition p1 values less than (1000)); 11:分解分區: reorganize partition 關鍵字可以對表的部分分區或全部分區進行修改,並且不會丟失數據。分解前后分區的整體范圍應該一致。 alter table my_user reorganize partition p1 into( partition p1 values less than (100), partition p3 values less than (1000) );