为何要分表和分区?html
平常开发中咱们常常会遇到大表的状况,所谓的大表是指存储了百万级乃至千万级条记录的表。这样的表过于庞大,致使数据库在查询和插入的时候耗时太长,性能低下,若是涉及联合查询的状况,性能会更加糟糕。分表和表分区的目的就是减小数据库的负担,提升数据库的效率,一般点来说就是提升表的增删改查效率。node
什么是分表?mysql
分表是将一个大表按照必定的规则分解成多张具备独立存储空间的实体表,咱们能够称为子表,每一个表都对应三个文件,MYD数据文件,.MYI索引文件,.frm表结构文件。这些子表能够分布在同一块磁盘上,也能够在不一样的机器上。app读写的时候根据事先定义好的规则获得对应的子表名,而后去操做它。算法
什么是分区?sql
分区和分表类似,都是按照规则分解表。不一样在于分表将大表分解为若干个独立的实体表,而分区是将数据分段划分在多个位置存放,能够是同一块磁盘也能够在不一样的机器。分区后,表面上仍是一张表,但数据散列到多个位置了。app读写的时候操做的仍是大表名字,db自动去组织分区的数据。数据库
mysql分表和分区有什么联系呢?
1.都能提升mysql的性高,在高并发状态下都有一个良好的表现。
2.分表和分区不矛盾,能够相互配合的,对于那些大访问量,而且表数据比较多的表,咱们能够采起分表和分区结合的方式(若是merge这种分表方式,不能和分区配合的话,能够用其余的分表试),访问量不大,可是表数据不少的表,咱们能够采起分区的方式等。
3.分表技术是比较麻烦的,须要手动去建立子表,app服务端读写时候须要计算子表名。采用merge好一些,但也要建立子表和配置子表间的union关系。
4.表分区相对于分表,操做方便,不须要建立子表。express
今天统计数据的时候发现一张表使用了表分区,借此机会记录一下。bash
表分区,是指根据必定规则,将数据库中的一张表分解成多个更小的,容易管理的部分。从逻辑上看,只有一张表,可是底层倒是由多个物理分区组成。并发
分表:指的是经过必定规则,将一张表分解成多张不一样的表。好比将用户订单记录根据时间成多个表。 分表与分区的区别在于:分区从逻辑上来说只有一张表,而分表则是将一张表分解成多张表。app
1)分区表的数据能够分布在不一样的物理设备上,从而高效地利用多个硬件设备。 2)和单个磁盘或者文件系统相比,能够存储更多数据 3)优化查询。在where语句中包含分区条件时,能够只扫描一个或多个分区表来提升查询效率;涉及sum和count语句时,也能够在多个分区上并行处理,最后汇总结果。 4)分区表更容易维护。例如:想批量删除大量数据能够清除整个分区。 5)可使用分区表来避免某些特殊的瓶颈,例如InnoDB的单个索引的互斥访问,ext3问价你系统的inode锁竞争等。
1)一个表最多只能有1024个分区 2) MySQL5.1中,分区表达式必须是整数,或者返回整数的表达式。在MySQL5.5中提供了非整数表达式分区的支持。 3)若是分区字段中有主键或者惟一索引的列,那么多有主键列和惟一索引列都必须包含进来。即:分区字段要么不包含主键或者索引列,要么包含所有主键和索引列。 4)分区表中没法使用外键约束 5)MySQL的分区适用于一个表的全部数据和索引,不能只对表数据分区而不对索引分区,也不能只对索引分区而不对表分区,也不能只对表的一部分数据分区。
命令:show variables like '%partition%' 运行结果:
mysql> show variables like '%partition%'; +-------------------+-------+ | Variable_name | Value | +-------------------+-------+ | have_partitioning | YES | +-------------------+-------+ 1 row in set (0.00 sec)
have_partintioning 的值为YES,表示支持分区。
1)RANGE分区:按照数据的区间范围分区
2)LIST分区:按照List中的值分区,与RANGE的区别是,range分区的区间范围值是连续的。
3)HASH分区
4)KEY分区
说明 在MySQL5.1版本中,RANGE,LIST,HASH分区要求分区键必须是INT类型,或者经过表达式返回INT类型。但KEY分区的时候,可使用其余类型的列(BLOB,TEXT类型除外)做为分区键。
利用取值范围进行分区,区间要连续而且不能互相重叠。 语法:
partition by range(exp)( //exp能够为列名或者表达式,好比to_date(created_date) partition p0 values less than(num) )
例如:
mysql> create table emp( -> id INT NOT null, -> store_id int not null -> ) -> partition by range(store_id)( -> partition p0 values less than(10), -> partition p1 values less than(20) -> );
上面的语句建立了emp表,并根据store_id字段进行分区,小于10的值存在分区p0中,大于等于10,小于20的值存在分区p1中。 注意 每一个分区都是按顺序定义的,从最低到最高。上面的语句,若是将less than(10) 和less than (20)的顺序颠倒过来,那么将报错,以下:
ERROR 1493 (HY000): VALUES LESS THAN value must be strictly increasing for each partition
RANGE分区存在的问题
mysql> insert into emp(id,store_id) values(2,30); ERROR 1526 (HY000): Table has no partition for value 30提示30这个值没有对应的分区。 解决办法 A. 预估分区键的值,及时新增分区。 B. 设置分区的时候,使用values less than maxvalue 子句,MAXVALUE表示最大的可能的整数值。 C. 尽可能选择可以所有覆盖的字段做为分区键,好比一年的十二个月等。
List分区是创建离散的值列表告诉数据库特定的值属于哪一个分区。 语法:
partition by list(exp)( //exp为列名或者表达式 partition p0 values in (3,5) //值为3和5的在p0分区 )
与Range不一样的是,list分区没必要生命任何特定的顺序。例如:
mysql> create table emp1( -> id int not null, -> store_id int not null -> ) -> partition by list(store_id)( -> partition p0 values in (3,5), -> partition p1 values in (2,6,7,9) -> );
注意 若是插入的记录对应的分区键的值不在list分区指定的值中,将会插入失败。而且,list不能像range分区那样提供maxvalue。
MySQL5.5中引入的分区类型,解决了5.5版本以前range分区和list分区只支持整数分区的问题。 Columns分区能够细分为 range columns分区和 list columns分区,他们都支持整数,日期时间,字符串三大数据类型。(不支持text和blob类型做为分区键) columns分区还支持多列分区(这里不详细展开)。
Hash分区主要用来分散热点读,确保数据在预先肯定个数的分区中尽量平均分布。 MySQL支持两种Hash分区:常规Hash分区和线性Hash分区。 A. 常规Hash分区:使用取模算法 语法:
partition by hash(store_id) partitions 4;
上面的语句,根据store_id对4取模,决定记录存储位置。 好比store_id = 234的记录,MOD(234,4)=2,因此会被存储在第二个分区。
常规Hash分区的优势和不足 优势:可以使数据尽量的均匀分布。 缺点:不适合分区常常变更的需求。假如我要新增长两个分区,如今有6个分区,那么MOD(234,6)的结果与以前MOD(234,4)的结果就会出现不一致,这样大部分数据就须要从新计算分区。为解决此问题,MySQL提供了线性Hash分区。
B. 线性Hash分区:分区函数是一个线性的2的幂的运算法则。 语法:
partition by LINER hash(store_id) partitions 4;
与常规Hash的不一样在于,“Liner”关键字。 算法介绍: 假设要保存记录的分区编号为N,num为一个非负整数,表示分割成的分区的数量,那么N能够经过如下步骤获得:
Step 1. 找到一个大于等于num的2的幂,这个值为V,V能够经过下面公式获得:
V = Power(2,Ceiling(Log(2,num)))
例如:刚才设置了4个分区,num=4,Log(2,4)=2,Ceiling(2)=2,power(2,2)=4,即V=4
Step 2. 设置N=F(column_list)&(V-1)
例如:刚才V=4,store_id=234对应的N值,N = 234&(4-1) =2
Step 3. 当N>=num,设置V=Ceiling(V/2),N=N&(V-1)
例如:store_id=234,N=2<4,因此N就取值2,便可。
假设上面算出来的N=5,那么V=Ceiling(4/2)=2,N=5&(2-1)=1,即在第一个分区。
线性Hash的优势和不足 优势:在分区维护(增长,删除,合并,拆分分区)时,MySQL可以处理得更加迅速。 缺点:与常规Hash分区相比,线性Hash各个分区之间的数据分布不太均衡。
相似Hash分区,Hash分区容许使用用户自定义的表达式,但Key分区不容许使用用户自定义的表达式。Hash仅支持整数分区,而Key分区支持除了Blob和text的其余类型的列做为分区键。 语法:
partition by key(exp) partitions 4;//exp是零个或多个字段名的列表
key分区的时候,exp能够为空,若是为空,则默认使用主键做为分区键,没有主键的时候,会选择非空唯一键做为分区键。
分区表中对每一个分区再次分割,又成为复合分区。
MySQ容许分区键值为NULL,分区键多是一个字段或者一个用户定义的表达式。通常状况下,MySQL在分区的时候会把NULL值看成零值或者一个最小值进行处理。
注意
Range分区中:NULL值被看成最小值来处理
List分区中:NULL值必须出如今列表中,不然不被接受
Hash/Key分区中:NULL值会被看成零值来处理
分区管理包括对于分区的增长,删除,以及查询。
alter table table_name add partition (partition p0 values ...(exp))values后面的内容根据分区的类型不一样而不一样。
alter table table_name add partition partitions 8;
上面的语句,指的是新增8个分区 。alter table table_name drop partition p0; //p0为要删除的分区名称删除了分区,同时也将删除该分区中的全部数据。同时,若是删除了分区致使分区不能覆盖全部值,那么插入数据的时候会报错。
alter table table_name coalesce partition 2; //将分区缩减到2个
coalesce [ˌkəʊəˈles] vi. 联合,合并mysql> select -> partition_name, -> partition_expression, -> partition_description, -> table_rows -> from -> INFORMATION_SCHEMA.partitions -> where -> table_schema='test' -> and table_name = 'emp'; +----------------+----------------------+-----------------------+------------+ | partition_name | partition_expression | partition_description | table_rows | +----------------+----------------------+-----------------------+------------+ | p0 | store_id | 10 | 0 | | p1 | store_id | 20 | 1 | +----------------+----------------------+-----------------------+------------+即,能够从information_schema.partitions表中查询。
mysql> explain partitions select * from emp where store_id=10 \G; *************************** 1. row *************************** id: 1 select_type: SIMPLE table: emp partitions: p1 type: system possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 1 Extra: 1 row in set (0.00 sec)上面的结果:partitions:p1 表示数据在p1分区进行检索。
[参考资料] 《深刻MySQL数据库开发、优化与管理维护(第2版)》
《高性能MySQL》
============================================
分表的几种方式:
一、mysql集群
它并非分表,但起到了和分表相同的做用。集群可分担数据库的操做次数,将任务分担到多台数据库上。集群能够读写分离,减小读写压力。从而提高数据库性能。
二、自定义规则分表
大表能够按照业务的规则来分解为多个子表。一般为如下几种类型,也可本身定义规则。
Range(范围)–这种模式容许将数据划分不一样范围。例如能够将一个表经过年份划分红若干个分区。 Hash(哈希)–这中模式容许经过对表的一个或多个列的Hash Key进行计算,最后经过这个Hash码不一样数值对应的数据区域进行分区。例如能够创建一个对表主键进行分区的表。 Key(键值)-上面Hash模式的一种延伸,这里的Hash Key是MySQL系统产生的。 List(预约义列表)–这种模式容许系统经过预约义的列表的值来对数据进行分割。 Composite(复合模式) –以上模式的组合使用
分表规则与分区规则同样,在分区模块详细介绍。
下面以Range简单介绍下如何分表(按照年份表)。
假设表结构有4个字段:自增id,姓名,存款金额,存款日期
把存款日期做为规则分表,分别建立几个表
2011年:account_2011
2012年:account_2012
……
2015年:account_2015
app在读写的时候根据日期来查找对应的表名,须要手动来断定。
var getTableName = function() { var data = { name: 'tom', money: 2800.00, date: '201410013059' }; var tablename = 'account_'; var year = parseInt(data.date.substring(0, 4)); if (year < 2012) { tablename += 2011; // account_2011 } else if (year < 2013) { tablename += 2012; // account_2012 } else if (year < 2014) { tablename += 2013; // account_2013 } else if (year < 2015) { tablename += 2014; // account_2014 } else { tablename += 2015; // account_2015 } return tablename; }
merge分表,分为主表和子表,主表相似于一个壳子,逻辑上封装了子表,实际上数据都是存储在子表中的。三、利用merge存储引擎来实现分表
咱们能够经过主表插入和查询数据,若是清楚分表规律,也能够直接操做子表。
子表2011年
CREATE TABLE `account_2011` ( `id` int(11) NOT NULL AUTO_INCREMENT , `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL , `money` float NOT NULL , `tradeDate` datetime NOT NULL PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci AUTO_INCREMENT=2 CHECKSUM=0 ROW_FORMAT=DYNAMIC DELAY_KEY_WRITE=0;
子表2012年
CREATE TABLE `account_2012` ( `id` int(11) NOT NULL AUTO_INCREMENT , `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL , `money` float NOT NULL , `tradeDate` datetime NOT NULL PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci AUTO_INCREMENT=2 CHECKSUM=0 ROW_FORMAT=DYNAMIC DELAY_KEY_WRITE=0;
主表,全部年
CREATE TABLE `account_all` ( `id` int(11) NOT NULL AUTO_INCREMENT , `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL , `money` float NOT NULL , `tradeDate` datetime NOT NULL PRIMARY KEY (`id`) ) ENGINE=MRG_MYISAM DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci UNION=(`account_2011`,`account_2012`) INSERT_METHOD=LAST ROW_FORMAT=DYNAMIC;
建立主表的时候有个INSERT_METHOD,指明插入方式,取值能够是:0 不容许插入;FIRST 插入到UNION中的第一个表; LAST 插入到UNION中的最后一个表。
经过主表查询的时候,至关于将全部子表合在一块儿查询。这样并不能体现分表的优点,建议仍是查询子表。
分区的几种方式
Range:
create table range( id int(11), money int(11) unsigned not null, date datetime )partition by range(year(date))( partition p2007 values less than (2008), partition p2008 values less than (2009), partition p2009 values less than (2010) partition p2010 values less than maxvalue );
List:
create table list( a int(11), b int(11) )(partition by list (b) partition p0 values in (1,3,5,7,9), partition p1 values in (2,4,6,8,0) );
Hash:
create table hash( a int(11), b datetime )partition by hash (YEAR(b) partitions 4;
Key:
create table t_key( a int(11), b datetime) partition by key (b) partitions 4;
分区管理
新增分区
ALTER TABLE sale_data ADD PARTITION (PARTITION p201010 VALUES LESS THAN (201011));
删除分区
--当删除了一个分区,也同时删除了该分区中全部的数据。
ALTER TABLE sale_data DROP PARTITION p201010;
分区的合并
下面的SQL,将p201001 - p201009 合并为3个分区p2010Q1 - p2010Q3
ALTER TABLE sale_data REORGANIZE PARTITION p201001,p201002,p201003, p201004,p201005,p201006, p201007,p201008,p201009 INTO ( PARTITION p2010Q1 VALUES LESS THAN (201004), PARTITION p2010Q2 VALUES LESS THAN (201007), PARTITION p2010Q3 VALUES LESS THAN (201010) );