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


上周四午休时分 , 我正在工位上小憩 , 睡梦中仿佛看到了自己拿着李白在荣耀峡谷里大杀四方的情景 , 就在我刚拿完五杀准备带领队友推对面水晶的时候 , 一句慌乱急促的“糟了”把我从睡梦中惊醒......
因为一条SQL,我差点被祭天......
本文插图
图片来自 Pexels
反常的 SQL 语句
我眯开朦胧的双眼 , 才发现刚才的发声来源于我的组长庄哥 , 看到他在紧张的点开日志系统查看日志 , 我预感到有什么不妙的事情发生 。
仔细一问才知道 , 原来就在我眯眼的期间 , 线上数据库服务器的 CPU 被打满 , 同时触发了生产数据库只读延迟的限定时间并且发出告警 , 而且告警的过程持续了半个小时 。
这让我倒吸了一口凉气 , 因为我们组做的系统很多都用的是同一个数据库服务器 , 日用户活跃量有好几十万 , 如果服务器崩溃了将会使所有的系统服务都不可用 。
于是我们赶紧通过 SQL 日志进行问题查找 , 最后排查出来是因为一张 SQL 的高量查询没有走索引导致 。
日志列表显示 , 这条 SQL 语句的扫描行数达到了上百万 , 基本就是全表扫描的情况 , 而且半个小时的时间查询了达上万次 , 每条 SQL 查询的耗时都在 3000ms 以上 。
我的天啊 , 难怪服务器会 CPU 打满 , 这么一条耗时的 SQL 语句查询量这么大 , 数据库的资源当然是直接就崩溃了 。
这是当时那条 SQL 的查询情况:
因为一条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)这个联合索引来分析:
因为一条SQL,我差点被祭天......
本文插图
图片来源于林晓斌老师的《MySQL 实战 45 讲》课程
【因为一条SQL,我差点被祭天......】当我们在 where 条件中查找 name 为“张三”的所有记录的时候 , 可以快速定位到 ID4 , 并且查出所有包含“张三”的记录 。