太可怕了!差点因为一条SQL被拖出去祭天……

2023-12-14 14:50

本文主要是介绍太可怕了!差点因为一条SQL被拖出去祭天……,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

点击上方 "程序员小乐"关注, 星标或置顶一起成长

后台回复“大礼包”有惊喜礼包!

每日英文

You can’t make the same mistake twice. The second time you make it, it’s no longer a mistake. It’s a choice. 

一个人不可能犯同样的错误两次。第二次还这样做,已经不再是犯错,那是你的选择。

每日掏心话

经过风雨沧桑,岁月的风霜洗净前尘往事,记忆的点点滴滴里早已遗忘了所有的忧伤愁闷,洒落的是一地淡淡的馨香。

来自:鄙人薛某 | 责编:乐乐

链接:cnblogs.com/yeya/p/13200655.html

程序员小乐(ID:study_tech)第 1099 次推文

往日回顾:华为员工违规领夜宵被发现后,被罚冻薪降考评,终身不得领夜宵,HR:哪个员工上班健身,定性考勤造假

     

   正文   

# 反常的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 查询,并且在不跑索引的情况下直接走全表扫描。不得不说,这真是个低级错误,说真的,我对这个原因真是感到啼笑皆非,再怎么说也工作几年了,怎么还犯一些新手级别的错误呢,这脸打得真是让我相当惭愧。# 总结虽然是低级错误,但造成的后果也算挺严重了,这次事件也让我更加的警醒,在以后的开发工作中必须要遵守该有的原则,大概有这么几点:①不能相信调用端。重要的参数都要先做验证,即使是非空值也需要做验证,不符合条件的就要直接返回或抛异常,不能参与业务 SQL 的查询,否则频繁的访问也会对服务造成负担。②SQL 语句要先做性能查询。对于数据量大的表,建好索引后,所有的 SQL 查询语句要用 explain 检测性能,并且根据结果来进一步优化索引。③代码必须要 Review。之前我没有放太大的精力在代码的 Review 上,虽说跟迭代排期的紧凑也有关系,但不管怎么说,Bug 确实是我的疏忽造成的,尤其是像空值这种细小的错误在 Java 里可以说家常便饭。千里之堤毁于蚁穴,有时一个小 Bug 很容易就引发整个系统的崩盘,这一次的问题也让我更加深刻的认识到了 Review 代码的重要性,不管业务开发的工作量有多麻烦,这一步操作绝对不能忽视。
码字不易,如果你觉得读完以后有收获,不妨点个在看让更多的人看到吧~

PS:欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。

版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢!

欢迎加入后端架构师交流群,在后台回复“学习”即可。

猜你还想看

阿里、腾讯、百度、华为、京东最新面试题汇集

8年开发,连登录接口都写这么烂...

新来的妹纸 rm -rf 把公司整个数据库删没了!!!

B站,被扫黄了

BAT等大厂Java面试经验总结

别找了,想获取 Java大厂面试题学习资料

扫下方二维码回复「手册」就好了

嘿,你在看吗

这篇关于太可怕了!差点因为一条SQL被拖出去祭天……的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/492956

相关文章

MySQL数据库双机热备的配置方法详解

《MySQL数据库双机热备的配置方法详解》在企业级应用中,数据库的高可用性和数据的安全性是至关重要的,MySQL作为最流行的开源关系型数据库管理系统之一,提供了多种方式来实现高可用性,其中双机热备(M... 目录1. 环境准备1.1 安装mysql1.2 配置MySQL1.2.1 主服务器配置1.2.2 从

深入理解Mysql OnlineDDL的算法

《深入理解MysqlOnlineDDL的算法》本文主要介绍了讲解MysqlOnlineDDL的算法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小... 目录一、Online DDL 是什么?二、Online DDL 的三种主要算法2.1COPY(复制法)

mysql8.0.43使用InnoDB Cluster配置主从复制

《mysql8.0.43使用InnoDBCluster配置主从复制》本文主要介绍了mysql8.0.43使用InnoDBCluster配置主从复制,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录1、配置Hosts解析(所有服务器都要执行)2、安装mysql shell(所有服务器都要执行)3、

k8s中实现mysql主备过程详解

《k8s中实现mysql主备过程详解》文章讲解了在K8s中使用StatefulSet部署MySQL主备架构,包含NFS安装、storageClass配置、MySQL部署及同步检查步骤,确保主备数据一致... 目录一、k8s中实现mysql主备1.1 环境信息1.2 部署nfs-provisioner1.2.

MySQL中VARCHAR和TEXT的区别小结

《MySQL中VARCHAR和TEXT的区别小结》MySQL中VARCHAR和TEXT用于存储字符串,VARCHAR可变长度存储在行内,适合短文本;TEXT存储在溢出页,适合大文本,下面就来具体的了解... 目录一、VARCHAR 和 TEXT 基本介绍1. VARCHAR2. TEXT二、VARCHAR

MySQL中C接口的实现

《MySQL中C接口的实现》本节内容介绍使用C/C++访问数据库,包括对数据库的增删查改操作,主要是学习一些接口的调用,具有一定的参考价值,感兴趣的可以了解一下... 目录准备mysql库使用mysql库编译文件官方API文档对象的创建和关闭链接数据库下达sql指令select语句前言:本节内容介绍使用C/

mybatis直接执行完整sql及踩坑解决

《mybatis直接执行完整sql及踩坑解决》MyBatis可通过select标签执行动态SQL,DQL用ListLinkedHashMap接收结果,DML用int处理,注意防御SQL注入,优先使用#... 目录myBATiFBNZQs直接执行完整sql及踩坑select语句采用count、insert、u

MySQL之搜索引擎使用解读

《MySQL之搜索引擎使用解读》MySQL存储引擎是数据存储和管理的核心组件,不同引擎(如InnoDB、MyISAM)采用不同机制,InnoDB支持事务与行锁,适合高并发场景;MyISAM不支持事务,... 目录mysql的存储引擎是什么MySQL存储引擎的功能MySQL的存储引擎的分类查看存储引擎1.命令

一文详解MySQL索引(六张图彻底搞懂)

《一文详解MySQL索引(六张图彻底搞懂)》MySQL索引的建立对于MySQL的高效运行是很重要的,索引可以大大提高MySQL的检索速度,:本文主要介绍MySQL索引的相关资料,文中通过代码介绍的... 目录一、什么是索引?为什么需要索引?二、索引该用哪种数据结构?1. 哈希表2. 跳表3. 二叉排序树4.

MySQL批量替换数据库字符集的实用方法(附详细代码)

《MySQL批量替换数据库字符集的实用方法(附详细代码)》当需要修改数据库编码和字符集时,通常需要对其下属的所有表及表中所有字段进行修改,下面:本文主要介绍MySQL批量替换数据库字符集的实用方法... 目录前言为什么要批量修改字符集?整体脚本脚本逻辑解析1. 设置目标参数2. 生成修改表默认字符集的语句3