业务背景
在业务量初期,数据量很小,大多数开发人员都选择了采用单库单表进行研发方案设计和构建。单库单表的好处是开发快,业务模型构建与库表映射简单,便于理解和沟通。随着业务增长,伴随单库单表而来的是查询瓶颈问题,首先在单表突破百万甚至千万,在已有索引的前提下查询优化空间并不多,而且数据变更往往要小心翼翼,一旦有大事务会直接拖慢整个数据库的查询性能,连锁雪崩效应带来的后果不堪设想,往往我们会对单库单表进行垂直和水平拆分以达到优化查询的目的。
问题描述
在水平拆分的过程中,通常都是以一个字段作为切分键对数据进行拆分,一般我们会使用用户ID等业务互通的字段。在水平拆分后,这里引申出一个问题,那就是当单表时,任何字段的检索都可以通过一条SELECT语句完成;在拆分后,由于切分键字段负责路由数据归属哪个库哪张表,在水平拆分数据之后切分键数据变成了必填字段,无论查询什么都需要至少或包含切分键字段参与,但是实际业务场景外部仅仅知道非切分键字段进行查询,需要对该场景进行解决。
切分键路由问题
单库单表、分库分表对比如下,水平拆分后业务字段中只有切分键字段可路由到数据,也即任何查询都要携带切分键信息才可以,要么通过切分键查询数据,要么通过切分键+任何业务字段进行查询。
表结构 |
字段定义 |
分库分表切分键字段 |
单库单表 |
字段A,字段B |
不需要 |
分库分表 |
字段A,字段B |
业务字段A |
如下图是单库单表做水平拆分,这里切分键是使用的userId,根据不同的userId进行数据库表归属。单表数据被拆分到多库表中,降低了数据量集中在单表,但是只能通过userId或userId+业务字段进行查询,无法单独通过userName、certificateNo等非切分键字段独立查询。
解决方案
表结构设计
当数据库表水平拆分后,设计表结构如下
表 |
核心字段定义 |
分库分表切分键字段 |
业务表 |
业务字段A,业务字段B |
业务字段A |
绑定关系表 |
绑定字段,被绑定字段 |
被绑定字段(这里是业务表未路由字段,即业务字段A之外的字段) |
以用户信息表举例,如下
表 |
业务字段定义 |
分库分表切分键字段 |
用户信息表 |
用户ID,姓名,身份证 |
用户ID |
绑定关系表 |
身份证(绑定字段),用户ID(被绑定字段) |
身份证(绑定字段) |
- 1.业务表正常水平拆分,以通用的核心业务字段做切分键进行数据路由
- 2.增加绑定关系表,与业务表同等进行水平拆分,以被绑定关系的业务字段作为切分键进行数据路由
流程设计
sequenceDiagram
业务请求-->> 数据库: 数据持久化
Note right of 数据库: 1.持久化数据<br>2.初始化绑定标志位
数据库-->> 异步消息: 绑定MQ
Note right of 异步消息: 数据库事务:<br>1.数据库持久化<br/>2.异步MQ
异步消息-->> 数据库: 变更标志位
Note right of 异步消息: 完成绑定标志位
补偿任务-->> 数据库:数据补偿
Note right of 补偿任务: 扫描初始化绑定标志位
数据库-->> 异步消息: 绑定MQ
Note right of 异步消息: 补偿处理
异步消息-->> 数据库: 变更标志位
Note right of 异步消息: 完成绑定标志位,数据最终一致
- 1.业务请求发起后通过数据库事务进行数据持久化和绑定MQ发送这两部操作,这部操作是依赖数据库的事务原子性来包装数据正常持久化和绑定MQ的发送的
- 2.在数据持久化时,对绑定字段进行标志位初始化,表示业务数据持久化成功,但绑定字段未持久化完成,这里是一个中间态,如果绑定数据出现异常,补偿任务也可通过该标志位进行数据补偿
- 3.异步消息负责处理绑定数据。由于业务表、绑定关系表路由字段不同,数据会路由到不同库表,因此无法通过数据库事务进行操作,这里通过异步绑定方式进行绑定关系持久化,实现最终一致
- 4.补偿任务会定时轮询业务数据的标志位,在一定时间内未完成绑定业务的数据会进行补偿,重发MQ,数据最终一致
其他方案
常见的解决方案之一是可以通过MySQL的binlog日志汇总到第三方数据作业平台,通过HBase+ElasticSearch等手段进行数据聚合,这里不对该方式进行介绍,仅谈论利用纯关系型数据库解决异构场景的方法。