因为一条SQL,我差点被祭天……

沙海 2021年4月12日12:37:42杂谈 Java评论45字数 3406阅读11分21秒阅读模式
摘要

速读摘要

速读摘要文章源自JAVA秀-https://www.javaxiu.com/11073.html

这让我倒吸了一口凉气,因为我们组做的系统很多都用的是同一个数据库服务器,日用户活跃量有好几十万,如果服务器崩溃了将会使所有的系统服务都不可用。看了这条语句,我又倒吸一口凉气,这不就是我写的系统调用的SQL语句吗?但是如果你的查询条件只有age的话,那么索引就不会生效,因为没有匹配最左边的字段,后面所有的索引字段都不会生效。但如果查询条件里面只有b的语句,是无法使用(a,b)这个联合索引的,这时候你不得不维护另外一个索引,也就是说你需要同时维护(a,b)、(b)这两个索引。文章源自JAVA秀-https://www.javaxiu.com/11073.html

原文约 2779 | 图片 6 | 建议阅读 6 分钟 | 评价反馈文章源自JAVA秀-https://www.javaxiu.com/11073.html

因为一条SQL,我差点被祭天......

程序IT圈 文章源自JAVA秀-https://www.javaxiu.com/11073.html

作者:很懒的程序员文章源自JAVA秀-https://www.javaxiu.com/11073.html

上周四午休时分,我正在工位上小憩,睡梦中仿佛看到了自己拿着李白在荣耀峡谷里大杀四方的情景,就在我刚拿完五杀准备带领队友推对面水晶的时候,一句慌乱急促的“糟了”把我从睡梦中惊醒......文章源自JAVA秀-https://www.javaxiu.com/11073.html

反常的 SQL 语句

我眯开朦胧的双眼,才发现刚才的发声来源于我的组长庄哥,看到他在紧张的点开日志系统查看日志,我预感到有什么不妙的事情发生。文章源自JAVA秀-https://www.javaxiu.com/11073.html

仔细一问才知道,原来就在我眯眼的期间,线上数据库服务器的 CPU 被打满,同时触发了生产数据库只读延迟的限定时间并且发出告警,而且告警的过程持续了半个小时。文章源自JAVA秀-https://www.javaxiu.com/11073.html

这让我倒吸了一口凉气,因为我们组做的系统很多都用的是同一个数据库服务器,日用户活跃量有好几十万,如果服务器崩溃了将会使所有的系统服务都不可用。文章源自JAVA秀-https://www.javaxiu.com/11073.html

于是我们赶紧通过 SQL 日志进行问题查找,最后排查出来是因为一张 SQL 的高量查询没有走索引导致。文章源自JAVA秀-https://www.javaxiu.com/11073.html

日志列表显示,这条 SQL 语句的扫描行数达到了上百万,基本就是全表扫描的情况,而且半个小时的时间查询了达上万次,每条 SQL 查询的耗时都在 3000ms 以上。文章源自JAVA秀-https://www.javaxiu.com/11073.html

我的天啊,难怪服务器会 CPU 打满,这么一条耗时的 SQL 语句查询量这么大,数据库的资源当然是直接就崩溃了。文章源自JAVA秀-https://www.javaxiu.com/11073.html

这是当时那条 SQL 的查询情况:文章源自JAVA秀-https://www.javaxiu.com/11073.html

因为一条SQL,我差点被祭天……文章源自JAVA秀-https://www.javaxiu.com/11073.html

临时处理

看了这条语句,我又倒吸一口凉气,这不就是我写的系统调用的 SQL 语句吗?完了,这回逃不掉了,真是人在睡梦里,锅从天上来。文章源自JAVA秀-https://www.javaxiu.com/11073.html

因为一条SQL,我差点被祭天……文章源自JAVA秀-https://www.javaxiu.com/11073.html

当然,因为是我自己写的 SQL,所以我一看就知道这条语句是有问题的。文章源自JAVA秀-https://www.javaxiu.com/11073.html

根据我的代码处理,这条 SQL 的调用还少了个重要的参数 user_fruit_id,这个参数没有传的话是不应该走这条 SQL 查询的。文章源自JAVA秀-https://www.javaxiu.com/11073.html

在我的设计里,该参数是数据表里一个联合索引的最左侧字段,如果该字段没有传值的话,那么索引就不会生效了。文章源自JAVA秀-https://www.javaxiu.com/11073.html

KEY `idx_userfruitid_type` (`user_fruit_id`,`task_type`,`receive_start_time`,`receive_end_time`) USING BTREE
文章源自JAVA秀-https://www.javaxiu.com/11073.html

虽然定位到了 SQL 语句,但是线上的问题刻不容缓,总不可能找出 Bug 改完再上线吧。文章源自JAVA秀-https://www.javaxiu.com/11073.html

所以,我们只能做了一个临时处理,就是在原来的表上多加了一个联合索引,其实就是去掉了 user_fruit_id 字段,让这些高量的查询都能走新的索引。文章源自JAVA秀-https://www.javaxiu.com/11073.html

就像下面这样:文章源自JAVA秀-https://www.javaxiu.com/11073.html

KEY `idx_task_type_receive_start_time` (`task_type`,`receive_start_time`,`receive_end_time`,`created_time`) USING BTREE
文章源自JAVA秀-https://www.javaxiu.com/11073.html

加上索引后,SQL 的扫描行数就大幅度的降低了,重启实例后就又能正常运行了。文章源自JAVA秀-https://www.javaxiu.com/11073.html

最左匹配原则

那么为什么最左侧的字段没传索引就不生效了,这是因为 MySQL 的联合索引是基于“最左匹配原则”匹配的。文章源自JAVA秀-https://www.javaxiu.com/11073.html

我们都知道,索引的底层是 B+ 树结构,联合索引的结构也是 B+ 树,只不过键值数量不是一个,而是多个,构建一颗 B+ 树只能根据一个值来构建,因此数据库依据联合索引最左的字段来构建 B+ 树。文章源自JAVA秀-https://www.javaxiu.com/11073.html

例如我们用两个字段(name,age)这个联合索引来分析:文章源自JAVA秀-https://www.javaxiu.com/11073.html

因为一条SQL,我差点被祭天……文章源自JAVA秀-https://www.javaxiu.com/11073.html

图片来源于林晓斌老师的《MySQL 实战 45 讲》课程文章源自JAVA秀-https://www.javaxiu.com/11073.html

当我们在 where 条件中查找 name 为“张三”的所有记录的时候,可以快速定位到 ID4,并且查出所有包含“张三”的记录。文章源自JAVA秀-https://www.javaxiu.com/11073.html

而如果要查找“张三,10”这一条特定的数据,就可以用 name = "张三" and age = 10 获取。文章源自JAVA秀-https://www.javaxiu.com/11073.html

因为联合索引的键值对是两个,所以只要前面的 name 确定的情况下就可以进一步定位到具体的 age 记录。文章源自JAVA秀-https://www.javaxiu.com/11073.html

但是如果你的查询条件只有 age 的话,那么索引就不会生效,因为没有匹配最左边的字段,后面所有的索引字段都不会生效。文章源自JAVA秀-https://www.javaxiu.com/11073.html

所以我之前写的 SQL 语句才会因为少了最左边的 user_fruit_id 字段而走了全表扫描的查询方式。文章源自JAVA秀-https://www.javaxiu.com/11073.html

正常来说,假设一个联合索引设计成(a,b)这样的结构的话,那么用 a and b 作为条件,或者 a 单独作为查询条件都会走索引,这种情况下我们就不要再为 a 字段单独设计索引了。文章源自JAVA秀-https://www.javaxiu.com/11073.html

但如果查询条件里面只有 b 的语句,是无法使用(a,b)这个联合索引的,这时候你不得不维护另外一个索引,也就是说你需要同时维护(a,b)、(b) 这两个索引。文章源自JAVA秀-https://www.javaxiu.com/11073.html

找出 Bug

虽然临时做了处理,但问题并不算解决,很明显是系统出现了 Bug 才会有走这样的查询条件。文章源自JAVA秀-https://www.javaxiu.com/11073.html

因为是我自己写的代码,所以知道是哪条 SQL 后我就马上定位到了代码里的具体方法,后来才发现是因为我对 user_fruit_id 字段的判空处理不生效所致。文章源自JAVA秀-https://www.javaxiu.com/11073.html

因为该字段是从调用方传过来的,所以我在方法参数里对该字段做了非空限制的注解,也就是 javax 包下的 @NotNull:文章源自JAVA秀-https://www.javaxiu.com/11073.html

public class GardenUserTaskListReq implements Serializable {    private static final long serialVersionUID = -9161295541482297498L;    @ApiModelProperty(notes = "水果id")    @NotNull(message = "水果id不能为空")    private Long userFruitId;    /**以下省略*/    .....................}
文章源自JAVA秀-https://www.javaxiu.com/11073.html

虽然加上该注解来做非空校验,但我却没有在参数加上另一个注解 @Validated。文章源自JAVA秀-https://www.javaxiu.com/11073.html

该注解如果没加上的话,那么调用 javax 包下的校验规则就都不生效,正确的写法是在 controller 层方法的参数前面加上注解:文章源自JAVA秀-https://www.javaxiu.com/11073.html

因为一条SQL,我差点被祭天……文章源自JAVA秀-https://www.javaxiu.com/11073.html

除此之外,因为 user_fruit_id 这个字段是另一张表的主键,我在代码里也没有对这张表是否存在这个 id 做查询判断。文章源自JAVA秀-https://www.javaxiu.com/11073.html

这样一来,无论调用方传什么值过来都会直接触发 SQL 查询,并且在不跑索引的情况下直接走全表扫描。文章源自JAVA秀-https://www.javaxiu.com/11073.html

因为一条SQL,我差点被祭天……文章源自JAVA秀-https://www.javaxiu.com/11073.html

不得不说,这真是个低级错误,说真的,我对这个原因真是感到嘀笑皆非,再怎么说也工作几年了,怎么还犯一些新手级别的错误呢,这脸打得真是让我相当惭愧。文章源自JAVA秀-https://www.javaxiu.com/11073.html

总结

虽然是低级错误,但造成的后果也算挺严重了,这次事件也让我更加的警醒,在以后的开发工作中必须要遵守该有的原则,大概有这么几点:文章源自JAVA秀-https://www.javaxiu.com/11073.html

①不能相信调用端。重要的参数都要先做验证,即使是非空值也需要做验证,不符合条件的就要直接返回或抛异常,不能参与业务 SQL 的查询,否则频繁的访问也会对服务造成负担。文章源自JAVA秀-https://www.javaxiu.com/11073.html

②SQL 语句要先做性能查询。对于数据量大的表,建好索引后,所有的 SQL 查询语句要用 explain 检测性能,并且根据结果来进一步优化索引。文章源自JAVA秀-https://www.javaxiu.com/11073.html

③代码必须要 Review。之前我没有放太大的精力在代码的 Review 上,虽说跟迭代排期的紧凑也有关系,但不管怎么说,Bug 确实是我的疏忽造成的,尤其是像空值这种细小的错误在 Java 里可以说家常便饭。文章源自JAVA秀-https://www.javaxiu.com/11073.html

千里之堤毁于蚁穴,有时一个小 Bug 很容易就引发整个系统的崩盘,这一次的问题也让我更加深刻的认识到了 Review 代码的重要性,不管业务开发的工作量有多麻烦,这一步操作绝对不能忽视。文章源自JAVA秀-https://www.javaxiu.com/11073.html

文章源自JAVA秀-https://www.javaxiu.com/11073.html

—————END—————推荐阅读:SpringBoot+vue.js搭建图书管理系统i++ 是线程安全的吗?IDEA 卡成球了 !咋优化 ?最美的Vue+Element开源后台管理系统基于SpringBoot的迷你商城系统,附源码!IntelliJ IDEA 2020.2.4款 神级超级牛逼插件推荐最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:关注公众号并回复 java 领取,更多内容陆续奉上。
文章源自JAVA秀-https://www.javaxiu.com/11073.html

明天见(。・ω・。)ノ♡文章源自JAVA秀-https://www.javaxiu.com/11073.html

继续阅读
速蛙云 - 极致体验,强烈推荐!!!购买套餐就免费送各大视频网站会员!快速稳定、独家福利社、流媒体稳定解锁!速度快,全球上网、视频、游戏加速、独立IP均支持!基础套餐性价比很高!这里不多说,我一直正在使用,推荐购买:https://www.javaxiu.com/59919.html
weinxin
资源分享QQ群
本站是JAVA秀团队的技术分享社区, 会经常分享资源和教程; 分享的时代, 请别再沉默!
沙海
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定