1.为什么需要分库分库分表?
1.1 数据库性能瓶颈的出现
最直观的表现就是对数据库的各种操作变慢,在高并发的场景下出现应用无法获取数据库连接,
1.2 数据库优化方案对比
1.2.1 SQL 与索引
在程序中对 SQL 语句进行优化,在数据库中添加索引。
这个是我们这边最常用的手段,也是成本低,效果明显的手段,但是过多的索引也会带来一些问题,不可盲目添加。个人理解是需要建立收益大的索引,建立最优的索引,一个索引可以对多个sql起作用,同时命中后对有效的减小数据集,所以建立索引也是一个值得探讨问题。
1.2.2 引擎和配置层面优化
-
选用特定的存储引擎,或者对表进行分区,对表结构进行拆分或者冗余处理,
或者对表结构比如字段的定义进行优化。
-
数据库配置的优化,比如连接数,缓冲区大小等等,优化配置的目的都是为了更高效地利用硬件。
1.2.3 操作系统与硬件
操作系统和硬件的优化,增加cpu,增加内存。
是否需要提升硬件,需要视情况而定,主要看导致数据库慢的原因是什么,盲目的叠加配置是不可取的。
1.2.4 数据库架构优化
-
读写分离
-
数据分片
当前面三种的收益不大时候,我们就要开始考虑在架构层面进行优化了,而架构层面最为有效的方法
就是进行分库分表,我的理解是,使用分库分表使用的恰当获得的收益十分显著
2 .分库分表的类型和特点
2.1分库分表的类型
垂直切分:基于表或字段划分,表结构不同。我们有单库的分表,也有多库的分库。
水平切分:基于数据划分,表结构相同,数据不同,也有同库的水平切分和多库的 切分
-
垂直拆分:
-
单库垂直分表 : 将一张表中的数据差分的得更加的细粒度,如:将用户信息表,拆分成基本信息表,联系方式表等
-
多库垂直分表:多库垂直分表就是把原来存储在一个库的不同的表,拆分到不同的数据库。个人理解微服务划分数据库的思想和这个类似,按照业务分到不同的数据库
垂直拆分还是由于业务量大的业务导致数据库数据量大,没有从根本上解决问题
-
水平拆分:
-
单库水平分表 :将一张表按照字段值的来划分到不同的表中
-
多库水平分表:将一张表按照字段值的来划分到不同的库中,例如:按照租户分库
2.2分库分表的带来的问题
分完库分完表之后,之前在单库的的不会存在的问题都暴露出来了:
-
跨库关联查询:
如何解决跨数据关联查询的问题
(1)字段冗余:将需要跨库查询查询的字段进行冗余处理
(2)数据同步:采用etl方式将需要关联的查询的数据进行同步
(3)广播表:将一些多个数据库都需要用到表在操作的时候进行广播,例如表单表
(4)绑定表:将相同分片值相同的数据
(5)系统层面封装:在不同的数据库节点把符合条件数据的数据查询出来,然后重新组装,返回给客户端。
-
分布式事务
如果在一个数 据库里面,我们可以用本地事务来控制,但是在不同的数据库里面就不行了。所以分布式环境里面的事务,我们也需要通过一些方案来解决.
-
全局事务
-
基于可靠消息服务的分布式事务
-
柔性事务 TCC
-
最大努力通知,通过消息中间件向其他系统发送消息
-
排序、翻页、函数等计算问题
max、min、sum、count 之类的函数在进行计算的时候,也需要先在每个分片上执行相应的函数,然后将各个分片的结果集进行汇总和再次计算,最终将结果返回
-
全局主键避重问题
使用主键自增策略,在不同的数据库会出现主键冲突问题,该如何解决:
-
UUID
-
Snowflake
-
使用redis来做自增策略
现在的一些开源产品来解决分库分表带来的问题,也大多采用以上一些解决方案
3.分库分表的方案设计与对比
思考一个sql执行的流程: DAO——Mapper(ORM)——JDBC——代理——数据库服务
当前市面的解决方案基本都是从这个流程入手,下面我们将一一为其做探讨:
3.1 DAO层
在这一层主要是做数据源的路由,一般也是多数据路由的选择方案,Spring 中提供了一个抽象类
AbstractRoutingDataSource,可以实现数据源的动态切换.
这种实现方案目前在我们这边也是有应用的,在shc中的patient-document中对多数据源的路由就采取了这种方式
-
优点: 不需要依赖 ORM 框架,即使替换了 ORM 框架也不受影响。实现简单(不需要解析 SQL 和路由规则),可以灵活地定制。
-
缺点:不能复用,不能跨语言,对于以上出现的问题都需要自己手动来进行处理
3.2 ORM 框架层
如我们用 MyBatis 连接数据库,也可以指定数据源。我们可以基于 MyBatis 插件的拦截机制(拦截 query 和 update 方法),实现数据源的选择.
-
缺点: 很明显同上
3.3 驱动层
不管是MyBatis 还是Hibernate,还是Spring 的JdbcTemplate,本质上都是对JDBC的封装,所以第三层就是驱动层。比如 Sharding-JDBC,就是对 JDBC 的对象进行了封装。
JDBC 的核心对象:
-
DataSource:数据源
-
Connection:数据库连接
-
Statement:语句对象
-
ResultSet:结果集
只要对这几个对象进行封装或者拦截或者代理,就可以实现分片的操作.
-
缺点:
1.仅支持JAVA
2.占用较多的数据库连接
3.数据聚合在业务实例执行
4.版本升级较为麻烦
-
优点: 相对灵活,性能高,支持丰富的DB
例如:TDDL、ShardingJDBC
3.4代理层
前面三种都是在客户端实现的,也就是说不同的项目都要做同样的改动,不同的编程语言也有不同的实现,代理层。代理层的数据库中间件,将自己伪装成一个数据库,接受业务端的链接。然后负载业务端的请求,解析或者转发到真正的数据库中,其实是基于协议层面的,可以看后续的对比。
-
缺点:
异构支持,DB支持有限
运维负担大,需要高可用,单独部署,稳定性高,配置后需要重启
-
优点: 无代码入侵
比如 Mycat ,Sharding-Proxy和一些云厂商的分布式数据库都是属于这一层。
3.5 数据库层面
最后一层就是在数据库服务上实现,也就是服务层,某些特定的数据库或者数据库的特定版本可以实现这个功能。
-
缺点: 受限于使用数据库的类型
例如很多非关系型数据库, redis,mongodb等
3.6方案对比
目前主流的分库分表方案就是sharding-jdbc和mycat,为此做了一个对比
由于我们这边需要采用动态的配置,以及更加个性化的实现,所以选择sharding-jdbc