Mysql分区?
译者:联创网络 4A 产品部 工号5151
来源:MySQL 5.1.16 Manual
如有转载,请注明出处。且作者对本文造成的损失不负任何责任。
文档里说,分区的表好像对版本有要求,5.15的分区表也许不能在5.16使用。
分区的大概意思?
Sql标准本身没有提供太多关于数据的物理存储的指导。Sql语言本身倾向于独立在存储的数据结构和表,字段,记录依赖的媒介之外。然而,大多数先进的数据库管理系统总是提供了一些方法去定位某些特定数据在文件系统或者是硬件中的物理位置。在mysql中,InnoDB存储引擎早就支持表空间的概念,还有啊,mysql服务器,在说到分区之前,就能通过配置用不同的目录存储不同的数据库了。
分区 就更进了一步。让你可以把单个的表根据你定义的规则分成不同的部分放在文件系统上。实际上,表的不同部分被定义成独立的表放在不同的地方。区分数据的功能被称为分区方法,在mysql中就是一个模块。简单地匹配一些值域或者集合,一个内部的hash函数或者一个线性hash函数。这个方法的调用是根据用户指定的分区类型,参数是用户提供的表达式的值。这个表达式可以是一个整数,或者是由一个函数计算多个列返回的整数值。表达式的值被传递到分区方法中,而分区方法返回一个分区号表示数据存储在哪个分区中。这个方法不能是常量或者随机的。它可能没有用到查询,但可以使用mysql中有效的表达式,只要表达式返回的整数小于整数的上限。
水平分区,就是不同的行可以放在不同的物理分区,mysql5.1还不支持垂直分区,就是不同的列放在不同的物理分区。
mysql> SHOW VARIABLES LIKE '%partition%';
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| have_partitioning | YES |
+-------------------+-------+
显示是否对分区进行支持,说来说去就是要 5.1.6 版本以上。
要建立分区表的话,你也可以用你的mysql服务器支持的任何存储引擎;mysql分区引擎运行在独立的层次上,并能与其他层次交互。在mysql5.1,一个被分区的表的所有分区必须使用相同的存储引擎;例如,你不能用对一个分区用MyISAM对另一个分区用InnoDB。当然了,你就是在同一个Mysql服务器上,在同一个数据库里对不同的分区了的表使用不同的引擎也是可以的。
要对一个分了区的表指定一个存储引擎,只要用[STORAGE] ENGINE
选项就行了,就像你对没分区的表作的一样。但是,你要注意,在CREATE TABLE语句中,[STORAGE] ENGINE和其他建表参数必须在所有的分区参数之前。如下例子显示了如何去建立一个被hash分成6个区的表,还使用了InnoDB存储引擎。
CREATE TABLE ti (id INT, amount DECIMAL(7,2), tr_date DATE)
ENGINE=INNODB
PARTITION BY HASH( MONTH(tr_date) )
PARTITIONS 6;
(注意,每一个分区子句必须包括[STORAGE] ENGINE
项,但是在MySql5.1里这是没用的)(那你说个p啊)
也可以建立分区的临时表,总之,这样的表的生命周期只有在MySql的会话周期中,这个和没分区的表是一样的。
请注意,分区要用到表的所有数据和索引;你不能只用数据和不用索引,或者vice versa,你也不能把一个表只分成一个区。
在建立分区表时。每个分区的数据和索引可以通过使用DATA DIRECTORY和INDEX DIRECTORY选项定义在一个特定的目录。而且,MAX_ROWS和MIN_ROWS可以用来定义各个分区最大和最小的行数。
分区的好处:
可以在一个表存储更多的数据,比如说一个表一个或者多个硬盘。
不用的数据可以通过卸载分区删除,添加更多数据也可以方便地通过添加新分区。
其他的好处通常和下面的分区方式有关系。这些特性现在还没有实现呢,但是在我们还是很重视的,我们希望在5.1的发布版里包括进来。
因为被分区了,符合WHERE条件的数据可能被放在同一个或者更多分区中,可以排除掉剩下的分区,某些查询可以极大地提高效率。分过区的表也许刚开始对常用的查询效果还不太好,你可以修改分区,重组表来提高查询效率。
包含统计函数,比如SUM()和COUNT()的查询,可以更容易地并行。例如说一个这样的查询:SELECT salesperson_id, COUNT(orders) as order_total FROM sales GROUP BY salesperson_id;
.就是说,这个查询可以同时在各个分区上运行。只要把各个分区的结果和起来就行了。
在多个硬盘上查询提高查询吞吐量,好像RAID。
分区类型
RANGE
分区:通过定义某些列的值域来分区
LIST
分区:与RANGE类似,不过不是值域,而是一些离散值的集合。
HASH
分区:通过用户对将要插入的行里的字段进行计算后的函数返回值来确定分区。无论函数是怎么样的,返回值不能使负数。
KEY
分区:类似于hash分区,但是不用函数,而是用多个列来区分,MySQL服务器会用自身的hash函数来处理。但是这些列必须只有整数。
必须记住,无论用了什么样的分区类型,分区总是在生成时自动编号,从0开始。当新的一行杯插入到一个分区表时,就通过分区编号来确定正确的分区。例如,你的表有4个分区,编号是0,1,2,3。就RANGE和LIST分区来说,必须确保有对应于每个分区编号的分区。对于HASH分区,用户函数必须返回大于0的值。对于KEY分区,就由MySQL服务器自带的hash函数来搞定。
分区命名通常遵从MySQL的一般命名规则,比如表命名和数据库命名。你会发现分区名是大小写不敏感的。例如,以下
CREATE TABLE
是会失败的
mysql> CREATE TABLE t2 (val INT)
-> PARTITION BY LIST(val)(
-> PARTITION mypart VALUES IN (1,3,5),
-> PARTITION MyPart VALUES IN (2,4,6)
-> );
ERROR 1488 (HY000): All partitions must have unique names in the table
错误,所有分区必须用唯一的名字。
因为,MYSQL分不出来mypart
和MyPart
。
值域分区
一个由值域分区的表,就是每个分区包含着符合由分区表达式定义的值域的行。值域应该是连续的,但是不能有重叠。要用VALUES LESS THAN
操作符。例如以下几个例子:假设你要为20家(编号1-20)音像连锁店建立一个职员表。
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '
1970-01-01
',
separated DATE NOT NULL DEFAULT '
9999-12-31
',
job_code INT NOT NULL,
store_id INT NOT NULL
);
这个表可以通过好几种方式值域分区,看你需要了。比如用store_id。例如,你要把表分成4个区,用PARTITION BY RANGE
子句,请看:
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT ' 1970-01-01 ',
separated DATE NOT NULL DEFAULT ' 9999-12-31 ',
job_code INT NOT NULL,
store_id INT NOT NULL
)
PARTITION BY RANGE (store_id) (
PARTITION p0 VALUES LESS THAN (6),
PARTITION p1 VALUES LESS THAN (11),
PARTITION p2 VALUES LESS THAN (16),
PARTITION p3 VALUES LESS THAN (21)
);
这么一来,所有的行对应着工作在店1到店5 职员都存储在分区p0,而对于工作在店1到店5 职员都存储在分区p1,等等。请注意,每个分区要按顺序定义。从低到高。这是PARTITION BY RANGE
格式要求。你可以认为这是在模拟在c或者java中的switch ...
这就很容易了,比如说一行数据(72, 'Michael', 'Widenius', '
1998-06-25
', NULL, 13)肯定被插到分区p2。但如果你添加第21家连锁店的员工呢?这种定义下,没有规则去处理大于20的数据,结果是数据库不知道把它放在哪里而导致一个错误。你可以用一个搞定所有的VALUES LESS THAN子句来处理:
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '
1970-01-01
',
separated DATE NOT NULL DEFAULT '
9999-12-31
',
job_code INT NOT NULL,
store_id INT NOT NULL
)
PARTITION BY RANGE (store_id) (
PARTITION p0 VALUES LESS THAN (6),
PARTITION p1 VALUES LESS THAN (11),
PARTITION p2 VALUES LESS THAN (16),
PARTITION p3 VALUES LESS THAN MAXVALUE
);
MAXVALUE
表示可能有的最大整数。现在,所有store_id比16大的行都放在分区p3了。更有甚者,当连锁店增加到25,30,或者更多,那你就可以用ALTER TABLE语句来添加新的分区到21-25,26-30,等等。
同样的,你可以由职员工号分区,基于job_code列。例如,假设一个两位的工号给店员,3位的给管理和人事,4位的给经理,你可以像下面这样来建表了:
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT ' 1970-01-01 ',
separated DATE NOT NULL DEFAULT ' 9999-12-31 ',
job_code INT NOT NULL,
store_id INT NOT NULL
)
PARTITION BY RANGE (job_code) (
PARTITION p0 VALUES LESS THAN (100),
PARTITION p1 VALUES LESS THAN (1000),
PARTITION p2 VALUES LESS THAN (10000)
);
这个例子,所有店员的数据在分区p0,所有管理和人事的在p1,所有经理的在分区p2。
也可以在VALUES LESS THAN
子句
用一个表达式。但是有一个最要紧的地方要注意,这个表达式的返回值是LESS THAN(<)的一部分,所以,这个返回值不能是NULL。所以hired
, separated
, job_code
, 和store_id
都是NOT NULL的。
相比把表的数据根据店号来划分,你也可以用一个计算DATA的表达式。例如,咱假设你想把表根据职员哪年离职来划分,那么,就是YEAR(separated)
。那就得这么写:
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT ' 1970-01-01 ',
separated DATE NOT NULL DEFAULT ' 9999-12-31 ',
job_code INT,
store_id INT
)
PARTITION BY RANGE ( YEAR(separated) ) (
PARTITION p0 VALUES LESS THAN (1991),
PARTITION p1 VALUES LESS THAN (1996),
PARTITION p2 VALUES LESS THAN (2001),
PARTITION p3 VALUES LESS THAN MAXVALUE
);
在这个例子中,所有在1991年前离职的职员的数据放在分区p0;而1991年到1995年离职的,在分区p1;而1996年到2000年离职的,在分区p3。
值域分区适用场合:
当你要删除旧数据。如果你用了上面的例子来建表,你可以通过语句:
ALTER TABLE employees DROP PARTITION p0
来删除所有在1991年前离职职员的数据。如果一个表有非常多的数据,那这样就比DELETE FROM要更有效率:
DELETE FROM employees WHERE YEAR(separated) <= 1990;
.
你想使用包含时间或者日期的列。
你频繁地查询用来对表分区的列。例如,当执行一个查询:
SELECT COUNT(*) FROM employees WHERE YEAR(separated) = 2000 GROUP BY store_id;
MySQL就能很快地定位到分区p2查询,因为其他的分区的纪录不会满足WHERE条件。备注:这个优化项在MySQL 5.1还无效,持续改进中。
List 分区
List分区在很多方面和值域分区很相似。如同在值域分区中,每一个分区必须明确定义。主要的不同在于,List分区中,每一个分区是基于某一列的值在属于一个集合来定义和查询的,而不是一个连续的值域。使用语句PARTITION BY LIST(expr)
,这里的expr是一个列的值或者是一个通过计算列值而返回一个整数的表达式,还可以由VALUES IN (value_list)子句来定义,value_list就是一个逗号分割的整数列表.
注意:在MySQL 5.1, LIST只能匹配一列整数.
与值域分区不同,值列分区不需要定义分区顺序.详见
Section 13.1.5, “CREATE TABLE
Syntax”.
如下例,我们假设要分区的表基本由这个CREATE TABLE语句来定义:
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '
1970-01-01
',
separated DATE NOT NULL DEFAULT '
9999-12-31
',
job_code INT,
store_id INT
);
(和值域分区定义的例子用的表一样)
假设有20个音像连锁店,分别属于4个地区
Region | Store ID Numbers |
North | 3, 5, 6, 9, 17 |
East | 1, 2, 10, 11, 19, 20 |
West | 4, 12, 13, 14, 18 |
Central | 7, 8, 15, 16 |
要把表分成各行按所在店的区域来存储,你得用这样的CREATE TABLE:
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '
1970-01-01
',
separated DATE NOT NULL DEFAULT '
9999-12-31
',
job_code INT,
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;
这比通过相应的SQL语句查询
DELETE FROM employees WHERE store_id IN (4,12,13,14,18);
.
效率要好得多。
注意,如果你要插入一行,而这分区这一列的值在分区的值的集合里不存在,这个插入语句就会失败,例如,在如上所述的列值分区的表中,这个语句会失败:
INSERT INTO employees VALUES
(224, 'Linus', 'Torvalds', '
2002-05-01
', '
2004-10-12
', 42, 21);
因为store_id值为21,21在分区pNorth
, pEast
, pWest
, or pCentral
定义中没有.要知道,这里可以没有能像VALUES LESS THAN MAXVALUE那样”无差别”的列值分区方法可以容纳列值中没有的值.就是说,所有要匹配的值必须在值列中能找到.
和值域分区一样,也可以把值列分区和哈希分区,主键分区结合起来,成为复合分区.
HASH
分区
Hash分区通常是为了确保数据在指定的分区之的分布平均.就值域和值列分区,你必须明确定义数据要被存储在哪个分区;而对于HASH分区,MySQL会帮你搞定,你只要定义要被HASH的一个列或者一个计算列的表达式和被分区表该分成几个区.
对应用HASH分区的表,必须在CREATE TABLE 语句后面加上PARTITION BY HASH (expr)语句,而expr是一个返回整数的表达式。而且,你也要加上一个PARTITIONS num(非负数)语句,表示这个表被分成多少个部分。
例如,以下语句建立一个对store_id作hash分区的表,分成4个分区
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '
1970-01-01
',
separated DATE NOT NULL DEFAULT '
9999-12-31
',
job_code INT,
store_id INT
)
PARTITION BY HASH(store_id)
PARTITIONS 4;
如果你使用PARTITIONS语句,分区数量缺省是1.例外的是,对于NDB簇的表,缺省分区数量和数据节点数量一样.为了确保所有记录能存储在分区里,可以修改MAX_SETTING配置.
使用不带数量的PARTITIOINS关键字是语法错误的.
你也可以用返回整数的表达式.例如,你要根据职员的雇佣年份来分区,请看 :
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '
1970-01-01
',
separated DATE NOT NULL DEFAULT '
9999-12-31
',
job_code INT,
store_id INT
)
PARTITION BY HASH( YEAR(hired) )
PARTITIONS 4;
你可以用MySQL支持的任何函数或者表达式,只要返回的值不是常量,也不能是随机整数.(换言之,变化必须是确定性的)总之,你要谨记,每次插入或者更新行的时候,都要计算一遍,就是说太复杂的表达式会影响运行性能.特别是做批量插入的时候.
待续……