您的位置:首页 > 数据库 > MySQL

MySql分区表

2016-11-09 13:56 281 查看
MySql分区表

传统的分库分表都是在应用层实现,拆分后都要对原有系统进行很大的调整以适应新拆分后的库或表,比如实现一个SQL中间件、原本的联表查询改成两次查询、实现一个全局主键生成器等等。

而下面介绍的MySQL分区表是在数据库层面,MySQL自己实现的分表功能,在很大程度上简化了分表的难度。

介绍

    对用户来说,分区表是一个独立的逻辑表,但是底层由多个物理子表实现。

也就是说,对于原表分区后,对于应用层来说可以不做变化,我们无需改变原有的SQL语句,相当于MySQL帮我们实现了传统分表后的SQL中间件,当然,MySQL的分区表的实现要复杂很多。

另外,在创建分区时可以指定分区的索引文件和数据文件的存储位置,所以可以把数据表的数据分布在不同的物理设备上,从而高效地利用多个硬件设备。

一些限制:

1.在5.6.7之前的版本,一个表最多有1024个分区;从5.6.7开始,一个表最多可以有8192个分区。

2.分区表中无法使用外键约束。

3.主表的所有唯一索引列(包括主键)都必须包含分区字段。MySQL官方文档中写的是:

    All columns used in the partitioning expression for a partitioned

    table must be part of every unique key that the table may have.

这句话不是很好理解,需要通过例子才能明白,MySQL官方文档也为此限制特意做了举例和解释。

分区表的类型

RANGE分区

根据范围分区,范围应该连续但是不重叠,使用PARTITION BY RANGE, VALUES LESS THAN关键字。不使用COLUMNS关键字时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 MAXVALUE

);

根据TIMESTAMP范围:

CREATE TABLE quarterly_report_status (

    report_id INT NOT NULL,

    report_status VARCHAR(20) NOT NULL,

    report_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

)

PARTITION BY RANGE ( UNIX_TIMESTAMP(report_updated) ) (

    PARTITION p0 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-01-01 00:00:00') ),

    PARTITION p1 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-04-01 00:00:00') ),

    PARTITION p2 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-07-01 00:00:00') ),

4000
    PARTITION p3 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-10-01 00:00:00') ),

    PARTITION p4 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-01-01 00:00:00') ),

    PARTITION p5 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-04-01 00:00:00') ),

    PARTITION p6 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-07-01 00:00:00') ),

    PARTITION p7 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-10-01 00:00:00') ),

    PARTITION p8 VALUES LESS THAN ( UNIX_TIMESTAMP('2010-01-01 00:00:00') ),

    PARTITION p9 VALUES LESS THAN (MAXVALUE)

);

添加COLUMNS关键字可定义非integer范围及多列范围,不过需要注意COLUMNS括号内只能是列名,不支持函数;多列范围时,多列范围必须呈递增趋势:

根据DATE、DATETIME范围:

CREATE TABLE members (

    firstname VARCHAR(25) NOT NULL,

    lastname VARCHAR(25) NOT NULL,

    username VARCHAR(16) NOT NULL,

    email VARCHAR(35),

    joined DATE NOT NULL

)

PARTITION BY RANGE COLUMNS(joined) (

    PARTITION p0 VALUES LESS THAN ('1960-01-01'),

    PARTITION p1 VALUES LESS THAN ('1970-01-01'),

    PARTITION p2 VALUES LESS THAN ('1980-01-01'),

    PARTITION p3 VALUES LESS THAN ('1990-01-01'),

    PARTITION p4 VALUES LESS THAN MAXVALUE

);

根据多列范围:

CREATE TABLE rc3 (

    a INT,

    b INT

)

PARTITION BY RANGE COLUMNS(a,b) (

    PARTITION p0 VALUES LESS THAN (0,10),

    PARTITION p1 VALUES LESS THAN (10,20),

    PARTITION p2 VALUES LESS THAN (10,30),

    PARTITION p3 VALUES LESS THAN (10,35),

    PARTITION p4 VALUES LESS THAN (20,40),

    PARTITION p5 VALUES LESS THAN (MAXVALUE,MAXVALUE)

 );

List分区

根据具体数值分区,每个分区数值不重叠,使用PARTITION BY LIST、VALUES IN关键字。跟Range分区类似,不使用COLUMNS关键字时List括号内必须为整数字段名或返回确定整数的函数。

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)

);

数值必须被所有分区覆盖,否则插入一个不属于任何一个分区的数值会报错。

mysql> CREATE TABLE h2 (

    ->   c1 INT,

    ->   c2 INT

    -> )

    -> PARTITION BY LIST(c1) (

    ->   PARTITION p0 VALUES IN (1, 4, 7),

    ->   PARTITION p1 VALUES IN (2, 5, 8)

    -> );

Query OK, 0 rows affected (0.11 sec)

mysql> INSERT INTO h2 VALUES (3, 5);

ERROR 1525 (HY000): Table has no partition for value 3

当插入多条数据出错时,如果表的引擎支持事务(Innodb),则不会插入任何数据;如果不支持事务,则出错前的数据会插入,后面的不会执行。

可以使用IGNORE关键字忽略出错的数据,这样其他符合条件的数据会全部插入不受影响。

mysql> TRUNCATE h2;

Query OK, 1 row affected (0.00 sec)

mysql> SELECT * FROM h2;

Empty set (0.00 sec)

mysql> INSERT IGNORE INTO h2 VALUES (2, 5), (6, 10), (7, 5), (3, 1), (1, 9);

Query OK, 3 rows affected (0.00 sec)

Records: 5  Duplicates: 2  Warnings: 0

mysql> SELECT * FROM h2;

+------+------+

| c1   | c2   |

+------+------+

|    7 |    5 |

|    1 |    9 |

|    2 |    5 |

+------+------+

3 rows in set (0.00 sec)

与Range分区相同,添加COLUMNS关键字可支持非整数和多列。

Hash分区

Hash分区主要用来确保数据在预先确定数目的分区中平均分布,Hash括号内只能是整数列或返回确定整数的函数,实际上就是使用返回的整数对分区数取模。

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;

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;

Hash分区也存在与传统Hash分表一样的问题,可扩展性差。MySQL也提供了一个类似于一致Hash的分区方法-线性Hash分区,只需要在定义分区时添加LINEAR关键字,如果对实现原理感兴趣,可以查看官方文档。

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 LINEAR HASH( YEAR(hired) )

PARTITIONS 4;

Key分区

    按照KEY进行分区类似于按照HASH分区,除了HASH分区使用的用户定义的表达式,而KEY分区的 哈希函数是由MySQL

    服务器提供。MySQL 簇(Cluster)使用函数MD5()来实现KEY分区;对于使用其他存储引擎的表,服务器使用其自己内部的

    哈希函数,这些函数是基于与PASSWORD()一样的运算法则。

Key分区与Hash分区很相似,只是Hash函数不同,定义时把Hash关键字替换成Key即可,同样Key分区也有对应与线性Hash的线性Key分区方法。

CREATE TABLE tk (

    col1 INT NOT NULL,

    col2 CHAR(5),

    col3 DATE

)

PARTITION BY LINEAR KEY (col1)

PARTITIONS 3;

另外,当表存在主键或唯一索引时可省略Key括号内的列名,Mysql将按照主键-唯一索引的顺序选择,当找不到唯一索引时报错。

子分区

子分区是分区表中每个分区的再次分割。创建子分区方法:

CREATE TABLE ts (id INT, purchased DATE)

    PARTITION BY RANGE( YEAR(purchased) )

    SUBPARTITION BY HASH( TO_DAYS(purchased) )

    SUBPARTITIONS 2 (

        PARTITION p0 VALUES LESS THAN (1990),

        PARTITION p1 VALUES LESS THAN (2000),

        PARTITION p2 VALUES LESS THAN MAXVALUE

    );



CREATE TABLE ts (id INT, purchased DATE)

    PARTITION BY RANGE( YEAR(purchased) )

    SUBPARTITION BY HASH( TO_DAYS(purchased) ) (

        PARTITION p0 VALUES LESS THAN (1990) (

            SUBPARTITION s0

                DATA DIRECTORY = '/disk0/data'

                INDEX DIRECTORY = '/disk0/idx',

            SUBPARTITION s1

                DATA DIRECTORY = '/disk1/data'

                INDEX DIRECTORY = '/disk1/idx'

        ),

        PARTITION p1 VALUES LESS THAN (2000) (

            SUBPARTITION s2

                DATA DIRECTORY = '/disk2/data'

                INDEX DIRECTORY = '/disk2/idx',

            SUBPARTITION s3

                DATA DIRECTORY = '/disk3/data'

                INDEX DIRECTORY = '/disk3/idx'

        ),

        PARTITION p2 VALUES LESS THAN MAXVALUE (

            SUBPARTITION s4

                DATA DIRECTORY = '/disk4/data'

                INDEX DIRECTORY = '/disk4/idx',

            SUBPARTITION s5

                DATA DIRECTORY = '/disk5/data'

                INDEX DIRECTORY = '/disk5/idx'

        )

    );

需要注意的是:每个分区的子分区数必须相同。如果在一个分区表上的任何分区上使用SUBPARTITION来明确定义任何子分区,那么就必须定义所有的子分区,且必须指定一个全表唯一的名字。

分区表的使用及查询优化

根据实际情况选择分区方法

对现有表分区的原则与传统分表一样。

传统的按照增量区间分表对应于分区的Range分区,比如对表的访问多是近期产生的新数据,历史数据访问较少,则可以按一定时间段(比如年或月)或一定数量(比如100万)对表分区,具体根据哪种取决于表索引结构。分区后最后一个分区即为近期产生的数据,当一段时间过后数据量再次变大,可对最后一个分区重新分区(REORGANIZE PARTITION)把一段时间(一年或一月)或一定数量(比如100万)的数据分离出去。

传统的散列方法分表对应于分区的Hash/Key分区,具体方法上面已经介绍过。

查询优化

分区的目的是为了提高查询效率,如果查询范围是所有分区那么就说明分区没有起到作用,我们用explain partitions命令来查看SQL对于分区的使用情况。

一般来说,就是在where条件中加入分区列。

比如表salaries结构为:

mysql> show create table salaries\G;

*************************** 1. row ***************************

       Table: salaries

Create Table: CREATE TABLE `salaries` (

  `emp_no` int(11) NOT NULL,

  `salary` int(11) NOT NULL,

  `from_date` date NOT NULL,

  `to_date` date NOT NULL,

  PRIMARY KEY (`emp_no`,`from_date`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8

/*!50100 PARTITION BY RANGE (year(from_date))

(PARTITION p1 VALUES LESS THAN (1985) ENGINE = InnoDB,

 PARTITION p2 VALUES LESS THAN (1986) ENGINE = InnoDB,

 PARTITION p3 VALUES LESS THAN (1987) ENGINE = InnoDB,

 PARTITION p4 VALUES LESS THAN (1988) ENGINE = InnoDB,

 PARTITION p5 VALUES LESS THAN (1989) ENGINE = InnoDB,

 PARTITION p6 VALUES LESS THAN (1990) ENGINE = InnoDB,

 PARTITION p7 VALUES LESS THAN (1991) ENGINE = InnoDB,

 PARTITION p8 VALUES LESS THAN (1992) ENGINE = InnoDB,

 PARTITION p9 VALUES LESS THAN (1993) ENGINE = InnoDB,

 PARTITION p10 VALUES LESS THAN (1994) ENGINE = InnoDB,

 PARTITION p11 VALUES LESS THAN (1995) ENGINE = InnoDB,

 PARTITION p12 VALUES LESS THAN (1996) ENGINE = InnoDB,

 PARTITION p13 VALUES LESS THAN (1997) ENGINE = InnoDB,

 PARTITION p14 VALUES LESS THAN (1998) ENGINE = InnoDB,

 PARTITION p15 VALUES LESS THAN (1999) ENGINE = InnoDB,

 PARTITION p16 VALUES LESS THAN (2000) ENGINE = InnoDB,

 PARTITION p17 VALUES LESS THAN (2001) ENGINE = InnoDB,

 PARTITION p18 VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */

则下面的查询没有利用分区,因为partitions中包含了所有的分区:

mysql> explain partitions select * from salaries where salary > 100000\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: salaries

   partitions: p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16,p17,p18

         type: ALL

possible_keys: NULL

          key: NULL

      key_len: NULL

          ref: NULL

         rows: 2835486

        Extra: Using where

只有在where条件中加入分区列才能起到作用,过滤掉不需要的分区:

mysql> explain partitions select * from salaries where salary > 100000 and from_date > '1998-01-01'\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: salaries

   partitions: p15,p16,p17,p18

         type: ALL

possible_keys: NULL

          key: NULL

      key_len: NULL

          ref: NULL

         rows: 1152556

        Extra: Using where

与普通搜索一样,在运算符左侧使用函数将使分区过滤失效,即使与分区函数想同也一样:

mysql> explain partitions select * from salaries where salary > 100000 and year(from_date) > 1998\G;

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: salaries

   partitions: p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16,p17,p18

         type: ALL

possible_keys: NULL

          key: NULL

      key_len: NULL

          ref: NULL

         rows: 2835486

        Extra: Using where

分区和分表的比较

    传统分表后,count、sum等统计操作只能对所有切分表进行操作后之后在应用层再次计算得出最后统计数据。而分区表则不受影响,可直接统计。

    Queries involving aggregate functions such as SUM() and COUNT() can easily be parallelized. A simple example of such a query might be SELECT salesperson_id, COUNT(orders) as order_total FROM sales GROUP BY salesperson_id;. By “parallelized,” we mean that
the query can be run simultaneously on each partition, and the final result obtained merely by summing the results obtained for all partitions.

    分区对原系统改动最小,分区只涉及数据库层面,应用层不需要做出改动。

    分区有个限制是主表的所有唯一字段(包括主键)必须包含分区字段,而分表没有这个限制。

    分表包括垂直切分和水平切分,而分区只能起到水平切分的作用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: