分区&分表

[目录]

为什么要分表和分区?

我们的数据库数据越来越大,随之而来的是单个表中数据太多。以至于查询速度变慢,而且由于表的锁机制导致应用操作也搜到严重影响,出现了数据库性能瓶颈。

mysql中有一种机制是表锁定和行锁定,是为了保证数据的完整性。表锁定表示你们都不能对这张表进行操作,必须等我对表操作完才行。行锁定也一样,别的sql必须等我对这条数据操作完了,才能对这条数据进行操作。当出现这种情况时,我们可以考虑分表或分区。

I. 分表d

什么是分表?

分表是将一个大表按照一定的规则分解成多张具有独立存储空间的实体表,每个表都对应三个文件,MYD数据文件,.MYI索引文件,.frm表结构文件。这些表可以分布在同一块磁盘上,也可以在不同的机器上。app读写的时候根据事先定义好的规则得到对应的表名,然后去操作它。

将单个数据库表进行拆分,拆分成多个数据表,然后用户访问的时候,根据一定的算法(如用hash的方式,也可以用求余(取模)的方式),让用户访问不同的表,这样数据分散到多个数据表中,减少了单个数据表的访问压力提升了数据库访问性能

分表的目的就在于此,减小数据库的负担,缩短查询时间

返回目录

Mysql分表分为垂直切分和水平切分

垂直切分

是指数据表列的拆分,把一张列比较多的表拆分为多张表

垂直拆分原则:

  • 把不常用的字段单独放在一张表;
  • 把text,blob(binary large object,二进制大对象)等大字段拆分出来放在附表中;
  • 经常组合查询的列放在一张表中;
  • 垂直拆分更多时候就应该在数据表设计之初就执行的步骤,然后查询的时候用jion关键起来即可。

水平拆分

是指数据表行的拆分,把一张的表的数据拆成多张表来存放。

水平拆分原则:

  • 通常情况下,我们使用hash、取模等方式来进行表的拆分 比如一张有400W的用户表users,为提高其查询效率我们把其分成4张表users1,users2,users3,users4 通过用ID取模的方法把数据分散到四张表内Id%4= [0,1,2,3] 然后查询,更新,删除也是通过取模的方法来查询
  • 部分业务逻辑也可以通过地区,年份等字段来进行归档拆分; 进行拆分后的表,这时我们就要约束用户查询行为。比如我们是按年来进行拆分的,这个时候在页面设计上就约束用户必须要先选择年,然后才能进行查询。

返回目录

分表的几种方式:

  • 1)mysql集群

它并不是分表,但起到了和分表相同的作用。集群可分担数据库的操作次数,将任务分担到多台数据库上。集群可以读写分离,减少读写压力。从而提升数据库性能。

  • 2)预先估计会出现大数据量并且访问频繁的表,将其分为若干个表(在数据写入之前就分表)

根据一定的算法(如用hash的方式,也可以用求余(取模)的方式)让用户访问不同的表。此时sql的代码需要更改。

例如论坛里面发表帖子的表,时间长了这张表肯定很大,几十万,几百万都有可能。 聊天室里面信息表,几十个人在一起一聊一个晚上,时间长了,这张表的数据肯定很大。像这样的情况很多。所以这种能预估出来的大数据量表,我们就事先分出个N个表,这个N是多少,根据实际情况而定。以聊天信息表为例:我们事先建100个这样的表,message_00,message_01,message_02..........message_98,message_99.然后根据用户的ID来判断这个用户的聊天信息放到哪张表里面,可以用hash的方式来获得,也可以用求余的方式来获得,方法很多。 或者可以设计每张表容纳的数据量是N条.

那么如何判断某张表的数据是否容量已满呢?

可以在程序段对于要新增数据的表,在插入前先做统计表记录数量的操作,当<N条数据,就直接插入,当已经到达阀值,可以在程序段新创建数据库表(或者已经事先创建好),再执行插入操作)。

  • 3)利用merge存储引擎来实现分表(在表写完数据之后)(只适用与myisam的存储引擎)

如果要把已有的大数据量表分开比较痛苦,最痛苦的事就是改代码,因为程序里面的sql语句已经写好了,用merge存储引擎来实现分表, 这种方法比较适合。

merge分表,分为主表和子表主表类似于一个壳子,逻辑上封装了子表,实际上数据都是存储在子表中的。(这种方式的话:sql的代码不需要太多更改)
我们可以通过主表插入和查询数据,如果清楚分表规律,也可以直接操作子表。

返回目录

merge存储引擎来分表:分表myisam

  1. 创建一个完整表存储着所有的成员信息(表名为member)
mysql> drop database IF EXISTS test;
mysql>create database test;
mysql> use test;
create table member(
id bigint auto_increment primary key,  (自动增长)
name varchar(20),
sex tinyint not null default '0'    //(微型的整数型,0-255,)(bitint为只有0和1)
)engine=myisam default charset=utf8 auto_increment=1;      //每次增长1
  1. 加入数据:
mysql> insert into member(name,sex) values('tom1',1);
mysql> insert into member(name,sex) select name,sex from member;      //将查出来的字段插入到这个表中的字段中,快速增长数据。

第二条语句多执行几次就有了很多数据

mysql> select * from member;
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 1 | tom1 | 1 |
| 2 | tom1 | 1 |
| 3 | tom1 | 1 |
| 4 | tom1 | 1 |
| 5 | tom1 | 1 |
| 6 | tom1 | 1 |
| 7 | tom1 | 1 |
| 8 | tom1 | 1 |
| 9 | tom1 | 1 |
| 10 | tom1 | 1 |
| 11 | tom1 | 1 |
| 12 | tom1 | 1 |
| 13 | tom1 | 1 |
| 14 | tom1 | 1 |
| 15 | tom1 | 1 |
| 16 | tom1 | 1 |
+----+------+-----+

返回目录

  1. 这里我们把member分两个表tb_member1,tb_member2 先创建这两个字表。
mysql> use test;
DROP table IF EXISTS tb_member1;
create table tb_member1(
  id bigint primary key ,
  name varchar(20),
  sex tinyint not null default '0'
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ;
mysql>DROP table IF EXISTS tb_member2;
create table tb_member2(
  id bigint primary key,
  name varchar(20),
  sex tinyint not null default '0'
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

或者:

//创建tb_member2也可以用下面的语句

create table tb_member2 like tb_member1;
  1. 创建主表tb_member (建立一个壳子的表结构)
mysql>DROP table IF EXISTS tb_member;
create table tb_member(
id bigint primary key ,
name varchar(20),
sex tinyint not null default '0'
) ENGINE=MERGE UNION=(tb_member1,tb_member2) INSERT_METHOD=LAST CHARSET=utf8 ;

注: INSERT_METHOD,此参数INSERT_METHOD = NO 表示该表(这个主表)不能做任何写入操作只作为查询使用

INSERT_METHOD = LAST表示插入到最后的一张子表里面。``INSERT_METHOD = first表示插入到第一张子表里面。

查看一下tb_member表的结构:

mysql> desc tb_member;
mysql> desc tb_member;
+-------+-------------+------+-----+---------+-----------------------------------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-----------------------------------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
| sex | tinyint(4) | NO | | 0 | |
+-------+-------------+------+-----+---------+------------------------------------------+
3 rows in set (0.00 sec)

注: 查看子表与主表的字段定义要一致

  1. 把数据分到两个分表中去: (从真实表中)
mysql> insert into tb_member1(id,name,sex) select id,name,sex from member where id%2=0;
mysql> insert into tb_member2(id,name,sex) select id,name,sex from member where id%2=1;

返回目录

查看两个子表的数据:

mysql> select * from tb_member1;
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 16 | tom1 | 1 |
| 14 | tom1 | 1 |
| 12 | tom1 | 1 |
| 10 | tom1 | 1 |
| 8 | tom1 | 1 |
| 6 | tom1 | 1 |
| 4 | tom1 | 1 |
| 2 | tom1 | 1 |
+----+------+-----+
8 rows in set (0.00 sec)
mysql> select * from tb_member2;
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 3 | tom1 | 1 |
| 1 | tom1 | 1 |
| 5 | tom1 | 1 |
| 7 | tom1 | 1 |
| 9 | tom1 | 1 |
| 11 | tom1 | 1 |
| 13 | tom1 | 1 |
| 15 | tom1 | 1 |
+----+------+-----+
8 rows in set (0.00 sec)

查看一下主表的数据:

mysql> select * from tb_member;
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 16 | tom1 | 1 |
| 14 | tom1 | 1 |
| 12 | tom1 | 1 |
| 10 | tom1 | 1 |
| 8 | tom1 | 1 |
| 6 | tom1 | 1 |
| 4 | tom1 | 1 |
| 2 | tom1 | 1 |
| 15 | tom1 | 1 |
| 13 | tom1 | 1 |
| 11 | tom1 | 1 |
| 9 | tom1 | 1 |
| 7 | tom1 | 1 |
| 5 | tom1 | 1 |
| 3 | tom1 | 1 |
| 1 | tom1 | 1 |
+----+------+-----+
16 rows in set (0.00 sec)
mysql> select * from tb_member where id=3;
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 3 | tom1 | 1 |
+----+------+-----+
1 row in set (0.00 sec)

注意:

总表只是一个外壳,存取数据发生在一个一个的子表里面。

注意:

每个子表都有自已独立的相关表文件,而主表只是一个壳,并没有完整的相关表文件

[root@localhost ~]# ls -l /usr/local/mysql/data/test/tb_member*
-rw-r-----. 1 mysql mysql 8614 Sep 15 21:49 /usr/local/mysql/data/test/tb_member1.frm
-rw-r-----. 1 mysql mysql 320  Sep 16 00:02 /usr/local/mysql/data/test/tb_member1.MYD
-rw-r-----. 1 mysql mysql 2048 Sep 16 00:43 /usr/local/mysql/data/test/tb_member1.MYI
-rw-r-----. 1 mysql mysql 8614 Sep 15 21:50 /usr/local/mysql/data/test/tb_member2.frm
-rw-r-----. 1 mysql mysql 180  Sep 16 00:02 /usr/local/mysql/data/test/tb_member2.MYD
-rw-r-----. 1 mysql mysql 2048 Sep 16 00:43 /usr/local/mysql/data/test/tb_member2.MYI
-rw-r-----. 1 mysql mysql 8614 Sep 16 21:12 /usr/local/mysql/data/test/tb_member3.frm
-rw-r-----. 1 mysql mysql 0    Sep 16 21:12 /usr/local/mysql/data/test/tb_member3.MYD
-rw-r-----. 1 mysql mysql 1024 Sep 16 21:12 /usr/local/mysql/data/test/tb_member3.MYI
-rw-r-----. 1 mysql mysql 8614 Sep 16 21:14 /usr/local/mysql/data/test/tb_member.frm
-rw-r-----. 1 mysql mysql 53   Sep 16 21:14 /usr/local/mysql/data/test/tb_member.MRG

返回目录


II. 分区

什么是分区?

分区和分表相似,都是按照规则分解表。不同在于分表将大表分解为若干个独立的实体表,而分区是将数据分段划分在多个位置存放,分区后,表还是一张表,但数据散列到多个位置了。app读写的时候操作的还是表名字,db自动去组织分区的数据。

返回目录

分区主要有两种形式:

水平分区

(Horizontal Partitioning) 这种形式分区是对表的行进行分区,所有在表中定义的列在每个数据集中都能找到,所以表的特性依然得以保持。 举个简单例子:一个包含十年发票记录的表可以被分区为十个不同的分区,每个分区包含的是其中一年的记录。

垂直分区

(Vertical Partitioning) 这种分区方式一般来说是通过对表的垂直划分来减少目标表的宽度,使某些特定的列被划分到特定的分区,每个分区都包含了其中的列所对应的行。 举个简单例子:一个包含了大text和BLOB列的表,这些text和BLOB列又不经常被访问,这时候就要把这些不经常使用的text和BLOB了划分到另一个分区,在保证它们数据相关性的同时还能提高访问速度。

返回目录

分区技术支持

在5.6之前

查看当前配置是否支持分区

mysql> SHOW VARIABLES LIKE '%partition%';
+-----------------------+---------------+
|Variable_name | Value |
+-----------------------+---------------+
| have_partition_engine | YES |
+-----------------------+------------------+

如果是yes表示你当前的配置支持分区

在5.6及以后,

则采用如下方式进行查看

mysql> show plugins;

在显示结果中,可以看到partition是·ACTIVE的,表示支持分区

| MyISAM                     | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| MRG_MYISAM                 | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| PERFORMANCE_SCHEMA         | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| ARCHIVE                    | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| BLACKHOLE                  | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| FEDERATED                  | DISABLED | STORAGE ENGINE     | NULL    | GPL     |
| partition                  | ACTIVE   | STORAGE ENGINE     | NULL    | GPL     |
| ngram                      | ACTIVE   | FTPARSER           | NULL    | GPL

返回目录

按照范围(range)方式的表分区

  1. 创建range分区的表
mysql> use test2;
mysql> create table if not exist user (
  -> id int not null auto_increment,
  -> name varchar(30) not null default '',
  -> sex int(1) not null default '0',
  -> primary key(id)
  -> )default charset=utf8 auto_increment=1         //默认步长为1
  -> partition by range(id) (
  -> partition p0 values less than (3),
  -> partition p1 values less than (6),        //分区存放在了同一个位置
  -> partition p2 values less than (9),
  -> partition p3 values less than (12),
  -> partition p4 values less than maxvalue
-> );
help create table;      将分区存放在不同的存储位置

partition_definition:
    PARTITION partition_name
        [VALUES
            {LESS THAN {(expr | value_list) | MAXVALUE}
            |
            IN (value_list)}]
        [[STORAGE] ENGINE [=] engine_name]
        [COMMENT [=] 'comment_text' ]
        [DATA DIRECTORY [=] 'data_dir']
        [INDEX DIRECTORY [=] 'index_dir']
        [MAX_ROWS [=] max_number_of_rows]
        [MIN_ROWS [=] min_number_of_rows]
        [TABLESPACE [=] tablespace_name]
        [(subpartition_definition [, subpartition_definition] ...)]

将分区存放在不同的存储位置

help create table ;   
mkdir -p /date/{area1,area2}     //创建数据文件和索引文件的位置:不能和mysql的存储路径相同(要存放在不同位置)
[root@node2 data]# chown -R mysql /data            //更改文件的属主
[root@node2 data]# ll
total 0
drwxr-xr-x. 2 mysql root 6 Sep 21 23:18 area1
drwxr-xr-x. 2 mysql root 6 Sep 21 23:18 area2
mysql> create table ceshi ( id int auto_increment, name varchar(30), primary key(id) ) engine=myisam default charset=utf8 auto_increment=1 partition by range(id) ( partition p0 values less than (3) data directory '/data/area1/'  index directory '/data/area1/', partition p1  values less than (6) data directory '/data/area2/' index directory '/data/area2/' );
Query OK, 0 rows affected (0.00 sec)

注意:

默认为innodb的引擎,没有index的分区位置,所以此时不能再将index directory 指定位置。

[root@node2 data]# ll area1
total 4
-rw-r-----. 1 mysql mysql    0 Sep 21 23:41 ceshi#P#p0.MYD
-rw-r-----. 1 mysql mysql 1024 Sep 21 23:41 ceshi#P#p0.MYI
[root@node2 data]# ll area2
total 4
-rw-r-----. 1 mysql mysql    0 Sep 21 23:41 ceshi#P#p1.MYD
-rw-r-----. 1 mysql mysql 1024 Sep 21 23:41 ceshi#P#p1.MYI

这是因为开启了独享表空间的选项,才会生成存放数据库的分区和存放索引的分区文件。

mysql> show variables like '%innodb_file_per_table%';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_file_per_table | ON    |
+-----------------------+-------+
1 row in set (0.01 sec)

否则将会在共享表空间之中形成存放的文件。

[root@node2 data]# cd /usr/local/mysql/data/
[root@node2 data]# ls
auto.cnf        ibdata1      ibtmp1      performance_schema  test2
ddl_log.log     ib_logfile0  mysql       sys
ib_buffer_pool  ib_logfile1  mysqld.err  test

插入些数据

mysql> insert into test2.user(name,sex)values ('tom1','0');
mysql> insert into test2.user(name,sex)values ('tom2','1');
mysql> insert into test2.user(name,sex)values ('tom3','1');
mysql> insert into test2.user(name,sex)values ('tom4','0');
mysql> insert into test2.user(name,sex)values ('tom5','0');
mysql> insert into test2.user(name,sex)values ('tom6','1');
mysql> insert into test2.user(name,sex)values ('tom7','1');
mysql> insert into test2.user(name,sex)values ('tom8','1');
mysql> insert into test2.user(name,sex)values ('tom9','1');
mysql> insert into test2.user(name,sex)values ('tom10','1');
mysql> insert into test2.user(name,sex)values ('tom11','1');
mysql> insert into test2.user(name,sex)values ('tom12','1');
mysql> insert into test2.user(name,sex)values ('tom13','1');
mysql> insert into test2.user(name,sex)values ('tom14','1');

返回目录

到存放数据库表文件的地方看一下

[root@localhost ~]# ls -l /usr/local/mysql/data/test2/user*
-rw-r-----. 1 mysql mysql 8614 Sep 16 21:46  /usr/local/mysql/data/test2/user.frm
-rw-r-----. 1 mysql mysql 98304 Sep 16 21:48 /usr/local/mysql/data/test2/user#P#p0.ibd
-rw-r-----. 1 mysql mysql 98304 Sep 16 21:48 /usr/local/mysql/data/test2/user#P#p1.ibd
-rw-r-----. 1 mysql mysql 98304 Sep 16 21:49 /usr/local/mysql/data/test2/user#P#p2.ibd
-rw-r-----. 1 mysql mysql 98304 Sep 16 21:49 /usr/local/mysql/data/test2/user#P#p3.ibd
-rw-r-----. 1 mysql mysql 98304 Sep 16 21:49 /usr/local/mysql/data/test2/user#P#p4.ibd
mysql> select count(id) as count from user;   //count=count(id)起到命名的作用
+-------+
| count |
+-------+
| 14 |
+-------+
1 row in set (0.00 sec)

从information_schema 系统库中的partitions表中查看分区信息

mysql>select * from information_schema.partitions where table_schema='test2' and table_name='user'\G;

*************************** 5. row ***************************
                TABLE_CATALOG: def
                 TABLE_SCHEMA: test2
                   TABLE_NAME: user
               PARTITION_NAME: p4
            SUBPARTITION_NAME: NULL
   PARTITION_ORDINAL_POSITION: 5
SUBPARTITION_ORDINAL_POSITION: NULL
             PARTITION_METHOD: RANGE
          SUBPARTITION_METHOD: NULL
         PARTITION_EXPRESSION: id
      SUBPARTITION_EXPRESSION: NULL
        PARTITION_DESCRIPTION: MAXVALUE
                   TABLE_ROWS: 0
               AVG_ROW_LENGTH: 0
                  DATA_LENGTH: 16384
              MAX_DATA_LENGTH: NULL
                 INDEX_LENGTH: 0
                    DATA_FREE: 0
                  CREATE_TIME: 2016-09-20 00:22:31
                  UPDATE_TIME: 2016-09-20 00:24:56
                   CHECK_TIME: NULL
                     CHECKSUM: NULL
            PARTITION_COMMENT:
                    NODEGROUP: default
              TABLESPACE_NAME: NULL

返回目录

从某个分区中查询数据

mysql> select * from test2.user partition(p0);

新增分区

mysql> alter table test2.user add partition (partition partionname values less than (n));

删除分区 当删除了一个分区,也同时删除了该分区中所有的数据。

ALTER TABLE test2.user DROP PARTITION p3;

分区的合并 下面的SQL,将p1 – p3合并为2个分区p01 – p02

mysql> alter table test2.user
  -> reorganize partition p1,p2,p3 into
  -> (partition p01 values less than (8),
  -> partition p02 values less than (12)
  -> );
[root@localhost ~]# ls -l /usr/local/mysql/data/test2/user*
-rw-r-----. 1 mysql mysql 8614 Sep 16 22:06 /usr/local/mysql/data/test2/user.frm
-rw-r-----. 1 mysql mysql 98304 Sep 16 22:06 /usr/local/mysql/data/test2/user#P#p01.ibd
-rw-r-----. 1 mysql mysql 98304 Sep 16 22:06 /usr/local/mysql/data/test2/user#P#p02.ibd
-rw-r-----. 1 mysql mysql 98304 Sep 16 21:48 /usr/local/mysql/data/test2/user#P#p0.ibd
-rw-r-----. 1 mysql mysql 98304 Sep 16 21:49 /usr/local/mysql/data/test2/user#P#p4.ibd
mysql> select * from test2.user partition(p01);
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 3 | tom3 | 1 |
| 4 | tom4 | 0 |
| 5 | tom5 | 0 |
| 6 | tom6 | 1 |
| 7 | tom7 | 1 |
+----+------+-----+
5 rows in set (0.00 sec)

返回目录

未分区表和分区表性能测试

  1. 创建一个未分区的表
mysql> create table test2.tab1(c1 int,c2 varchar(30),c3 date);
  1. 创建分区表,按日期的年份拆分
mysql> CREATE TABLE test2.tab2 ( c1 int, c2 varchar(30) , c3 date )
PARTITION BY RANGE (year(c3)) (PARTITION p0 VALUES LESS THAN (1995),
PARTITION p1 VALUES LESS THAN (1996) , PARTITION p2 VALUES LESS THAN (1997) ,
PARTITION p3 VALUES LESS THAN (1998) , PARTITION p4 VALUES LESS THAN (1999) ,
PARTITION p5 VALUES LESS THAN (2000) , PARTITION p6 VALUES LESS THAN (2001) ,
PARTITION p7 VALUES LESS THAN (2002) , PARTITION p8 VALUES LESS THAN (2003) ,
PARTITION p9 VALUES LESS THAN (2004) , PARTITION p10 VALUES LESS THAN (2010),
PARTITION p11 VALUES LESS THAN MAXVALUE );

注意:

最后一行,考虑到可能的最大值 通过存储过程插入100万条测试数据

返回目录

  1. 创建存储过程:
mysql> delimiter $$       //指定存储过程结束符,自定义的
mysql>CREATE PROCEDURE load_part_tab()
  begin
  declare v int default 0;
  while v < 1000000
  do
  insert into test2.tab1
  values (v,'testing partitions',adddate('1995-01-01',(rand(v)*36520) mod 3652));
  set v = v + 1;
  end while;
  end
  $$

注:

RAND()函数在0和1之间产生一个随机数,如果一个整数参数N被指定,它被用作种子值。每个种子产生的随机数序列是不同的。

执行存储过程load_part_tab向test2.tab1表插入数据

mysql> delimiter ;           //注意有空格
mysql> call load_part_tab();

向test2.tab2表中插入数据

mysql> insert into test2.tab2 select * from test2.tab1;

测试SQL性能

mysql> select count(*) from test2.tab1 where c3 > '1995-01-01' and c3 < '1995-12-31';
+----------+
| count(*) |
+----------+
| 219642 |
+----------+
1 row in set (0.84 sec)
mysql> select count(*) from test2.tab2 where c3 > '1995-01-01' and c3 < '1995-12-31';
+----------+
| count(*) |
+----------+
| 219642 |
+----------+
1 row in set (0.09 sec)

结果表明分区表比未分区表的执行时间少很多。

通过explain语句来分析执行情况

mysql> flush tables;
mysql> explain select count(*) from test2.tab1 where c3 > '1995-01-01' and c3 < '1995-12-31'\G;
*************************** 1. row ***************************
  id: 1
  select_type: SIMPLE
  table: tab1
  partitions: NULL
  type: ALL
possible_keys: NULL
  key: NULL
  key_len: NULL
  ref: NULL
  rows: 2001552
  filtered: 11.11
  Extra: Using where
1 row in set, 1 warning (0.00 sec)
mysql> explain select count(*) from test2.tab2 where c3 > '1995-01-01' and c3 < '1995-12-31'\G;
*************************** 1. row ***************************
  id: 1
  select_type: SIMPLE
  table: tab2
  partitions: p1
  type: ALL
possible_keys: NULL
  key: NULL
  key_len: NULL
  ref: NULL
  rows: 220206
  filtered: 11.11
  Extra: Using where
1 row in set, 1 warning (0.00 sec)

explain语句显示了SQL查询要处理的记录数目可以看出分区表比未分区表的明显扫描的记录要少很多。

返回目录

创建索引后情况测试

mysql> create index idx_of_c3 on test2.tab1(c3);
Query OK, 0 rows affected (5.07 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> create index idx_of_c3 on test2.tab2(c3);
Query OK, 0 rows affected (4.87 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> flush tables;
mysql> select count(*) from test2.tab1 where c3 > '1996-01-01' and c3 < '1996-12-31';
+----------+
| count(*) |
+----------+
| 220264 |
+----------+
1 row in set (0.12 sec)

重启mysqld服务

mysql> select count(*) from test2.tab2 where c3 > '1996-01-01' and c3 < '1996-12-31';
+----------+
| count(*) |
+----------+
| 220264 |
+----------+
1 row in set (0.11 sec)

创建索引后分区表比未分区表相差不大(数据量越大差别会明显些)

返回目录


III. mysql分区的类型

1. RANGE分区

基于属于一个给定连续区间的列值,把多行分配给分区。这些区间要连续且不能相互重叠,使用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 (21)
);

按照这种分区方案,在商店1到5工作的雇员相对应的所有行被保存在分区P0中,商店6到10的雇员保存在P1中,依次类推。
注意,每个分区都是按顺序进行定义,从最低到最高。

对于包含数据(72, 'Michael', 'Widenius', '1998-06-25', NULL, 13)的一个新行,可以很容易地确定它将插入到p2分区中,但是如果增加了一个编号为第21的商店,将会发生什么呢?在这种方案下,由于没有规则把store_id大于20的商店包含在内,服务器将不知道把该行保存在何处,将会导致错误。 要避免这种错误,可以创建maxvalue分区,所有不在指定范围内的记录都会被存储到maxvalue所在的分区中。

mysql> alter table test2.user add partition (partition p4 values less than maxvalue);

返回目录

2. LIST分区

类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择。

LIST分区通过使用“PARTITION BY LIST(expr)”来实现,其中“expr” 是某列值或一个基于某个列值、并返回一个整数值的表达式,然后通过“VALUES IN (value_list)”的方式来定义每个分区,其中“value_list”是一个通过逗号分隔的整数列表。

要按照属于同一个地区商店的行保存在同一个分区中的方式来分割表,可以使用下面的“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;来进行删除,

它与具有同样作用的DELETE (删除)查询

DELETE query DELETE FROM employees WHERE store_id IN (4,12,13,14,18);

比起来,要有效得多。 要点:如果试图插入列值不在分区值列表中的一行时,那么“INSERT”查询将失败并报错。例如,假定LIST分区的采用上面的方案,下面的插入将失败:

INSERT INTO employees VALUES(224, 'Linus', 'Torvalds', '2002-05-01', '2004-10-12', 42, 21);


mysql> insert into shui values (224,'sdf','fafa',2,100);
ERROR 1526 (HY000): Table has no partition for value 100

这是因为“store_id”列值21不能在用于定义分区pNorth, pEast, pWest,或pCentral的值列表中找到。要重点注意的是,LIST分区没有类似如“VALUES LESS THAN MAXVALUE”这样的包含其他值在内的定义。将要匹配的任何值都必须在值列表中找到。

返回目录

3. HASH分区

这种模式允许DBA通过对表的一个或多个列的Hash Key进行计算,最后通过这个Hash码不同数值对应的数据区域进行分区。

hash分区的目的是将数据均匀的分布到预先定义的各个分区中,保证各分区的数据量大致一致。

mysql> create table t_hash( a int(11), b datetime) partition by hash(year(b)) partitions 4;

hash的分区函数页需要返回一个整数值。partitions子句中的值是一个非负整数,不加的partitions子句的话,默认为分区数为1。

mysql> insert into t_hash values(1,'2010-04-01');

该记录会被放入分区p2中。因为插入2010-04-01进入表t_hash,那么
MOD(YEAR('2010-04-01'),4)=2

mysql> select * from information_schema.partitions where table_schema='test2' and table_name='t_hash'\G;

*************************** 1. row ***************************
  TABLE_CATALOG: def
  TABLE_SCHEMA: test2
  TABLE_NAME: t_hash
  PARTITION_NAME: p0
  SUBPARTITION_NAME: NULL
  PARTITION_ORDINAL_POSITION: 1
SUBPARTITION_ORDINAL_POSITION: NULL
  PARTITION_METHOD: HASH
  SUBPARTITION_METHOD: NULL
  PARTITION_EXPRESSION: year(b)
  SUBPARTITION_EXPRESSION: NULL
  PARTITION_DESCRIPTION: NULL
  TABLE_ROWS: 0
  AVG_ROW_LENGTH: 0
  DATA_LENGTH: 16384
  MAX_DATA_LENGTH: NULL
  INDEX_LENGTH: 0
  DATA_FREE: 0
  CREATE_TIME: 2016-09-16 22:48:59
  UPDATE_TIME: 2016-09-17 23:36:22
  CHECK_TIME: NULL
  CHECKSUM: NULL
  PARTITION_COMMENT:
  NODEGROUP: default
  TABLESPACE_NAME: NULL
*************************** 2. row ***************************
  TABLE_CATALOG: def
  TABLE_SCHEMA: test2
  TABLE_NAME: t_hash
  PARTITION_NAME: p1
  SUBPARTITION_NAME: NULL
  PARTITION_ORDINAL_POSITION: 2
SUBPARTITION_ORDINAL_POSITION: NULL
  PARTITION_METHOD: HASH
  SUBPARTITION_METHOD: NULL
  PARTITION_EXPRESSION: year(b)
  SUBPARTITION_EXPRESSION: NULL
  PARTITION_DESCRIPTION: NULL
  TABLE_ROWS: 0
  AVG_ROW_LENGTH: 0
  DATA_LENGTH: 16384
  MAX_DATA_LENGTH: NULL
  INDEX_LENGTH: 0
  DATA_FREE: 0
  CREATE_TIME: 2016-09-16 22:48:59
  UPDATE_TIME: 2016-09-17 23:36:22
  CHECK_TIME: NULL
  CHECKSUM: NULL
  PARTITION_COMMENT:
  NODEGROUP: default
  TABLESPACE_NAME: NULL
*************************** 3. row ***************************
  TABLE_CATALOG: def
  TABLE_SCHEMA: test2
  TABLE_NAME: t_hash
  PARTITION_NAME: p2
  SUBPARTITION_NAME: NULL
  PARTITION_ORDINAL_POSITION: 3
SUBPARTITION_ORDINAL_POSITION: NULL
  PARTITION_METHOD: HASH
  SUBPARTITION_METHOD: NULL
  PARTITION_EXPRESSION: year(b)
  SUBPARTITION_EXPRESSION: NULL
  PARTITION_DESCRIPTION: NULL
  TABLE_ROWS: 1
  AVG_ROW_LENGTH: 16384
  DATA_LENGTH: 16384
  MAX_DATA_LENGTH: NULL
  INDEX_LENGTH: 0
  DATA_FREE: 0
  CREATE_TIME: 2016-09-16 22:48:59
  UPDATE_TIME: 2016-09-17 23:23:26
  CHECK_TIME: NULL
  CHECKSUM: NULL
  PARTITION_COMMENT:
  NODEGROUP: default
  TABLESPACE_NAME: NULL
*************************** 4. row ***************************
  TABLE_CATALOG: def
  TABLE_SCHEMA: test2
  TABLE_NAME: t_hash
  PARTITION_NAME: p3
  SUBPARTITION_NAME: NULL
  PARTITION_ORDINAL_POSITION: 4
SUBPARTITION_ORDINAL_POSITION: NULL
  PARTITION_METHOD: HASH
  SUBPARTITION_METHOD: NULL
  PARTITION_EXPRESSION: year(b)
  SUBPARTITION_EXPRESSION: NULL
  PARTITION_DESCRIPTION: NULL
  TABLE_ROWS: 0
  AVG_ROW_LENGTH: 0
  DATA_LENGTH: 16384
  MAX_DATA_LENGTH: NULL
  INDEX_LENGTH: 0
  DATA_FREE: 0
  CREATE_TIME: 2016-09-16 22:48:59
  UPDATE_TIME: 2016-09-17 23:23:26
  CHECK_TIME: NULL
  CHECKSUM: NULL
  PARTITION_COMMENT:
  NODEGROUP: default
  TABLESPACE_NAME: NULL
4 rows in set (0.00 sec)

可以看到P2分区有一条记录。当前这个例子并不能把数据均匀的分布到各个分区,因为按照YEAR函数进行的,该值本身是离散的。如果对连续的值进行HASH分区,如自增长的主键,则可以较好地将数据平均分布。

请思考:
mysql> insert into t_hash values(1,'2012-04-01');
记录会插入哪个分区?
取出2012的年份进行hash的函数运算,即:取模的计算,进行分区的划分。

返回目录

4. key分区

key分区和hash分区相似,不同在于hash分区是用户自定义函数进行分区,key分区使用mysql数据库提供的函数进行分区,NDB cluster使用MD5函数来分区,对于其他存储引擎mysql使用内部的hash函数。

mysql> create table t_key( a int(11), b datetime) partition by key(b) partitions 4;

上面的RANGE、LIST、HASH、KEY四种分区中,分区的条件必须是整形,如果不是整形需要通过函数将其转换为整形。

5. columns分区

mysql-5.5开始支持COLUMNS分区,可视为RANGE和LIST分区的进化,COLUMNS分区可以直接使用非整形数据进行分区。 COLUMNS分区支持以下数据类型:   所有整形,如INT SMALLINT TINYINT BIGINT。FLOAT和DECIMAL则不支持。
  日期类型,如DATE和DATETIME。其余日期类型不支持。
  字符串类型,如CHAR、VARCHAR、BINARY和VARBINARY。BLOB和TEXT类型不支持。
  COLUMNS可以使用多个列进行分区。

返回目录

mysql分表和分区有什么区别呢

  1. 实现方式上
  2. mysql的分表是真正的分表,一张表分成很多表后,每一个小表都是完整的一张表,都对应三个文件,一个.MYD数据文件,.MYI索引文件,.frm表结构文件。
  3. 分区不一样,一张大表进行分区后,他还是一张表,不会变成二张表,但是他存放数据的区块变多了
  4. 数据处理上
  5. a)分表后,数据都是存放在分表里,总表只是一个外壳,存取数据发生在一个一个的分表里面。
  6. b)分区呢,不存在分表的概念,分区只不过把存放数据的文件分成了许多小块,分区后的表呢,还是一张表,数据处理还是由自己来完成。
  7. 提高性能上
  8. a)分表后,单表的并发能力提高了,磁盘I/O性能也提高了。并发能力为什么提高了呢,因为查寻一次所花的时间变短了,如果出现高并发的话,总表可以根据不同的查询,将并发压力分到不同的小表里面。
  9. b)mysql提出了分区的概念,主要是想突破磁盘I/O瓶颈,想提高磁盘的读写能力,来增加mysql性能。 在这一点上,分区和分表的测重点不同,分表重点是存取数据时,如何提高mysql并发能力上;而分区呢,如何突破磁盘的读写能力,从而达到提高mysql性能的目的。
  10. 实现的难易度上
  11. a)分表的方法有很多,用merge来分表,是最简单的一种方式。这种方式跟分区难易度差不多,并且对程序代码来说可以做到透明的。如果是用其他分表方式就比分区麻烦了。
  12. b)分区实现是比较简单的,建立分区表,根建平常的表没什么区别,并且对开代码端来说是透明的。

返回目录

mysql分表和分区有什么联系?

  1. 都能提高mysql的性高,在高并发状态下都有一个良好的表现。
  2. 分表和分区不矛盾,可以相互配合的,对于那些大访问量,并且表数据比较多的表,我们可以采取分表和分区结合的方式,访问量不大,但是表数据很多的表,我们可以采取分区的方式等。
  3. 分表技术是比较麻烦的,需要手动去创建子表,app服务端读写时候需要计算子表名。采用merge好一些,但也要创建子表和配置子表间的union关系。
  4. 表分区相对于分表,操作方便,不需要创建子表。

返回目录

results matching ""

    No results matching ""