mysql数据库中的数据是以文件的形势存在磁盘上的,默认放在/mysql/data下面(可以通过my.cnf中的datadir来查看),
一张表主要对应着三个文件,一个是frm存放表结构的,一个是myd存放表数据的,一个是myi存表索引的。
[root@laoyang test]# ls -al
总用量 1811444
drwx------ 2 mysql mysql 4096 10月 17 15:12 .
drwxr-xr-x 4 mysql mysql 4096 10月 17 14:37 ..
-rw-rw---- 1 mysql mysql 8962 10月 10 17:45 bz_info.frm
-rw-rw---- 1 mysql mysql 347727032 10月 17 15:16 bz_info.MYD
-rw-rw---- 1 mysql mysql 56341504 10月 17 15:16 bz_info.MYI
-rw-rw---- 1 mysql mysql 8962 10月 10 17:44 dz_info.frm
-rw-rw---- 1 mysql mysql 418645764 10月 17 15:15 dz_info.MYD
-rw-rw---- 1 mysql mysql 81381376 10月 17 15:15 dz_info.MYI
1
2
3
4
5
6
7
8
9
10
2)数据库分区处理
如果一张表的数据量太大的话,那么myd,myi就会变的很大,查找数据就会变的很慢,这个时候我们可以利用mysql的分区功能,在物理上将这一张表对应的三个文件,分割成许多个小块,这样呢,我们查找一条数据时,就不用全部查找了,只要知道这条数据在哪一块,然后在那一块找就行了。如果表的数据太大,可能一个磁盘放不下,这个时候,我们可以把数据分配到不同的磁盘里面去。
表分区是Mysql被Oracle收购后推出的一个新特性。
一、表分区通俗解释
通俗地讲表分区是将一大表,根据条件分割成若干个小表。mysql5.1开始支持数据表分区了。
如:某用户表的记录超过了600万条,那么就可以根据入库日期将表分区,也可以根据所在地将表分区。当然也可根据其他的条件分区。
二、为什么要对表进行分区?
为了改善大型表以及具有各种访问模式的表的可伸缩性,可管理性和提高数据库效率。
2.1 表分区要解决的问题:
当表非常大,或者表中有大量的历史记录,而“热数据”却位于表的末尾。如日志系统、新闻。。此时就可以考虑分区表。【注:此处也可以使用分表,但是会增加业务的复杂性。】
2.2 表分区有如下优点:
1)与单个磁盘或文件系统分区相比,可以存储更多的数据。
2)对于那些已经失去保存意义的数据,通常可以通过删除与那些数据有关的分区,很容易地删除那些数据。
相反地,在某些情况下,添加新数据的过程又可以通过为那些新数据专门增加一个新的分区,来很方便地实现。
同样的,你可以很快的通过删除分区来移除旧数据。你还可以优化、检查、修复个别分区。
3)一些查询可以得到极大的优化。 可以把一些归类的数据放在一个分区中,可以减少服务器检查数据的数量加快查询。
这主要是借助于满足一个给定WHERE语句的数据可以只保存在一个或多个分区内,这样在查找时就不用查找其他剩余的分区。
PS:因为分区可以在创建了分区表后进行修改,所以在第一次配置分区方案时还不曾这么做时,可以重新组织数据,来提高那些常用查询的效率。
4)涉及到例如SUM()和COUNT()这样聚合函数的查询,可以很容易地进行并行处理。
这种查询的一个简单例子如
“SELECT salesperson_id, COUNT (orders) as order_total FROM sales GROUP BY salesperson_id;”。
通过“并行”,这意味着该查询可以在每个分区上同时进行,最终结果只需通过总计所有分区得到的结果。
5)通过跨多个磁盘来分散数据查询,来获得更大的查询吞吐量。
三、mysql分区类型
根据所使用的不同分区规则可以分成几大分区类型。
3.1 RANGE 分区:
基于属于一个给定连续区间的列值,把多行分配给分区。
举例:
create table foo_range (
id int not null auto_increment,
created DATETIME,
primary key (id, created)
) engine = innodb partition by range (TO_DAYS(created))(
PARTITION foo_1 VALUES LESS THAN (TO_DAYS('2016-10-18')),
PARTITION foo_2 VALUES LESS THAN (TO_DAYS('2017-01-01'))
);
//新增一个分区
ALTER TABLE foo_range ADD PARTITION(
PARTITION foo_3 VALUES LESS THAN (TO_DAYS('2017-10-18'))
);
//插入数据
insert into `foo_range` (`id`, `created`) values (1, '2016-10-17'),(2, '2016-10-20'),(3, '2016-1-25');
//查询
explain partitions select * from foo_range where created = '2016-10-20';
//查询结果:
mysql> explain partitions select * from foo_range where created = '2016-10-20';
+----+-------------+-----------+------------+-------+---------------+---------+---------+------+------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------+------------+-------+---------------+---------+---------+------+------+--------------------------+
| 1 | SIMPLE | foo_range | foo_2 | index | NULL | PRIMARY | 12 | NULL | 2 | Using where; Using index |
+----+-------------+-----------+------------+-------+---------------+---------+---------+------+------+--------------------------+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
3.2 LIST 分区:
类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择。
create table foo_list
(empno varchar(20) not null ,
empname varchar(20),
deptno int,
birthdate date not null,
salary int
)
partition by list(deptno)
(
partition p1 values in (10),
partition p2 values in (20),
partition p3 values in (30)
);
1
2
3
4
5
6
7
8
9
10
11
12
13
以上显示,以部门号为分区依据,每个部门一个分区。
3.3 HASH分区:
基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL中有效的、产生非负整数值的任何表达式。
HASH分区主要用来确保数据在预先确定数目的分区中平均分布。在RANGE和LIST分区中,必须明确指定一个给定的列值或列值集合应该保存在哪个分区中。
在HASH分区中,MySQL 自动完成这些工作,你所要做的只是基于将要被哈希的列值指定一个列值或表达式,以及指定被分区的表将要被分割成的分区数量。
create table foo_hash
(empno varchar(20) not null ,
empname varchar(20),
deptno int,
birthdate date not null,
salary int
)
partition by hash(year(birthdate))
partitions 4;
1
2
3
4
5
6
7
8
9
以上创建了4个分区。
3.4 KEY分区:
类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值。
create table foo_key
(empno varchar(20) not null ,
empname varchar(20),
deptno int,
birthdate date not null,
salary int
)
partition by key(birthdate)
partitions 4;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
3.5 复合分区:
基于RANGE/LIST 类型的分区表中每个分区的再次分割。子分区可以是 HASH/KEY 等类型。
四、常见分区操作
修改已有表举例:
ALTER TABLE bj_info
PARTITION BY RANGE(id) PARTITIONS 14(
PARTITION part_00yntai VALUES LESS THAN (610001),
PARTITION part_01shxia VALUES LESS THAN (1220001),
PARTITION part_02zhfu VALUES LESS THAN (1830001),
PARTITION part_03fuhan VALUES LESS THAN (2440001),
PARTITION part_04mping VALUES LESS THAN (3660001),
PARTITION part_06chngdao VALUES LESS THAN (4270001),
PARTITION part_07lonkou VALUES LESS THAN (4880001),
PARTITION part_08layang VALUES LESS THAN (5490001),
PARTITION part_09laihou VALUES LESS THAN (6100001),
PARTITION part_10peglai VALUES LESS THAN (6710001),
PARTITION part_11zhoyuan VALUES LESS THAN (7320001),
PARTITION part_12qixa VALUES LESS THAN (7930001),
PARTITION part_13haiyng VALUES LESS THAN (8540000),
PARTITION part_05laisan VALUES LESS THAN MAXVALUE
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
五、获取分区表信息的方法
5.1 show create table 表名
可以查看创建分区表的create语句
举例:
mysql> show create table foo_list;
+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| foo_list | CREATE TABLE `foo_list` (
`empno` varchar(20) NOT NULL,
`empname` varchar(20) DEFAULT NULL,
`deptno` int(11) DEFAULT NULL,
`birthdate` date NOT NULL,
`salary` int(11) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8
/*!50100 PARTITION BY LIST (deptno)
(PARTITION p1 VALUES IN (10) ENGINE = MyISAM,
PARTITION p2 VALUES IN (20) ENGINE = MyISAM,
PARTITION p3 VALUES IN (30) ENGINE = MyISAM) */ |
+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
5. 2 show table status
可以查看表是不是分区表
举例:
SHOW TABLE STATUS LIKE ‘foo_range’;
结果如红色部分所示:
5.3 查看information_schema.partitions表
如下命令可以查看表具有哪几个分区、分区的方法、分区中数据的记录数等信息
mysql> select
-> partition_name part,
-> partition_expression expr,
-> partition_description descr,
-> table_rows
-> from information_schema.partitions where
-> table_schema = schema()
-> and table_name='foo_range';
+-------+------------------+--------+------------+
| part | expr | descr | table_rows |
+-------+------------------+--------+------------+
| foo_1 | TO_DAYS(created) | 736620 | 2 |
| foo_2 | TO_DAYS(created) | 736695 | 1 |
| foo_3 | TO_DAYS(created) | 736985 | 0 |
+-------+------------------+--------+------------+
3 rows in set (0.00 sec)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
5.4 explain partitions select语句
通过此语句来显示扫描哪些分区,及他们是如何使用的.
举例如下:
mysql> explain partitions select * from foo_range;
+----+-------------+-----------+-------------------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------+-------------------+-------+---------------+---------+---------+------+------+-------------+
| 1 | SIMPLE | foo_range | foo_1,foo_2,foo_3 | index | NULL | PRIMARY | 12 | NULL | 4 | Using index |
+----+-------------+-----------+-------------------+-------+---------------+---------+---------+------+------+-------------+
1 row in set (0.00 sec)
1
2
3
4
5
6
7
六、性能对比(分区表和非分区表)
步骤一:创建两张表: part_tab(分区表),no_part_tab(普通表)
CREATE TABLE part_tab
(c1 int default NULL, c2 varchar(30) default NULL, c3 date not null)
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) );
CREATE TABLE no_part_tab(c1 int default NULL, c2 varchar(30) default NULL, c3 date not null);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
步骤二:创建存储过程。
CREATE PROCEDURE load_part_tab()
begin
declare v int default 0;
while v < 8000000
do
insert into part_tab
values (v,'testingpartitions',adddate('1995-01-01',(rand(v)*36520)mod 3652));
set v = v + 1;
end while;
end;
//调用存储过程,插入数据
call load_part_tab();
//从 part_tab 导入数据到 no_part_tab
insert into no_part_tab select * from part_tab;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
步骤三:执行查询速度比对
select count(*) from part_tab where c3 > date '1995-01-01' and c3 < date '1995-12-31';
1
耗时:0.407s
select count(*) from no_part_tab where c3 > date '1995-01-01'and c3 < date '1995-12-31';
1
耗时:3.716s:3.716/0.407=9.13倍。
扫描次数对比:
mysql> explain select count(*) from part_tab where c3 > date '1995-01-01'and c3 < date '1995-12-31';
+----+-------------+----------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+------+---------------+------+---------+------+--------+-------------+
| 1 | SIMPLE | part_tab | ALL | NULL | NULL | NULL | NULL | 798458 | Using where |
+----+-------------+----------+------+---------------+------+---------+------+--------+-------------+
1 row in set (0.00 sec)
mysql> explain select count(*) from no_part_tab where c3 > date '1995-01-01'and c3 < date '1995-12-31';
+----+-------------+-------------+------+---------------+------+---------+------+---------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------------+------+---------------+------+---------+------+---------+-------------+
| 1 | SIMPLE | no_part_tab | ALL | NULL | NULL | NULL | NULL | 8000000 | Using where |
+----+-------------+-------------+------+---------------+------+---------+------+---------+-------------+
1 row in set (0.00 sec)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
如上:普通表扫描了 8000000次, 分区表扫描了798458次。
分区表扫描比例是普通表的:798458/ 8000000 = 9.98%。
七、分区适用场景
7.1常见使用场景
1)当数据量很大(过T)时,肯定不能把数据再如到内存中,这样查询一个或一定范围的item是很耗时。另外一般这情况下,历史数据或不常访问的数据占很大部分,最新或热点数据占的比例不是很大。这时可以根据有些条件进行表分区。
2)分区表的更易管理,比如删除过去某一时间的历史数据,直接执行truncate,或者狠点drop整个分区,这比detele删除效率更高
3)当数据量很大,或者将来很大的,但单块磁盘的容量不够,或者想提升IO效率的时候,可以把没分区中的子分区挂载到不同的磁盘上。
4)使用分区表可避免某些特殊的瓶颈,例如Innodb的单个索引的互斥访问..
5)单个分区表的备份很恢复会更有效率,在某些场景下
总结:可伸缩性,可管理性,提高数据库查询效率。
7.2 业务场景举例
项目中需要动态新建、删除分区。如新闻表,按照时间维度中的月份对其分区,为了防止新闻表过大,只保留最近6个月的分区,同时预建后面3个月的分区,这个删除、预建分区的过程就是分区表的动态管理。
参考:
http://blog.51yip.com/mysql/1029.html
http://blog.51yip.com/mysql/949.html
http://blog.51yip.com/mysql/1013.html
http://blog.csdn.net/feihong247/article/details/7885199
http://www.wiquan.com/article/669
分区坑:
http://www.simlinux.com/archives/133.html
创建路径:
http://dev.mysql.com/doc/refman/5.7/en/create-table.html
5.6版本才支持:
http://www.linuxidc.com/Linux/2014-01/95725.htm
https://dev.mysql.com/doc/refman/5.6/en/tablespace-placing.html