概论

Mysql 分区与分表
Mysql 分区表性能分析
Mysql 分区操作实战

  1. 分区的优势:海量数据时,把一张大表按照业务上的字段拆分成几个独立的分区,从而提升综合性能。这个特性由数据库自身维护,用户在操作数据时仍然像操作一张表,非常友好的一种数据库特性。
  2. 每个分区的锁也是独立的(表锁和行级锁)
  3. 分区键和索引键不是一个列的时候,基于普通索引列的查询也会走索引,相当于N个分区有N个普通索引,并行的执行索引
  4. 分区类型:水平分区(Range分区、List分区、Hash分区、Key分区) 和 垂直分区(把常用字段和不常用字段放在不同的分区里面)
  5. 要在WHERE后面带分区列,且不能是表达式,否则就是大表扫描所有分区
  6. 使用EXPLAIN PARTITIONS SELECT来判断是否进行了分区过滤

基本概念

为什么要分区?

  1. 数据量增多之后,索引遇到了瓶颈,即使构建了相关索引,查询和操作的速度仍然很慢。
  2. 没有分区时,都是通过分表的形式来实现一个业务大表,拆分为几个小表,来实现提速的效果,但是代码的复杂度就提升了,因为在代码层面上就是同时要操作几张表。
  3. 分区可以看作是MySQL底层帮我们实现分表,分区表是一个独立的逻辑表,底层由多个物理子表组成

比如按照年份拆分:例如查询 2018、2019年所有的name=“huang”的数据,分区的话,代码查询的是同一张表,只是查询条件不同了而已。分表的话,则需要查询2张不同的表

  1. //分表
  2. select * from user_2019 where name="huangzs";
  3. select * from user_2018 where name="huangzs";
  4. //分区
  5. select * from user where name="huangzs" and Year(registDate) in ('2019','2018');

分区表的特点:

  • 高性能:Mysql查询优化器会分析sql,根据查询条件选择对应的分区进行查询,而不是全表扫描;另外如果在分区字段上创建索引的话,那么每个独立的分区拥有独立的索引,性能显著提升
  • 易编程:对外编程来说,不同分区的子表,仍然是一张表,减少了编程的复杂度,具体的分区选择由 查询优化器决定
  • 分区的锁是独立的,分区表的表锁之间相互并不关联,例如把p1分区的表加独占锁,同时可以往p2分区插入数据,提高了并发性。如果分区键上再建立索引的话,可以实现行级锁,大大提升写的性能。

适用的业务场景:使用数据时有明显的区分,比如时间先后、批次

  • 很适合大量历史数据,少量活跃数据的场景
  • 适合有批次概念、有时间概念的查询业务场景

创建分区的技术条件:

  • 分区字段必须包含在主键里面,所以要么按照ID来做Range分区,要么用分区键和ID做联合主键
  • 分区键的运算结果必须为整数,除了使用YEAR, TO_DAY等日期函数外,还可以使用其数学函数,比如取模,按7取模是周几等

分区表的存储结构(InnoDB):1个pizza_order.frm文件、1个pizza_order.par文件、N个pizza_order#p#p1.ibd文件

分区的分类

  1. 水平分区
  • Range 分区:适合范围查找 (例如时间,例如ID,金额等等)
  • List 分区:适合 In查询,或者 精确查询(例如批次 01 、 02 批次)
  • HASH 分区:只适合精确查询,不适合范围查找(预先设定好几个块,按照hash算法填充)
  1. 垂直分区
    把不常用的大字段的字段单独分区出来,提高效率,又不影响数据的结构和完整性。

分区子句中可以使用各种函数,但表达式的返回值必须是一个确定的整数,且不能是一个常数。MySQL还支持一些其他分区,比如键值、哈希、列表分区,但在生产环境中很少见到。

  1. 创建Range分区
  1. 创建订单表,按照创建日期进行分区,这样按照日期查询的时候,就会根据区块查询,而不是所有的数据扫描了
  2. CREATE TABLE pizza_order(
  3. id bigint(20) NOT NULL AUTO_INCREMENT,
  4. created DATETIME NOT NULL COMMENT '创建时间',
  5. PRIMARY KEY (id, created)) ENGINE=InnoDB PARTITION BY RANGE(YEAR(created))(
  6. PARTITION p1 VALUES LESS THAN (2015),
  7. PARTITION p2 VALUES LESS THAN (2016),
  8. PARTITION p3 VALUES LESS THAN (2017),
  9. PARTITION p_latest VALUES LESS THAN MAXVALUE);
  10. INSERT INTO `test`.`pizza_order` (`id`, `created`) VALUES ('3', '2014-01-01 00:00:00');
  11. INSERT INTO `test`.`pizza_order` (`id`, `created`) VALUES ('4', '2015-07-01 00:00:00');
  12. INSERT INTO `test`.`pizza_order` (`id`, `created`) VALUES ('1', '2016-01-01 00:00:00');
  13. INSERT INTO `test`.`pizza_order` (`id`, `created`) VALUES ('2', '2016-07-01 00:00:00');
  14. INSERT INTO `test`.`pizza_order` (`id`, `created`) VALUES ('5', '2017-02-02 00:01:01');
  15. EXPLAIN select * from pizza_order where CREATED='2014-01-01 00:00:00';
  16. EXPLAIN select * from pizza_order where CREATED>'2016-02-02 00:01:01';
  1. List分区,这种如果插入语句不在IN中,则会插入失败
  1. PARTITION BY LIST(store_id)
  2. PARTITION pNorth VALUES IN (3,5,6,9,17),
  3. PARTITION pEast VALUES IN (1,2,10,11,19,20),
  4. PARTITION pWest VALUES IN (4,12,13,14,18),
  5. PARTITION pCentral VALUES IN (7,8,15,16)
  6. );
  1. Hash分区:PARTITIONS为分区的数量, 即会根据分区键的值计算出一个hash值,然后以4为模进行存储,好处是,不用再重新建分区了。
    比如对ID进行Hash分区,非常适合根据ID进行的精确查找,而且分布会很均匀
  1. PARTITION BY HASH(store_id)
  2. PARTITIONS 4;
  1. Key分区:类似于Hash分区,但是Hash分区的值必须是整数,和比如store_id,比如 year(date),但是Key分区支持除了 Bol、text字段的其它所有字段类型,自己有函数做Hash散列

分区的使用

  • 使用分区的前提是:查询都能够过滤掉很多额外的分区、分区本身并不会带来很多额外的代价
  • 在WHERE条件中带入分区列,如果没有这些条件,MySQL会扫描所有分区

查看分区情况

  • 来查看分区查询走了哪几个块区。
    EXPLAIN PARTITIONS select * from pizza_order where CREATED>’2016-02-02 00:01:01’;
  • 查看该表的所有分区块的存储情况
  1. SELECT PARTITION_NAME,PARTITION_METHOD,PARTITION_EXPRESSION,PARTITION_DESCRIPTION,TABLE_ROWS,SUBPARTITION_NAME,SUBPARTITION_METHOD,SUBPARTITION_EXPRESSION
  2. FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA=SCHEMA() AND TABLE_NAME='pizza_order';

注意事项

  1. 要在WHERE后面带分区列,且不能是表达式,否则就是大表扫描所有分区
  1. //全表扫描
  2. EXPLAIN PARTITIONS SELECT * FROM `pizza_order` where id in (1,3);
  3. //只扫描 p-2016 和p-latest 2个分区
  4. EXPLAIN PARTITIONS select * from pizza_order where CREATED>'2016-02-02 00:01:01';
  1. 使用EXPLAIN PARTITIONS SELECT来判断是否进行了分区过滤
  2. 分区键的运算结果是Null的会自动分配到第一个分区
  3. 对分区表的分区键创建索引,那么这个索引也将被分区,分区键没有全局索引一说(当然也可以不对分区字段添加索引)
  4. 5.7版本开始,只有InnoDB和NDB索引引擎支持分区(8.0版本也是如此),Myisam 5.6还是支持的,5.7.17就不支持了

分区的限制

  • 分区最大数:8192
  • Innodb分区表不支持外键
  • 不支持全文搜索