前言: 分表、分库是一个复杂的工程,需要考虑到很多,只能提供一些思路,具体的实现还是得按照具体的业务进行实施,此文章仅代表鄙人的总结和理解,如有错漏,欢迎指正...
当数据库比较庞大,读写操作特别是写入操作过于频繁,很难由一台服务器支撑的时候,我们就要考虑进行数据库的切分。
所谓数据库的切分,就是我们按照某些特定的条件,将一台数据库上的数据分散到多台数据库服务器上。因为使用多台服务器,所以当一台服务器宕机后,整个系统只有部分数据不可用,而不是全部不可用。
因此,数据库切分不仅能够用多台服务器分担数据库的负载压力,还可以提高系统的总体可用性。
分库分表一定是为了支撑高并发、数据量大两个问题的。无论使用哪种方式分表 只要不是按照分表字段查询都得查询所有表。
分库-垂直拆分:每张表独立出去,一张表一个数据库服务器
分库是啥意思?就是你一个库一般我们经验而言,最多支撑到并发 2000,一定要扩容了,而且一个健康的 单库并发值你最好保持在每秒 1000 左右,不要太大。那么你可以将一个库的数据拆分到多个库中,访问的时候就访问一个库好了。
优点:专库专用,结构清晰,拓展容易,易于维护
缺点:表关联join麻烦,业务不同导致性能单一瓶颈
1、Join操作(ER表就是一个分表规则,你关联查询逻辑不用改,因为相关数据都在一个库里了)
在一个数据查询当中,通常会涉及数据库的多张数据表,所以需要通过SQL的join操作来进行拼表,但是进行分库之后可能这些表分散到了多个不同的子数据库中了,故需要对原有应用代码所使用的SQL进行修改,由原来的一条SQL查询对应的数据改为在应用代码中通过HTTP或者RPC的方式从各个子服务获取数据,然后在应用代码中进行数据组装。
所以进行分库之后,对于单个请求性能反而可能会下降,因为增加了不同主机之间的服务调用,网络请求。但是对于整个系统的整体性能和吞吐量则可以提高。因为拆分之后各个子服务并发执行而且速度更快,即使需要不同服务之间的网络通信调用,由于各个子服务调用都执行更快,故系统整体可以运行地更快。
如果原有系统数据表之间耦合度较低,每个系统子模块的内聚性比较好,很少进行join拼表查询,则就会减少不同子服务之间的调用和减少对原有应用代码的修改调整,故在数据库设计时,可以适度进行数据冗余存储,即适度地进行反范式设计来减少join的需要,保持各个子模块的内聚性,这样可以提供系统后期的可拓展性,如在进行微服务拆分和分库时,操作更加方便。
2、分布式事务
对于数据更新操作,如果更新的表都在一个数据库当中,则可以基于MySQL的Innodb存储引擎的事务机制来保证数据的一致性,而不需要在应用代码中进行这方面的考虑。
在进行分库之后,如果一次更新涉及到的表分散到了不同的子数据库中,则此时需要基于分布式事务来保证数据一致性。通常的做法是每个子数据库基于MySQL的事务机制来实现事务安全,在整体上需要在应用代码中保证所有子数据库都操作成功才执行,即整体的ACID。
分布式事务的实现方案:
包括两阶段提交,基于消息队列实现等,具体后续分析。
MySQL也提供分布式事务的实现XA。由于分布式事务需要涉及多个节点的事务,所以在性能方面相对于单机事务,事务执行所需时间比较长,故会增加数据更新操作的延时。
所以在进行分库时,要尽量减少分布式事务的需要,是否需要分布式事务也要作为是否将一个子服务进行继续拆分的考虑条件,即如果拆分后需要通过分布式事务来实现数据一致性,则此时可能不适合再进行拆分。
分表-水平拆分:整张表中的数据信息,按照一定的数据分出去另外的数据库服务器。控制在1000万之内一个数据库服务器。
优点:合理分配大数据,不存在高并发
缺点:拆分规则复杂,不好操作
拆分原则:先合理垂直切分,再适时水平切分;先模块化切分,后数据集切分”。
分表后--数据迁移
1.停机迁移方案(要熬夜不推荐,但是面试可以拿这个来说,因为比较简单粗暴不会露馅)
步骤如下:
(1)出一个公告,比如“今晚00:00~6:00进行停机维护,暂停服务”
(2)写一个迁移程序,读db-old数据库,通过中间件(mycat 或 Sharding-JDBC)写入新库db-new1和db-new2
(3)校验迁移前后一致性,没问题就切该部分业务到新库。
2.双写迁移方案
老库和多个新分表分库都执行增删改查,然后执行迁移工具,将如果新库没有就迁移到新库去,多执行几次保证完全迁移后,再把业务改一下,直接使用新表的增删改查,一旦分表上线后所有的数据写入、查询都是针对于分表的,所以原有大表内的数据必须得迁移到分表里,不然对业务的影响极大。
我们估算了对一张 2 亿左右的表进行迁移,自己写的迁移程序,大概需要花4~5天的时间才能完成迁移。意味着这段时间内,以前的数据对用户是不可见的,显然这样业务不能接受。
3.做兼容处理:
分表改造上线后,所有新产生的数据写入分表,但对历史数据的操作还走老表,这样就少了数据迁移这一步骤。
只是需要在操作数据之前做一次路由判断,当新数据产生的足够多时(我们是两个月时间),几乎所有的操作都是针对于分表,再从库启动数据迁移,数据迁移完毕后将原有的路由判断去掉。
最后所有的数据都从分表产生和写入。
迁移数据后怎么验数据一致性
1、先验数量是否一致,因为验数量比较快。
至于验具体的字段,有两种方法:
1、有一种方法是,只验关键性的几个字段是否一致。
2、还有一种是,一次取50条(不一定50条,具体自己定,我只是举例),然后像拼字符串一样,拼在一起。
用md5进行加密,得到一串数值。新库一样如法炮制,也得到一串数值,比较两串数值是否一致。
如果一致,继续比较下50条数据。如果发现不一致,用二分法确定不一致的数据在0-25条,还是26条-50条。
以此类推,找出不一致的数据,进行记录即可。
分表后--业务兼容
报表统计:
首先是报表,没分表之前之间查询一张表就搞定了,现在不同,由一张表变为 N 张表。
所以原有的查询要改为遍历所有的分表,考虑到性能可以利用多线程并发查询分表数据然后汇总。
不过只依靠 Java 来对这么大量的数据做统计分析还是不现实,刚开始可以应付过去,后续还得用上大数据平台来处理。
查询:
再一个是查询,原有的分页查询肯定是不能用了,毕竟对上亿的数据分页其实没什么意义。
只能提供通过分表字段的查询,比如是按照订单 ID 分表,那查询条件就得带上这个字段,不然就会涉及到遍历所有表。
这也是所有分表之后都会遇到的一个问题,除非不用 MySQL 这类关系型数据库。
分库后--业务兼容(多数据源处理)
应用自身对这些数据的查询、写入都要改为调用一个独立的 Dubbo 服务,由这个服务对迁移的表进行操作。
暂时不做数据迁移,所以查询时也得按照分表那样做一个兼容,如果查询老数据就要在当前库查询,新数据就要调用 Dubbo 接口进行查询。
对这些表的一些关联查询也得改造为查询 Dubbo 接口,在内存中进行拼接即可。
如果数据量确实很大,也可将同步的 Dubbo 接口换为写入消息队列来提高吞吐量。
历史数据归档(大数据分析等)
将 N 个月之前的数据要定期迁移到HBASE之类存储,保证MySQL中的数据一直保持在一个可接受的范围。而归档数据的查询便依赖于大数据提供服务。

参考博客:
MySQL学习(四):分库分表的原理
分库分表实践
结语:以往都是看别人的博客进行学习技术,其中不乏有精华博客也有吊儿郎当的CV大法文章,所以决定将自己所学所用所整理的知识分享给大家,主要还是想为了后浪们少走些弯路,多些正能量的博客,如有错漏,欢迎指正,仅希望大家能在我的博客中学到知识,解决到问题,那么就足够了。谢谢大家!(转载请注明原文出处)