随着业务和数据量的增长,单库的IO压力越来远大,可将单一数据库的数据分散到多个数据库中,来缓解数据库的访问压力。
数据库切分的两种方式:
1. 垂直切分
垂直分库:根据业务的耦合程度,将关联度低的表存储在不同的数据库中。与微服务的做法相似,每个微服务使用单独的数据库。
垂直分表:基于数据库中的列进行。新建一张扩展表,将不经常使用或字段长度较大的字段拆分到扩展表中。
在字段很多的情况下,通过垂直分表,能避免跨页问题(MySQL底层通过数据页存储,每页存储16KB,一条数据占用空间过大会导致跨页)。
另外,数据是以行为单位,将数据加载到内存中,垂直分表会使一行的数据变短,内存中能够加载更多行数据,减少了磁盘IO。
2. 水平切分
垂直切分后,可能有些单表的数据还是非常大,这时需要对这个表上的数据进行水平切分。
水平切分便是将一个表按照不同的规则分散到多个数据库或多个表中。如果只做库内水平切分,意义不大,因为单个库内不同表的访问,仍然会竞争同一个物理机的CPU和内存。
几种典型的数据分片规则:
1. 根据数值范围
ID区间或时间区间。比如:按照日期将不同年或月的数据放进不同的表中。将 userId 为1~9999的数据放进一个库,10000~19999的数据放进另一个库。
缺点:热点数据被访问的频率很高,可能会成为性能瓶颈。
2. 根据数据取模
一般采用 hash 取模的切分方式,数据比较均匀。
比如 对 userId 取模,余数为0的放进一个库,余数为1的放进另一个库,以此类推。
缺点:
1. 后期集群扩容时,需要迁移旧数据(一致性hash能解决这个问题)。
2. 跨分片查询时,性能低下。在上例中,如果查询条件中没有 userId,会导致无法定位数据库,从而需要同时向多个库同时发起查询,再合并取并集,分库反而成了拖累。
3. 数据库切分带来的问题
1. 数据发生了切分,跨界点 join 问题是不可避免的。常用的解决方法就是分两次查询完成,在第一次查询结果中找出关联数据的 id,根据 id 发起第二次请求得到关联数据。除此之外,在分片之前,可以将有关联的表放进同一个库中,避免跨分片 join 的问题。
2. 分布式事务处理复杂。
分布式事务能够最大程度上保证数据库操作的原子性,但在提交事务时需要协调多个数据库节点,延长了事务的执行时间。使数据库在访问共享资源时,发生冲突和死锁的概率增高。
3. 全局主键重复问题
在水平切分情况下,表中数据存储在不同数据库中,会出现主键重复的问题,主键的自增特性也没了用武之地。
需要设计全局主键,避免跨库主键重复。