1. 分库分表简介
MySQL 作为互联网公司都会用到的数据库,如果在使用过程中出现性能问题,会采用MySQL的横向扩展,使用主从复制来提高读性能,要是解决写入问题,需要进行分库分表。分库分表是业务发展到一定阶段,数据积累到一定量级而衍生出来的解决方案。当DB的数据量级到达一个阶段,写入和读取的速度会出现瓶颈,即使是有索引,索引文件也会变的很大,复杂度越来越高,修改数据时更新索引树,效率较低,执行慢。而且数据库的物理文件大的会使备份和恢复等操作变的很困难。这个时候由于DB的瓶颈已经严重影响到了业务,最有效的解决方案莫过于DB的分库分表。数据库表的拆分解决的问题主要是存储和性能问题,MySQL在单表数据量达到一定量级后,性能会急剧下降,相比较于sqlserver和Oracle这些收费DB来说,MySQL在某些方面还是处于弱势,但是表的拆分这个策略却适用于几乎所有的关系型数据库。目前市面上的公司主流的数据库就是MySQL,使得掌握数据库的性能优化尤为的重要。
2. 分库分表的目的
- 解决由于数据量过大而导致数据库性能降低的问题;
- 将原来独立的数据库拆分成若干数据库组成,解决单数据瓶颈问题和分担单个数据的压力;
- 将数据大表拆分成若干数据表组成,降低单表数据量,提高单表性能。
3. 分库分表标准
在数据量达到多少可以考虑分库分表呢,根据不同公司,不同服务器性能,可能会存在一定的差异,下面提供三条参考的标准:
- 存储占用100G+
- 数据增量每天200w+
- 单表条数5000万+(MySQL的单表达到5000万条数据后,性能会严重下降)
以上的三条标准并非是同时满足,而是存在一个方面就可以考虑分库分表,具体如何分,需要参考实际的场景。
4. 分库
4.1 分库的方式
- 垂直分库:将单个数据库,根据不同的业务模块,将数据库进行归类划分,子数据库之间表结构都是不同的,核心理念就是专库专用。
- 水平分库:以字段为依据,按照一定的策略,将一个库内的数据拆分到多个库中,核心理念是解决大表的问题。
4.2 垂直分库
4.2.1 概念
将单个数据库,根据不同的业务模块,将数据库进行归类划分,子数据库之间表结构都是不同的,核心理念就是专库专用。
4.2.2 场景分析
系统绝对并发量上来了,并且可以抽象出单独的业务模块。比如原数据库中包含订单模块、用户模块、支付模块、仓储模块、物流模块等多个模块的数据表,表的数量庞大。此时可以根据不同的模块进行划分,对应模块自身的独立数据库。目前流行的微应用、微服务就是从业务层面和数据库层面的垂直切分。
4.2.3 示意图
经过垂直分库方式最终达到的效果图如下:
4.3 水平分库
4.3.1 概念
以字段为依据,按照一定的策略,将一个库内的数据拆分到多个库中,核心理念是解决大表的问题。一般都是才用数据表的主键,通过求模法、一致性Hash等方式实现,可以尽可能的将数据均匀的落在多个数据库中。最终形成的多个数据库表数量和表结构都是相同的。
4.3.2 场景分析
首先是当前的这个数据库已经是原子化了,不可再做垂直分库优化,然后就是系统绝对并发量上来了,仅分表已经难以根本上解决问题。
4.3.3 示意图
经过水平分库方式最终达到的效果图如下:
5. 分表
5.1 分表的方式
- 垂直分表:垂直分表即”宽表拆窄表”,以字段为依据,按照字段的活跃性,将表中字段拆到不同的表(主表和扩展表)中。
- 水平分表:水平分表是以字段为依据,按照一定策略(hash、range 等),将一个表中的数据拆分到多个表中,也称为库内分表。
5.2 垂直分表
5.2.1 概念
垂直分表即”宽表拆窄表”,以字段为依据,按照 字段的活跃性,将表中字段拆到不同的表(主表和扩展表)中。垂直分表一般是表中的字段较多,将冗余字段,不常用字段,数据较大,长度较长(例如 text 类型字段)的拆分到”扩展表”。一般是针对那种几百列的宽表,也可以避免在查询时,数据量太大造成的”跨页”问题。
5.2.2 场景分析
系统绝对并发量并没有上来,表的记录并不多,但是字段多,并且热点数据和非热点数据在一起,单行数据所需的存储空间较大。以至于数据库缓存的数据行减少,查询时会去读磁盘数据产生大量的随机读IO,产生IO瓶颈。
垂直分表的拆分原则是将热点数据(可能会冗余经常一起查询的数据)放在一起作为主表,非热点数据放在一起作为扩展表。这样更多的热点数据就能被缓存下来,进而减少了随机读IO。
拆了之后,要想获得全部数据就需要关联两个表来取数据。但记住,千万别用 join,因为join不仅会增加CPU负担并且会讲两个表耦合在一起(必须在一个数据库实例上)。关联数据,应该在业务Service层做文章,分别获取主表和扩展表数据然后用关联字段关联得到全部数据。
5.2.3 示意图
经过垂直分表方式最终达到的效果图如下:
5.3 水平分表
5.3.1 概念
水平分表是以字段为依据,按照一定策略(hash、range 等),将一个表中的数据拆分到多个表中,也称为库内分表。
水平分表的结果是每个表的结构都一样,每个表的数据都不一样,没有交集,所有表的并集是全量数据。
5.3.2 场景分析
系统绝对并发量并没有上来,只是单表的数据量太多,影响了SQL效率,加重了CPU负担,以至于成为瓶颈。采用库内分表后,单表的数据量少了,单次SQL执行效率高,自然减轻了CPU的负担。
5.2.3 示意图
经过水平分表方式最终达到的效果图如下:
6. 分库分表方案
上面介绍了垂直分库、水平分库、垂直分表、水平分表,在垂直分表中,主要是根据业务划分,不涉及策略问题。但是在水平分库、垂直分表、水平分表中都会涉及到策略问题,数据放到哪个库、哪个表,这个规则策略怎么来定,就成为了问题。
分库分表是对数据库拆分的一种解决方案,根据实施切片逻辑的层次不同,我们将分库分表方案大致分为三大类:客户端分片、代理分片和支持事务的分布式数据库
6.1 客户端分片
所谓的客户端分片即在使用数据库的应用层直接操作分片逻辑,分片规则需要在同一个应用的多个节点间进行同步,每个应用层嵌入一个操作切片的逻辑实现。
6.1.1 应用层直接实现
这是一种非常通用的解决方案,通过在应用层直接通过分片规则的计算,确定命中对应服务器内数据库中的表。
这种解决方案虽然有一定的代码侵入,但是实现起来比较简单,但是切片的逻辑是自己开发的,如果生产上遇到了问题,能快速定位解决。
当然这种方式也存在缺点:代码的耦合度比较高,其次这种实现方式会让数据库保持的链接比较多,这要看应用服务的节点数量,需要提前进行容量上的评估。
6.1.2 通过定制JDBC协议实现
这种解决方案主要是为了解决应用层直接实现方案中的代码耦合问题,通过定制JDBC协议来实现(主要是针对业务逻辑层提供与JDBC一致的接口),让分库分表在JDBC的内部实现。目前市面上使用的比较多的技术框架就是当当网的开源框架:Sharding JDBC。
6.1.3 通过定制ORM框架实现
目前ORM框架非常流行,流行的JPA、Mybatis和Hibernate都是优秀的ORM框架,通过定制ORM框架来实现分库分表方案,常见的有基于Mybatis的分库分表方案的解决。如下面的代码:
<select id="selectUser" parameterType="java.util.Map" resultType="User">
select user_id as userId,user_name as userName
from user_#{index}
where user_id = #{userId}
</select>
通过业务逻辑,计算出具体访问表的编号index
,然后动态的拼接到SQL语句中。
6.2 代理分片
代理分片就是在应用层和数据库层之间添加一个代理层,把分片的路由规则配置在代理层,代理层对外提供与JDBC兼容的接口给应用层,在业务实现之后,在代理层配置路由规则即可。
这种方案的优点:让应用层的开发人员专注于业务逻辑的实现,把分库分表的配置留给代理层处理。
同样也存在缺点:增加了代理层,这样的话对每个数据库操作都增加了一层网络传输,这对性能是有影响的,同时需要维护增加的代理层,也有了硬件成本,线上生产环境出现了问题,不能迅速定位,需要有一定的技术专家来维护。
我们常见的Mycat就是基于此种解决方案来实现的。
6.3 支持事务的分布式数据库
支持分布式事务的框架,目前有OceanBase、TiDB框架,这些框架将可伸缩特定和分布式事务的实现包装到了分布式数据库内部实现,对使用者透明,使用者不需要直接控制这些特性,但是对事务的支持不如关系型数据,适合大数据日志系统、统计系统、查询系统、社交网站等。
但是OceanBase、TiDB框架都是收费使用的,无形中增加了企业的成本,在很多企业都不去采用这些框架,而是采用客户端分片(Sharding-JDBC)或代理分片(Mycat)实现。
7. 常见的分片策略
7.1 按照哈希切片
对数据库的某个字段进行来求哈希,再除以分片总数后取模,取模后相同的数据为一个分片,这样将数据分成多个分片的方法叫做哈希分片。
- 优点:数据切片比较均匀,数据压力分散的效果好
- 缺点:数据分散后,对于查询需求需要进行聚合处理
7.2 按照时间切片
按照时间的范围将数据分布到不同的分片上,比如我们可以将交易数据按照与进行切片,或者按照季度进行切片,由交易数据的多少来决定按照什么样的时间周期来进行切片。这种切片方式适合明显时间特点的数据,常见的就是订单历史查询。
8. 分库分表带来的问题
分库分表是很好的思想,但是也会带来很多的问题。
- 分布式事务问题:之前单库、单表的操作就不会出现分布式事务问题,由于垂直分库,导致根据业务线划分数据库,如果DB1操作异常回滚,会导致DB2的操作无法回滚等类似问题,这个应该如何解决。解决方案的主要思想就是采用分段式提交事务,最终一致性解决。
- 跨节点join问题:在一个数据库中进行多表关联查询,直接使用join查询。进行分库分表后,将多表分布到多个节点上就不能只用join关联查询了。解决方案的主要思想采用字段冗余方案,或者调用别的服务,结果归并。
- 主键重复问题:以前采用主键自增。在进行水平分表后,表中数据的主键重复,解决方案的主要思想是由业务逻辑来统一生成主键ID,实现不重复,常采用UUID或者雪花算法来生成。由于UUID是无序的,在插入数据后,主键索引的建立过程性能消耗过高,所以更推荐使用雪花ID来实现。
- 跨节点合并排序、分页的问题:比如分页查询,多个数据库、多个数据表,如何实现分页查询、排序等操作。主要的解决思路就是多数据源分别查询,归并得出最终结果。当然如果仅仅是单库中单个表的水平分表,其实可以通过建立逻辑表的方式来实现。
- 多数据源管理的问题:多数据源,在什么情况下命中到什么数据源,将是一套较为复杂的逻辑。在Sharding-JDBC框架中提供了一套解决方案,实现了多数据源的管理。
总结:目前市面比较常用的分库分表的框架是当当网的Sharding-JDBC和阿里的Mycat。通过对Sharding-JDBC和Mycat的基础了解,在基础的项目中使用Sharding-JDBC是完全可以实现的,上手简单,可以直接集成到项目中,无需单独的服务器部署。Mycat属于分片代理服务,需要另外安排服务器资源来部署,并且是集群部署(单机部署存在单点故障的问题),对于资源要求比较敏感的,就有点尴尬啦。
Sharding-JDBC的官方文档:https://shardingsphere.apache.org/document/current/cn/overview/
9. 参考文章
MySQL分库分表,何时分?怎么分?
MySQL思考-分库分表(Sharding-JDBC)
Mysql分库分表实战(一)——一文搞懂Mysql数据库分库分表