因为我的一个低级错误,生产数据库崩溃了将近半个小时
前言
halo,相信大家一定过了一个很开心的端午节吧,我看朋友圈里各种晒旅游,晒美食的,真是羡慕啊,不像我,感冒了只能在家撸文章。
当然,玩的多开心,节后上班就有多郁闷,假日综合征可不是说说而已。对此我想表达的是,没事,不用郁闷,来看我如何自爆家丑来让你们开心下。
反常的sql语句
上周四午休时分,我正在工位上小憩,睡梦中仿佛看到了自己拿着李白在荣耀峡谷里大杀四方的情景,就在我刚拿完五杀准备带领队友推对面水晶的时候,一句慌乱急促的“糟了”把我从睡梦中惊醒。我眯开朦胧的双眼,才发现刚才的发声来源于我的组长庄哥,看到他在紧张的点开日志系统查看日志,我预感到有什么不妙的事情发生,仔细一问才知道,原来就在我眯眼的期间,线上数据库服务器的CPU被打满,同时触发了生产数据库只读延迟的限定时间并且发出告警,而且告警的过程持续了半个小时。
这让我倒吸了一口凉气,因为我们组做的系统很多都用的是同一个数据库服务器,日用户活跃量有好几十万,如果服务器崩溃了将会使所有的系统服务都不可用,于是我们赶紧通过sql日志进行问题查找,最后排查出来是因为一张sql的高量查询没有走索引导致,日志列表显示,这条sql语句的扫描行数达到了上百万,基本就是全表扫描的情况,而且半个小时的时间查询了达上万次,每条sql查询的耗时都在3000ms以上。我的天啊,难怪服务器会CPU打满,这么一条耗时的sql语句查询量这么大,数据库的资源当然是直接就崩溃了,这是当时那条sql的查询情况:
临时处理
看了这条语句,我又倒吸一口凉气,这不就是我写的系统调用的sql语句吗?完了,这回逃不掉了,真是人在睡梦里,锅从天上来。
当然,因为是我自己写的sql,所以我一看就知道这条语句是有问题的。
根据我的代码处理,这条sql的调用还少了个重要的参数user_fruit_id
,这个参数没有传的话是不应该走这条sql查询的,在我的设计里,该参数是数据表里一个联合索引的最左侧字段,如果该字段没有传值的话,那么索引就不会生效了。
KEY `idx_userfruitid_type` (`user_fruit_id`,`task_type`,`receive_start_time`,`receive_end_time`) USING BTREE
虽然定位到了sql语句,但是线上的问题刻不容缓,总不可能找出bug改完再上线吧,所以,我们只能做了一个临时处理,就是在原来的表上多加了一个联合索引,其实就是去掉了user_fruit_id
字段,让这些高量的查询都能走新的索引,就像下面这样
KEY `idx_task_type_receive_start_time` (`task_type`,`receive_start_time`,`receive_end_time`,`created_time`) USING BTREE
加上索引后,sql的扫描行数就大幅度的降低了,重启实例后就又能正常运行了。
最左匹配原则
那么为什么最左侧的字段没传索引就不生效了,这是因为MySQL的联合索引是基于“最左匹配原则”匹配的。
我们都知道,索引的底层是B+树结构,联合索引的结构也是B+树,只不过键值数量不是一个,而是多个,构建一颗B+树只能根据一个值来构建,因此数据库依据联合索引最左的字段来构建B+树。
例如我们用两个字段(name,age)这个联合索引来分析,
图片来源于林晓斌老师的《MySQL实战45讲》课程,
当我们在where条件中查找name为“张三”的所有记录的时候,可以快速定位到ID4,并且查出所有包含“张三”的记录,而如果要查找“张三,10”这一条特定的数据,就可以用 name = “张三” and age = 10 获取,因为联合索引的键值对是两个,所以只要前面的name确定的情况下就可以进一步定位到具体的age记录,但是如果你的查询条件只有age的话,那么索引就不会生效,因为没有匹配最左边的字段,后面所有的索引字段都不会生效,所以我之前写的sql语句才会因为少了最左边的user_fruit_id
字段而走了全表扫描的查询方式。
正常来说,假设一个联合索引设计成(a,b)这样的结构的话,那么用a and b作为条件,或者a单独作为查询条件都会走索引,这种情况下我们就不要再为a字段单独设计索引了。
但如果查询条件里面只有b的语句,是无法使用(a,b)这个联合索引的,这时候你不得不维护另外一个索引,也就是说你需要同时维护(a,b)、(b) 这两个索引。
找出Bug
虽然临时做了处理,但问题并不算解决,很明显是系统出现了bug才会有走这样的查询条件。因为是我自己写的代码,所以知道是哪条sql后我就马上定位到了代码里的具体方法,后来才发现是因为我对user_fruit_id
字段的判空处理不生效所致。
因为该字段是从调用方传过来的,所以我在方法参数里对该字段做了非空限制的注解,也就是javax包下的@NotNull ,
public class GardenUserTaskListReq implements Serializable { private static final long serialVersionUID = -9161295541482297498L; @ApiModelProperty(notes = "水果id") @NotNull(message = "水果id不能为空") private Long userFruitId; /**以下省略*/ ..................... }
虽然加上该注解来做非空校验,但我却没有在参数加上另一个注解**@Validated**,该注解如果没加上的话,那么调用javax包下的校验规则就都不生效,正确的写法是在controller层方法的参数前面加上注解,
除此之外,因为user_fruit_id这个字段是另一张表的主键,我在代码里也没有对这张表是否存在这个id做查询判断,这样一来,无论调用方传什么值过来都会直接触发sql查询,并且在不跑索引的情况下直接走全表扫描。
不得不说,这真是个低级错误,说真的,我对这个原因真是感到嘀笑皆非,再怎么说也工作几年了,怎么还犯一些新手级别的错误呢,这脸打得真是让我相当惭愧。
总结
虽然是低级错误,但造成的后果也算挺严重了,这次事件也让我更加的警醒,在以后的开发工作中必须要遵守该有的原则,大概有这么几点:
1、不能相信调用端。重要的参数都要先做验证,即使是非空值也需要做验证,不符合条件的就要直接返回或抛异常,不能参与业务sql的查询,否则频繁的访问也会对服务造成负担。
2、sql语句要先做性能查询。对于数据量大的表,建好索引后,所有的sql查询语句要用explain检测性能,并且根据结果来进一步优化索引。
3、代码必须要review。之前我没有放太大的精力在代码的review上,虽说跟迭代排期的紧凑也有关系,但不管怎么说,bug确实是我的疏忽造成的,尤其是像空值这种细小的错误在Java里可以说家常便饭。千里之堤毁于蚁穴,有时一个小bug很容易就引发整个系统的崩盘,这一次的问题也让我更加深刻的认识到了review代码的重要性,不管业务开发的工作量有多麻烦,这一步操作绝对不能忽视。
后续
知道了bug的原因,改完代码当天就重新发布了,后来,庄哥告诉我说,为了以后让组里的其他人对此次问题有所警戒,让我写一篇问题记录总结一下,我想了一下,这不是我的强项啊,但怎么说也确实是自己的问题,还是老老实实的写一下记录好了。我本以为这样就可以松一口气了,可平哥 (组里的一位大佬) 却突然用诡异的眼神看着我,语重心长的说,上次xxx也因为线上出现问题写了报告,你这一次估计也不能例外了,可能要一万字以上。我瞬间就感觉一个雷劈到了我头上,苍天啊。。。。。。
看在我自曝家丑的份上,各位看官觉得满意的还请点个赞支持一下。
我是鄙人薛某,一个不拘于技术的互联网人,想看更多精彩文章可以关注我的公众号,里面不仅有技术,还有有趣的吹水文哦~~~
原创不易,看官们的 【三连】 将是我创作的最大动力,感谢各位的支持!
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
连mybits工作原理都不懂,你敢说你自己会java?
云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 一、相较于Hibernate,mybatis的优势在哪里? Hibernate缺点: 1、运行效率低,内存占用比较严重2、针对单一对象的增删改查,适合Hibernate,而Hibernate在批量操作时处于弱势3、虽然Hibernate引入一二级缓存、lazyload、查询缓存等更多优化空间(对于那些改动 不大且经常使用的数据,可将他们放入缓存中),但Hibernate对于持久层封装过于完 整,导致开发人员无法对sql进行优化,不适用于大型项目 mybatis优点: 1、代码量大大减少,开发效率高2、 MyBatis相当灵活,SQL写在XML里,从程序代码中彻底分离,降低耦合度,便于统 一管理和优化,并可重用3、运行效率高 二、mybatis原理 下面是mybatis的一个原理图,看懂这个图对理解mybatis工作原理很重要: 上面的原理图看的不是很清晰,下面再详细介绍一下mybatis的主要成员: 1、Configuration MyBatis所有的配置信息都保存在Configurat...
- 下一篇
云数据库进入本地数据中心,AWS混合云平台支持两大开源数据库
AWS宣布其混合云平台AWS Outposts现在支持其关系数据库服务RDS,客户在自己本地的数据中心即可创建RDS数据库实例。据悉,Outposts现在支持两种RDS数据库,分别是MySQL和PostgreSQL。 AWS Outposts是一项混合云服务,可将AWS基础设施、AWS服务、API和工具扩展到几乎任何的数据中心、主机托管空间或本地基础设施。AWS Outposts非常适合需要低延迟访问本地系统、本地数据处理或本地数据存储的工作负载。 近年来,AWS不断加大正在混合云方面的投入,与微软Azure Stack、Google Anthos等混合云服务的竞争。在2019年底,AWS发布了VMware Cloud on AWS Outposts提供在本地AWS Outposts基础设施上运行托管的VMware软件定义数据中心。 通过AWS Outposts,企业可以在本地运行Amazon EC2、Amazon EBS、容器服务Amazon EKS、分析服务Amazon EMR等。 现在,AWS Outposts对两大开源数据库的全面支持,进一步提升了企业的混合体验,在本地轻松扩展...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8编译安装MySQL8.0.19
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Docker安装Oracle12C,快速搭建Oracle学习环境