这一周线上碰到一个诡异的BUG。数据库
线上有个定时任务,这个任务须要查询一个表几天范围内的一些数据作一些处理,每隔十分钟执行一次,直至成功。网络
经过日志发现,从凌晨5:26分开始到5:56任务执行了三次,三次都由于SQL查询超时而执行失败,而诡异的是,任务到凌晨6:00多就执行成功了。运维
天天都是凌晨五点多失败,凌晨六点执行成功。测试
点开异常日志一看是这样的:spa
总结来讲就是MySQL查询超时。日志
像这种稳定复现的BUG,我原觉得只需三分钟能定位,没有想到却耗费了我半天的时间。code
看到超时SQL,大多数人第一反应就是这个SQL没有走索引,我也不例外,我当时的第一反应就是这条SQL没有走索引。因而,我将日志里面的SQL复制了出来,脱敏处理一下大概是这样的一条SQL:索引
select * from table_a where status_updated_at >= ? and status_updated_at < ?
SQL里面有两个日期参数,这两个起始日期是某种商品的可交易时间区间,相隔三到五天,我取了17天的时间间隔的保守值,Explain了一下这条SQL。接口
从图上能够看到这条SQL的执行仍是走了索引的。走的是根据status_updated_at字段创建的索引。事务
执行了一下也只耗时了135毫秒。
根据Explain结果,我当时的推断是:这条SQL确定走了索引,若是没有走索引,那六点多钟的查询确定也会超时,由于这个表的数据是千万级别的。
为了验证这一推断,我找DBA帮我导出了一下凌晨5点到早上7点关于这个表的慢SQL,DBA告诉我那个时间段没有关于这个表的慢SQL。
这也进一步验证了我说推断:这条SQL走了索引,只是在五点多的时候由于一些神秘缘由致使了超时。
接下来,须要作的就是找出这个神秘的缘由。
按照以往的经验,我认为有这几点因素会致使查询超时
首先,我经过监控系统查看了那段时间MySQL的运行状况,链接数和CPU负载等指标都很是正常。因此,由于MySQL负载致使超时首先就能够被排除。
那会不会是其余业务操做这个表影响的呢?
首先,咱们线上数据库事务隔离级别设置的是RR(可重复读),由于MVCC的存在,简单的修改确定是不会影响查询至超时的。
要想影响惟一的可能性就是别的业务在update这个表数据的时候,更新条件没有走索引,致使行锁升级成表锁,而且,这个操做要恰好在凌晨5点多执行,且持续了半个小时。
这个条件很是苛刻,我检查了相关的代码,问了相关负责人,并无这种状况,全部的更新都是根据Id主键更新的。关键是,若是更新SQL的更新条件没有走索引,确定会是一个慢SQL的,那么,咱们在慢SQL日志文件里面就能找到它,实际上并无。
是否是由于凌晨5点多,数据库在备份的缘由呢?
首先备份锁表不会锁这么久,这个任务是前先后后半个小时都执行失败了;
其次咱们是备份的从库,并非备份的主库;
最后,咱们的备份任务都不是凌晨五点执行的。
因此,由于备份致使超时能够排除了。
是否是网络波动的缘由呢?
我找运维同窗帮忙看了一下执行任务的那台机器那段时间的网络状况,很是平缓没有丝毫问题,机房也没有出现什么网络抖动的状况。
再者,若是是网络问题,确定会影响其余任务和业务的,事实上,从监控系统中查看其余业务并无什么异常。
因此,由于网络波动致使超时也能够排除了。
我前后排除了索引、网络、备份、业务竞争MySQL资源等因素,在脑海里模拟了N种状况,脑补了一条SQL整个执行过程,想不到会有什么其余缘由了。
这个事情变得诡异了起来,DBA劝我暂时放弃,建议我把任务执行时间延后,加一些监控日志再观察观察。毕竟,又不是不能用。
放弃是不可能放弃的,我是一个铁头娃,遇到BUG不解决睡不着觉。
理清思路,从头来过,我向DBA要了一份线上五点到六点的慢SQL的文件,本身从新找了一遍,仍是没有找到这个表相关的慢SQL。
在我忽然要放弃的时候,我忽然发现SQL日志记录里面的时区都是标准时区的,而我那个任务执行的时候是北京时间,要知道标准时区和北京时区是差了8个小时的!
好家伙!我都要想到量子力学了,结果发现时区没对上?
会不会是DBA找慢SQL的时候时间找错了啊?
我将这个“重大发现”告诉了DBA,DBA帮我从新跑一份慢SQL,好家伙,出现了我想要那个表的慢SQL。
从日志上面能够看到,查询的日期区间从2020年9月到2021年4月,时间跨度7个月。MySQL成本计算的时候认为区间太大,走索引还不如直接扫描全表,最终没有走索引扫描了1800W条数据。
说好的时间区间最多七天呢?怎么变成了七个月?
赶忙定位代码,定位发现底层在取时间区间时,调了一个RPC接口,这个接口预期返回的时间区间只有几天,结果返回了七个月的时间区间。这段逻辑是18年上线的。
因而联系提供这个RPC接口的相关人员,经过查找验证肯定这是底层数据的问题,应该返回几天结果返回了几个月。
最后修复了相关数据,增长了相应的校验和监控,从新发布系统,这个存在了两年的BUG也就得以解决了。
这个故事到这里也就结束了。
再回顾一下,还有几个问题须要回答一下:
缘由就是六点基本上没有业务在调用MySQL,那个时候的MySQL的资源是很是充足的,加上MySQL的机器也配置很是的高,因此这条SQL硬生生跑成功了。听起来有点离谱,但确实是这样的。
这个时间区间底层数据用的很少,目前只发现只有这个超时SQL任务在调用。
原来业务量没有这么大,加上机器配置高,扫描整个表也花不了多久时间。凌晨五六点执行,没有对线上的服务形成影响。
任务失败是很正常的,由于还依赖一些其余数据,其余数据提供的时间不肯定,因此任务会一直跑直到成功。
复盘一下整个过程,对于这个查询超时SQL问题的排查,我从索引、网络、备份、业务竞争MySQL资源等方面一一分析,却忽略了最重要的因素——执行的究竟是哪一条SQL。
我想固然的认为执行的SQL就是我想象中的那样并对此深信不疑,后面的努力也就成了徒劳。
这本是一个简单的问题,我却把他复杂化了,这是不该该的。
这是一个典型的例子,业务量不大的时候埋下的坑,业务发展迅速的时候就暴露了,万幸的是,没有影响到核心交易系统,若是是核心交易系统的话,可能就会致使一次P0的事故。
虽然这个代码不是我写的,但我从中获得的教训就是对线上环境要有敬畏之心,对依赖数据要有怀疑之心,对问题排查要有客观之心。
线上的环境极其复杂,有着各自版本迁移和业务变动遗留下来的数据,这些状况开发人员是没法所有考虑到的,测试也很难覆盖测试,带着主观的想法去写代码很容易致使BUG,有些BUG在业务量还不大的时候不容易引发重视,但随着业务的发展,这些欠下的债终究要还。
你能够保证你写的逻辑没有问题,可是你不能保证服务上游提供的数据都符合预期。多想一下若是上游数据异常,本身写的服务会不会出问题,多加一些数据校验和报警会省去不少没必要要的麻烦。
排查问题的时候,必定要客观,不要带着主观感觉。原本就是由于主观而致使的BUG,你还想固然的代入去查找问题,这固然会加大排查问题的难度。