Mysql分区表 介绍和使用

时间:2021-06-19 21:47:09

试想这样一个场景:

你有一张表,存储引擎为InnoDB,里面存储的数据量达到了上亿级别。

此时,因为数据量巨大,肯定不能在每次查询的时候都扫描全表。
就算是使用索引(B-Tree),除非使用索引覆盖查询,否则数据库服务器需要根据查询的结果回表,查询所有符合条件的数据,
如果数据量巨大,会产生大量的随机IO,最终使得应用程序僵死。另外,这种数据量下,索引维护的代价也非常高。

分区表适用的场景?

分区表适用于数据量非常大,并且拥有某个特定字段可以据此将数据划分成几块的场景。

例如用户购买的商品记录表,可以依据购买时间,将全量记录划分为多个子分区。

那么有同学会问,为什么不直接用物理分表呢?

例如,有一张存储了3亿条商品数据的表goods,出于性能考虑,我们可能会将表拆分成300个子表,每张表存储100W条数据,此时,我们有了goods_0、goods_1….goods_299。

但这样做的问题是:开发者需要自行按照特殊条件,对于自身要操作的表做判断,然后自行改写sql去操作指定的物理子表,
这样的问题在于,将开发逻辑变得复杂化,并且代码变得”丑”了。
如果你使用了某些ORM框架,那么就更烦人了,你需要改写model定位自身table的逻辑。


所以此时我们可以考虑使用分区表

What?(分区表是什么)

分区表可以用一张表存储大量数据,达到和物理分表同样的效果,但操作起来更简单,对于使用者来说和普通表无差别

How?(怎么使用它)

Mysql在创建表时使用PARTITION BY字句定义每个分区,例子如下:
CREATE TABLE goods (
create_date DATETIME NOT NULL,
........
) ENGINE=InnoDB PARTITION BY RANGE(YEAR(create_date))(
PARTITION p_2014 VALUES LESS THAN (2015) ENGINE=InnoDB,
PARTITION p_2015 VALUES LESS THAN (2016) ENGINE=InnoDB,
PARTITION p_2016 VALUES LESS THAN (2017) ENGINE=InnoDB,
PARTITION p_others values LESS THAN MAXVALUE ENGINE=InnoDB);

上面的建表语句中,我们创建了一个商品表goods,其中定义了创建时间(create_date)字段,
我们使用范围分区方式建立分区表,然后我们使用该字段的年份作为分区条件,
分别将时间在2015年之前的数据存放在了p_2014分区,
将时间在2016年之前(也就是2015年整年的数据)存放在了p_2015分区。
将时间在2017年之前(也就是2016年整年的数据)存放在了p_2016分区。
然后将2017年以及之后的数据都放在了最后一个分区p_others。

更多使用方式

分区表不仅可以根据字段范围分区,也支持通过键值、哈希和列表分区,不过我们最常用的就是根据范围进行分区。
可以使用数学模函数进行分区,也可以根据时间范围进行分区,
甚至我们可以自行定义一个分区列,将想要落在相同分区的数据的该列都设为相同值。

分区表的操作逻辑

SELECT:
读锁不会影响同时发生的其他读操作,不必担心。
INSERT:
分区层先打开并锁住所有分区表,确定由哪个分区接收这条记录,再释放全表锁并锁住对应分区,将记录写入对应底层表
DELETE:
类似于INSERT
UPDATE:
分区层先打开并锁住所有底层表,然后确定要更新的数据在哪个分区,取出该数据并更新,再判断更新后的数据应该
存储到哪个分区,最后对新分区进行写入操作,然后对老分区做删除操作。

虽然每个操作都会“打开并锁住所有分区表”,但这并不表示分区表在处理过程中是锁住全表的,
分区表的锁机制取决于我们所选择的存储引擎,如果我们使用InnoDB构建分区表,
那么会在分区层(通过分区条件定位到分区后)释放表锁,之后的锁机制会按照InnoDB方式进行。

分区的优势:

1、使得一张表能存储更多的数据

2、让开发者更加专心于业务逻辑,而不是繁琐的sql条件匹配

3、让你在使用ORM框架时,更加的简单方便,无需修改ORM框架,和操作普通表完全相同

4、对于表的维护更加方便,当你需要修改字段或者调整索引时,无需同时操作300张表

5、当某些数据不再有价值时,可以直接清空一个分区,降低删除的代价(普通的删除需要根据where条件匹配后再回表删除),例如删除2015年之前的记录,可以直接清空p_2014分区,因为每个分区在底层是单独的子表,所以无需根据时间字段筛选

分区表的陷阱

分区设计上的陷阱:

上面例子中,按照时间分区的方式,会带来一个问题:
随着时间的增长,我们也需要新增分区,否则所有的数据都会落到最后的分区中,成为一个大分区,
当然,新增一个分区的代价是非常小的,完全不用太担心,但如果你已经懒到不想改表了,可以使用
自增id取模进行hash来避免这个问题,例如建立100个分区,然后以id取模100的方式作为分区条件。
这样做的好处是增长的数据都可以完全的均分到所有分区,不会造成大分区的存在,
但坏处是每个分区的数据量都会一直增长,并且在进行范围操作时会锁住大量分区。

分区列的必须作为查询条件:

因为需要根据分区列来确定数据所在分区,所以分区列必须作为查询条件,
如果不使用分区列的查询条件,那么就无法进行分区过滤,Mysql最终会扫描所有分区,这就和我们的初衷相违背了。

其他限制:

1、所有分区都必须使用相同的存储引擎

2、某些存储引擎不支持分区(MERGE、CSV、FEDERATED)

3、一张表最多只能有1024个分区

4、分区表中无法对非分区列建立唯一索引(Unique Index)

5、分区表中无法使用外键