浅析MySQL中主从延迟问题的原因与解决方法
作者:DaveCui
从一个主从延迟问题开始回顾主从复制原理,并思考主从延迟造成的原因和解决方案。当然,作为底层开发,最后还是只能快准狠的通过一个简单粗暴的等待方案进行应对。
事情的起因
事情要从我写下这样的代码开始
// 获取当前数据库中未使用的数据转为正在使用的状态 int updateUsing = fateDataDao.update(FateDataStatusEnum.UNUSED.getCode(),FateDataStatusEnum.USING.getCode()); log.info("update UNUSED to USING:{}",updateUsing); // 获取正在使用状态的数据 List<FateData> fateDataList = fateService.queryStatus(FateDataStatusEnum.USING.getCode()); log.info("queryStatus USING:{}",fateDataList.size());
这部分逻辑清晰简单明了,把UNUSED状态的数据更新为USING状态,然后查询取出USING状态的数据。
按照一个正常的逻辑来说updateUsing
的数量和fateDataList.size()
的数量应该一样,但是,他不正常。
我在测试环境小数据量测试时,这段代码逻辑完全无误。但是上了灰度环境进行大量数据的测试就出现了这样的问题。
此时,我带着疑惑和不解,将目光投向百度。
首先我认为问题可能好似,MYSQL更新返回的是查询到的行数,而不是受影响的行数。
但是负责MYSQL的同事和我说MYSQL已经配置了返回受影响行数,并告诉我应该是主从延迟问题,没办法解决,看看业务能不能改下吧。
这时,我才反应过来当时粗略了解的主从延迟问题,我已经忘的差不多了。
什么是主从复制
要了解主从延迟,首先就要知道什么是主从复制。
MySQL的主从复制(Master-Slave Replication)是一种数据库复制技术,用于解决数据备份、读写分离、负载均衡以及故障恢复等问题。
主从复制的基本原理是将一个数据库实例(主服务器)的数据复制到另一个或多个数据库实例(从服务器),使得从服务器的数据与主服务器保持同步。
主从复制的基本工作流程
- 从服务器连接到主服务器,生成两个线程,一个I/O线程,一个SQL线程
- 主服务器记录所有的数据更改(INSERT、UPDATE、DELETE),同时会生成一个 log dump 线程,用来给从库 i/o线程传binlog。
- 主服务器会生成一个 log dump 线程将binlog写到relay log(中继日志) 文件中
- 从服务器的SQL 线程会读取relay log文件中的日志,并解析成具体操作,来实现主从的操作一致,而最终数据一致;
流程图如下:
主从复制解决的问题
要用到主从复制的原因主要是为了高可用、高并发:
- 数据备份和恢复: 从服务器可以用作主服务器的备份,当主服务器发生故障时,可以快速切换到从服务器进行恢复。
- 读写分离: 主服务器负责写操作,而从服务器可以用于处理读操作,从而分担主服务器的负载,提高系统性能。
- 负载均衡: 多个从服务器可以平均分担读请求,实现负载均衡,提高系统的可伸缩性。
- 高可用性: 当主服务器故障时,可以快速切换到一个从服务器,保证系统的高可用性。
主从复制带来的问题
主从复制也会造成一些衍生出的问题:
- 数据一致性: 主从复制是异步的,存在一定的延迟,因此在进行读写分离时,需要注意可能出现的数据一致性问题。
- 写操作集中: 所有写操作都集中在主服务器上,可能导致主服务器的负载较高。
- 配置和维护: 操作复杂,需要正确配置主从服务器,以及定期进行监控和维护,确保系统正常运行。
本次遇到的bug,主要就是数据一致性方面的问题了。
主从延迟的原因
主库使用单线程顺序写入binlog,效率很高。然而从库的SQL Thread线程需要对主库的日志进行随机IO来重新执行DML和DDL,效率较低,难以跟上主库日志写入速度,因此产生了主从延迟。
另外,从库SQL Thread也是单线程,当主库并发较高时,产生大量DML,超过了从库单线程能处理的速度,或者从库中有大查询语句产生锁等待,也会导致从库执行延迟,无法跟上主库的进度。
主从延迟的解决方案
从主从延迟的原因,我们定位出主要是主库的高并发和从库的SQL Thread效率低造成了这样的问题。
所以,在不增加机器的情况下的解决方案就是控制主库的并发或者提升从库的SQL Thread处理效率,例如MySQL 5.6 版本后,提供的一种多线程的方式。
简单粗暴的解决方案
当然,对于我们公司的底层开发来说,这种层次的设计需要更高层面的人来推动,而且也需要更长的时间才能处理。
所以这里贴出我自己的解决方案。Thread.sleep
时间请自行控制。
// 获取当前数据库中未使用的数据转为正在使用的状态 int updateUsing = fateDataDao.update(FateDataStatusEnum.UNUSED.getCode(),FateDataStatusEnum.USING.getCode()); LOGGER.info("update UNUSED to USING:{}",updateUsing); // 获取正在使用状态的数据 List<FateData> fateDataList = fateService.queryStatus(FateDataStatusEnum.USING.getCode()); LOGGER.info("queryStatus USING:{}",fateDataList.size()); // 如果数据相等,直接略过。 if(updateUsing != fateDataList.size()){ // updateUsing为0,但fateDataList不为空的情况。任务失败,未更新 if (updateUsing == 0){ Cat.logEvent("updateTmpData","jobFailed:"+"updateUsing:"+updateUsing+"--fateDataList:"+fateDataList.size()); transaction.setStatus(Transaction.SUCCESS); return response; } // 数据数目不相等,等待三秒相等再继续 boolean equalFlag = false; while(!equalFlag){ // 等待主从延迟 Thread.sleep(3000); fateDataList = fateService.queryStatus(FateDataStatusEnum.USING.getCode()); Cat.logEvent("updateTmpData","equalFailed:"+"updateUsing:"+updateUsing+"--fateDataList:"+fateDataList.size()); LOGGER.info("queryStatus USING:{}",fateDataList.size()); equalFlag = true; } // 数据数目不相等,则需要数据不为空再继续 while(fateDataList.isEmpty()){ // 等待主从延迟 Thread.sleep(1000); fateDataList = fateService.queryStatus(FateDataStatusEnum.USING.getCode()); Cat.logEvent("updateTmpData","queryFailed:"+"updateUsing:"+updateUsing+"--fateDataList:"+fateDataList.size()); LOGGER.info("queryStatus USING:{}",fateDataList.size()); } }
知识补充
Mybatis使用<update>标签怎么返回影响行数
Mybatis使用<update>标签怎么返回影响行数
在MyBatis Plus中,使用<update>标签执行更新操作时,默认情况下是不返回影响行数的。但是可以通过配置来实现返回影响行数的功能。
在MyBatis Plus的配置文件(通常是mybatis-plus-config.xml)中,可以添加如下配置项:
<configuration> <settings> <setting name="returnAffectedCount" value="true"/> </settings> </configuration>
通过设置returnAffectedCount为true,可以让MyBatis Plus在执行更新操作后返回影响行数。
另外,如果只需要获取更新操作的影响行数而不需要返回具体的更新结果,可以使用MyBatis Plus提供的UpdateWrapper或者LambdaUpdateWrapper来进行更新操作,并通过调用相应的方法获取影响行数。例如:
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>(); updateWrapper.eq("age", 25); int affectedCount = userMapper.update(null, updateWrapper);
在上述示例中,使用了UpdateWrapper.eq()方法指定了更新条件,并通过调用userMapper.update()方法执行更新操作,并返回影响行数。
总结起来,在MyBatis Plus中使用<update>标签执行更新操作时,默认情况下是不返回影响行数的。但可以通过配置来实现返回影响行数的功能,或者使用UpdateWrapper或者LambdaUpdateWrapper来获取影响行数。
到此这篇关于浅析MySQL中主从延迟问题的原因与解决方法的文章就介绍到这了,更多相关MySQL主从延迟内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!