简介
- 分库:将数据从单个数据库拆分成多个数据库
- 分表:将数据从单张表拆分成多张表
两者可以各自单独实现,也可以结合实现,用以解决不一样的问题
原因
- 单库CPU、磁盘、内存、带宽、连接数等有限,高并发情况下出现性能瓶颈
- 单表数据过大(超过千万级别),量变产生质变,就算利用索引等其他数据库优化手段也无法进一步提升单表的SQL读写性能
好处
- 分库:利用多机多库分担了单库压力,提升数据库整体并发性能
- 分表:减少了单表的数据量,提升SQL执行效率
缺点
- 跨库关联困难 —— 单库查询,业务层进行组装
- 跨库排序、分页、函数计算困难 —— 业务层聚合
- 分布式事务 —— 2PC/TCC等
- 分布式主键 —— 雪花算法等
- 存量及增量数据迁移、扩容 —— 方案复杂
考虑时机
阿里编码规约:
【推荐】单表行数超过500万行或者单表容量超过2GB,才推荐进行分库分表。
说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
反例:某业务三年总数据量才2万行,却分成1024张表,问:你为什么这么设计?答:分1024
张表,不是标配吗?
提前预测数据量,当单表行数超过500万行或者单表容量超过2GB,才推荐进行分库分表,否则过早的分库分表,容易引入上述的各种分布式问题
方式
垂直拆分
拆分后各库、表结构不同,并不会减少单库单表的数据量
分库
根据业务分库:如支付金融体系的业务就可以拆分出公司、订单、额度、交易、渠道、账单等一系列根据业务划分的库,其中只保存与自己业务相关的表
分表
根据字段含义分表:如将不常用的字段或者blob、text的大字段单独拆分到一张从表中,利用主表的主键进行关联,提升主表查询效率。
案例:用户信息主表user保存常用的如姓名、手机等信息,而用户信息扩展表user_ext可以保存用户的兴趣爱好、个人描述等不常用且为text的信息
水平拆分
拆分后各库、表结构相同,大大减少单库单表的数据量
分库
以字段为依据,按照一定策略(hash、range等),将一个库中的数据拆分到多个库中,如订单库由原先的pay-order拆分为pay-order_0/pay-order_1等库
分表
以字段为依据,按照一定策略(hash、range等),将一个表中的数据拆分到多个表中,如订单库中的订单表由原先的order拆分为order_0/order_1等表
总结
- 垂直拆分属于业务层面上的划分,以业务领域的职责范畴进行设计划分,这步在讨论业务场景时就可以以开发小组为单位划分得出
- 水平拆分属于独立系统内部层面上的划分,是由于单库或单表数据量过大,将同样结构的数据根据一定路由规则分库分表存放,提升独立系统的性能
引入方案
数据库代理或者数据源代理
image.png
数据库代理类中间件
这类分库分表中间件的核心原理是在应用和数据库的连接之间搭起一个代理层,应用连接这个代理层发送SQL请求,代理层再根据用户配置的分库分表等规则进行解析后,将SQL路由到真实的数据库,代表的产品是MyCAT
image.png
- 优点:对于应用透明,应用可以像连接MySQL一样连接MyCAT,配置简单且无代码侵入
- 缺点:SQL需要进行额外的转发,且部署MyCAT增加组件运维成本,且对开发和运维人员要求较高
框架类
代表框架为Sharding-JDBC:定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。
image.png
- 优点:性能高,无需额外部署和依赖
- 缺点:需要改动大量配置和SQL代码,且存在部分不兼容SQL的情况,代码侵入性高
手写类
很多公司老代码会利用手写AOP或者利用MyBatis的插件机制来处理分库分表:比如执行SQL之前先根据分库策略解析分片键设置数据源,再根据分表策略设置SQL的表前缀,与Sharding-JDBC是类似的机制,但实现以及功能上肯定是没有开源框架完善
策略
选择合适的分片键与合适的策略需要经常慎重考虑
取模
比如用户表以用户ID为维度取模,分8张表,则10%8=2即放到2_user表中
- 优点:数据分配均匀,不会有热点问题
- 缺点:假如后期增加分表,扩容和数据迁移非常麻烦
同时分库分表取模策略
x个库,y个表
库:id % x
表:(id / x) %y
范围
比如订单表以月为维度进行存放,如order_202108/order_202109
- 优点:利于扩容,不需要数据迁移
- 缺点:假设某个月或者双十一当月订单量较多,访问较大,其他历史订单访问少,就会产生热点问题
手动指定
比如业务上所有的SQL都是需要指定租户ID的,那么就可以以手动指定租户ID对应某个表的策略进行分表,便于查询又可以解决数据迁移以及热点问题
分析各个租户的数据量,数据量较大的租户的数据可以自己放一张表,而其他数据量较小的租户可以共用某张表,可以设计如下的租户ID与表前缀映射关系:
1:1
2:2
3:3
4:4
5:4
如未手动指定的租户统一默认放在0表
多维度分表
某些表为了适应不同条件查询,可能会采用多个维度进行分表,比如订单表可能同时需要以订单ID/用户ID进行查询,此时有两种方案:
- 多套分表:以订单ID和用户ID分别取模分表,每次处理订单都需要处理两套分表,且数据异常冗余
- 分表ID共用:取用户ID的后X位,生成订单ID,比如用户ID为12345678,如果取后4位,那么生成的订单ID规则可以自己指定,只要能够取出用户ID后4位5678即可,这样无论是任何维度单独查询都没有问题
另外还有可能以卖家维度进行查询,此时可以另起一个卖家服务以及独立数据库,以卖家ID为维度进行分表
数据迁移
停机迁移
- 事先从从库将历史数据迁移到各个分表,记录最新数据的时间
- 关闭网关流量入口
- 只查询第一步最新数据的时间到停机前这段时间内有更新的数据,再次迁移到各个分表
- 发布新版本
- 开启网关流量入口
双写不停机迁移
- 系统中修改写数据库的代码,同时写旧表和新表
- 编写数据迁移工具读取旧表数据,插入或者更新至新表
- 反复进行第二步的数据比对直至数据一致
- 发布新版本:把写旧表的代码删除,全部走新表
网友评论