日常 Bug 排查 - 偶发性读数据不一致

日常 Bug 排查 - 偶发性读数据不一致

前言

日常 Bug 排查系列都是一些简单 Bug 的排查。笔者将在这里介绍一些排查 Bug 的简单技巧,同时顺便积累素材。

Bug 现场

业务场景

先描述这个问题出现的业务场景。这是一个支付的场景,如果支付成功了,我们就把支付状态置为 success (主单据更新) 同时写入支付成功时间戳为 t1 (子单据更新)。支付成功之后,我们还需要做其它的动作,做这个动作的时候我们需要刚才的支付成功时间戳 t1。那么,我们正常的请求顺序即为:

Bug 现场

奇怪的是,线上运行时候,会有极小的概率 (大概是几亿分之一) 获取的这个时间戳为 0!也即在读到主单为 success 的时候,看到的子单时间戳是 0!由于时间戳为 0,所以调用下游 RPC 传参错误导致了调用失败。 如下图所示:

思路

因为在请求 1 中,我们是在事务内更新的,数据应该始终保持一致才对。那很直观的第一个思考点就是: 思路 1: 是不是事务没生效?笔者看了下源代码,使用没有问题,也不存在类内方法互相调用的情况。再者说,如果事务没生效,概率不至于这么低。

思路 2: 稍加思索一下,好像这个是事务隔离级别的原因。在这个 Case 里面,看上去数据库采用的 RC 隔离级别,也就是读已提交。如下图所示:

复制代码
t1时刻,请求2查询到的子单据时间戳为0
t2时刻,请求1提交,这时候将子单据时间戳更新为t1,主单据状态为success
t3时刻,请求2由于RC隔离级别,能看到请求1的提交,主单状态为success,所以判定可以进行下游RPC的调用,但是由于在t1时刻获取到的时间戳为0,导致调用失败

矛盾点

数据库隔离级别是 RC 应该能非常好的解释出现 Bug 时的行为。于是笔者查了一下隔离级别,发现是 RR,这就陷入了矛盾!但由于 RC 这个隔离级别解释这个 Bug 非常的靠谱,所以笔者看了下业务的数据库配置,发现它有 100 个库。那么就自然有了下一步猜想:这 100 个库中有的是 RR 的,有的是 RC 的。出问题的那个库正好就是 RC 的。

指定库查询隔离级别

于是笔者就根据业务的 shardKey 到了指定的库查询隔离级别,发现它果然是 RC 级别的,真相大白!这 100 个库中大概有 1/3 的库是 RC 隔离级别。

后续修复

这个问题是由于 DBA 在换库的过程中采用了默认的配置,导致原来设置为 RR 级别的库在换了大容量机器后被默认改成了 RC 隔离级别。DBA 找了个时间将隔离级别切换回 RR 后问题就消失了,并编写了相应的巡检脚本防止此类问题再次发生。

总结

隔离级别是比较微妙的,相关问题大多只在高并发大流量下才会有偶发性的显现,分库分表集群中不同 DB 的隔离级别由于种种原因导致的不一致会加大问题的排查难度。有时候遇到无法解释问题时可以考虑下底层组件的设置问题。

公众号

欢迎大家加我公众号<<解Bug之路>>,干货量大管饱。

相关推荐
码事漫谈18 小时前
黄仁勋被裁第一天,就收到了橄榄枝
后端
星恒随风19 小时前
MySQL 从零入门(五):MySQL 内置函数详解——日期、字符串、数学函数与实战案例
linux·数据库·笔记·sql·mysql
金銀銅鐵19 小时前
带您看懂字节码指令(二):空循环里有什么
java·后端·ai编程
皮皮林55120 小时前
比 Xshell 还好用的 SSH 客户端神器,MobaXterm 太爱了!
后端
爱码少年21 小时前
Springboot下如何将CSV文件数据导入H2、Mysql数据库表
数据库·mysql·csv
我不会起名字32221 小时前
会话缓存失效排查:区分 Redis 失效与节点广播
redis·后端·go·即时通讯·im·故障排查
2603_969694851 天前
【JavaWeb学习记录】MyBatis核心知识点与实战笔记:从入门到动态SQL
笔记·sql·学习·mysql·mybatis·javaweb·学习方法
深入云栈1 天前
Netty 4.2.x 源码深度解析 (二十三):ByteBuf 核心设计 —— 索引、类型与零拷贝
java·后端·架构
JackJiang1 天前
IM踩坑:聊天消息ID怎么设计?一个UUID搞不定,我用双ID解决
后端·架构
开心猴爷1 天前
iOS 上架 App 流程
后端·ios