年底的线上故障,踩坑了最常见的一种索引失效场景

快过年,我的线上发布出现故障

"五哥,你在上线吗?",旁边有一个声音传来。

"啊,怎么了?"。真是要命,在上线发布时候,我最讨厌别人叫我的名字 。我慌忙站起来,看向身后,原来是 建哥在问我。我慌忙的问,怎么回事。

"DBA 刚才在群里说,Task数据库 cpu 负载增加!有大量慢查询",建哥来我身边,跟我说。

慢慢的,我身边聚集着越来越多的人

"你在上线Task服务吗?改动什么内容了,看看要不要立即回滚?"旁边传来声音。此时,我的心开始怦怦乱跳,手心发痒,紧张不已。

我检查着线上机器的日志,试图证明报警的原因不是出在我这里。

我对着电脑,微微颤抖地回答大家:"我只是升级了基础架构的Jar包,其他内容没有改动啊。"此时我已分不清是谁在跟我说话,只能对着电脑作答......

这时DBA在群里发送了一条SQL,他说这条SQL导致了大量的慢查询。

我突然记起来了,我转过头问林哥:"林哥,你上线了什么内容?"这次林哥有代码的变更,跟我一起上线。我觉得可能是他那边有问题。

果然,林哥看着代码发呆。他嘟囔道:"我添加了索引啊,怎么会有慢查询呢?"原来慢查询的SQL是林哥刚刚添加的,这一刻我心里的石头放下了,问题并不在我,我轻松了许多。

"那我先回滚吧",幸好我们刚发布了一半,现在回滚还来得及,我尝试回滚机器。此刻我的紧张情绪稍稍平静下来,手也不再发抖。

既然不是我的问题,我可以以吃瓜的心态,暗中观察事态的发展。我心想:真是吓死我了,幸好不是我的错。

然而我也有一些小抱怨:为什么非要和我一起搭车上线,出了事故,还得把我拖进来。

故障发生前的半小时

2年前除夕前的一周,我正准备着过年前的最后一次线上发布,这时候我刚入职两个月,自然而然会被分配一些简单的小活。这次上线的内容是将基础架构的Jar包升级到新版本。一般情况下,这种配套升级工作不会出问题,只需要按部就班上线就行。

"五哥,你是要上线 Task服务吗?",工位旁的林哥问我,当时我正做着上线前的准备工作。

"对啊,马上要发布,怎么了?",我转身回复他。

"我这有一个代码变更,跟你搭车一起上线吧,改动内容不太多。已经测试验证过了",林哥说着,把代码变更内容发给我,简单和我说了下代码变更的内容。我看着改动内容确实不太多,新增了一个SQL查询,于是便答应下来。我重新打包,准备发布上线。

半小时以后,便出现了文章开头的情景。新增加的SQL 导致大量慢查询,数据库险些被打挂。

为什么加了索引,还会出现慢查询呢?

"加了索引,为什么还有慢查询?",这是大家共同的疑问。

事后分析故障的原因,通过 mysql explain 命令,查看该SQL 确实没有命中索引,从而导致慢查询。

这个SQL 大概长这个样子!我去掉了业务相关的部分。

select * from order_discount_detail where orderId = 1123;

order_discount_detailorderId 这一列上确实加了索引,不应该出现慢查询,乍一看,没有什么问题。我本能的想到了索引失效的几种场景。难道是类型不匹配,导致索引失效?

果不其然, orderId 在数据库中的类型 是 varchar 类型,而传参是按照 long 类型传的。

复习一下: 类型转换导致索引失效

类型转换导致索引失效,是很容易犯的错误

因为在某些特殊场景下要对接外部订单,存在订单Id为字符串的情况,所以 orderId被设计成 varchar 字符串类型。然而出问题的场景比较明确,订单id 就是long类型,不可能是字符串类型。

所以林哥,他在使用Mybatis 时,直接使用 long 类型的 orderId字段传参,并且没有意识到两者数据类型不对。

因为测试环境数据量比较小,即使没有命中索引,也不会有很严重的慢查询,并且测试环境请求量比较低,该慢查询SQL 执行次数较少,所以对数据库压力不大,测试阶段一直没有发现性能问题。

直到代码发布到线上环境------------数据量和访问量都非常高的环境,差点把数据库打挂。

mybatis 能避免 "类型转换导致索引失效" 的问题吗?

mybatis能自动识别数据库和Java类型不一致的情况吗?如果发现java类型和数据库类型不一致,自动把java 类型转换为数据库类型,就能避免索引失效的情况!

答案是不能。我没找到 mybatis 有这个能力。

mybatis 使用 #{} 占位符,会自动根据 参数的 Java 类型填充到 SQL中,同时可以避免SQL注入问题。

例如刚才的SQL 在 mybatis中这样写。

select * from order_discount_detail where orderId = #{orderId};

orderId 是 String 类型,SQL就变为

select * from order_discount_detail where orderId = '1123';

mybatis 完全根据 传参的java类型,构建SQL,所以不要认为 mybatis帮你处理好java和数据库的类型差异问题,你需要自己关注这个问题!

再次提醒,"类型转换导致索引失效"的问题,非常容易踩坑。并且很难在测试环境发现性能问题,等到线上再发现问题就晚了,大家一定要小心!小心!

险些背锅

可能有朋友疑问,为什么发布一半时出现慢查询,单机发布阶段不能发现这个问题吗?

之所以没发现这个问题,是因为 新增SQL在 Kafka消费逻辑中,由于单机发布机器启动时没有争抢到 kafka 分片,所以没有走到新代码逻辑。

此外也没有遵循降级上线的代码规范,如果上线默认是降级状态,上线过程中就不会有问题。放量阶段可以通过降级开关快速止损,避免回滚机器过程缓慢而导致的长时间故障。

不是我的问题,为什么我也背了锅

因为我在发布阶段没有遵循规范,按照规定的流程应该在单机发布完成后进行引流压测。引流压测是指修改机器的Rpc权重,将Rpc请求集中到新发布的单机上,这样就能提前发现线上问题。

然而由于我偷懒,跳过了单机引流压测。由于发布的第一台机器没有抢占到Kafka分片,因此无法执行新代码逻辑。即使进行了单机引流压测,也无法提前发现故障。虽然如此,但我确实没有遵循发布规范,错在我。

如果上线时没有出现故障,这种不规范的上线流程可能不会受到责备。但如果出现问题,那只能怪我倒霉。在复盘过程中,我的领导抓住了这件事,给予了重点批评。作为刚入职的新人,被指责确实让我感到不舒服。

快要过年了,就因为搭车上线,自己也要承担别人犯错的后果,让我很难受。但是自己确实也有错,当时我的心情复杂而沉重。

两年前的事了,说出来让大家吃个瓜,乐呵一下。如果这瓜还行,东东发财的小手点个赞

相关推荐
Adolf_19932 分钟前
Flask-JWT-Extended登录验证, 不用自定义
后端·python·flask
叫我:松哥14 分钟前
基于Python flask的医院管理学院,医生能够增加/删除/修改/删除病人的数据信息,有可视化分析
javascript·后端·python·mysql·信息可视化·flask·bootstrap
海里真的有鱼17 分钟前
Spring Boot 项目中整合 RabbitMQ,使用死信队列(Dead Letter Exchange, DLX)实现延迟队列功能
开发语言·后端·rabbitmq
工业甲酰苯胺28 分钟前
Spring Boot 整合 MyBatis 的详细步骤(两种方式)
spring boot·后端·mybatis
新知图书1 小时前
Rust编程的作用域与所有权
开发语言·后端·rust
wn5312 小时前
【Go - 类型断言】
服务器·开发语言·后端·golang
希冀1232 小时前
【操作系统】1.2操作系统的发展与分类
后端
GoppViper3 小时前
golang学习笔记29——golang 中如何将 GitHub 最新提交的版本设置为 v1.0.0
笔记·git·后端·学习·golang·github·源代码管理
爱上语文4 小时前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
serve the people4 小时前
springboot 单独新建一个文件实时写数据,当文件大于100M时按照日期时间做文件名进行归档
java·spring boot·后端