准线上事故之MySQL优化器索引选错

1 背景最近组里来了许多新的小伙伴,大家在一起聊聊技术 , 有小兄弟提到了MySQL的优化器的内部策略,想起了之前在公司出现的一个线上问题,今天借着这个机会 , 在这里分享下过程和结论 。排查的过程中,也是学习的过程 , 下面把排查的过程和分析记录下来 , 以供大家参考 。
2 过程和分析2.1 问题发现20年的某个下午,突然收到大量慢查询的告警 , 同时业务运营在群里反馈红包相关页面加载慢,怀疑系统出问题了,问题发到群里之后,经过日志定位和代码review多重确认,有一条sql成了重点怀疑对象,最终确定的原因是MySQL查询过程中,优化器没有选择最优的索引导致的 。

准线上事故之MySQL优化器索引选错

文章插图
图片
需要说明的是,这里使用的MySQL版本是5.7版本 。存储引擎是默认的InnoDB
2.2 问题定位涉及到的表如下:
准线上事故之MySQL优化器索引选错

文章插图
图片
问题sql如下:
select `bonus_id`, `bonus_code`, `over_at`, `status`, `bonus_num`, `product_id` from `zz_test_table` where `user_id` = '16723149' and `over_at` > '2020-11-25 20:45:41' and `status` = 0 and `bonus_id` in ('382364983', '486697270', '486834963') order by `over_at` asc limit 1;该sql就涉及一张表zz_test_table(真实表名已被隐藏),表里面有两个索引,一个是over_at字段对应的idx_over_at索引,另一个就是bonus_id字段对应的主键索引 。
可以看到 , sql其实并不复杂,但是执行结果竟然耗费3秒以上,对于一个面向App用户的接口,3秒以上的响应简直无法接受,如果对业务影响严重点的话,甚至于都需要写事故报告了 。
果断祭出explAIn大法 先来看看原始的查询情况,如下图:
准线上事故之MySQL优化器索引选错

文章插图
图片
可以看到mysql并没有命中主键索引,而是命中的idx_over_at索引,预估行数为41314647行,这里大家就不要纠结了,为什么这么大的表,历史原因了,后面已经优化掉了 。
MySQL官方文档中有描述,我们可以直接强制指定优化器使用我们指定的索引 。
准线上事故之MySQL优化器索引选错

文章插图
图片
强制指定使用主键索引试试
准线上事故之MySQL优化器索引选错

文章插图
图片
发现使用强制索引之后 , sql执行0.103秒就返回了 。
问题定位到这里,好像已经比较清楚了,就是MySQL优化器没有正确选择索引导致的呗 。
MySQL:我可不背这个锅 , 你们自己好好反省下 。
MySQL说的有道理 , 为啥好端端的线上会出现3秒的慢查询呢,这个情况之前为什么没有呢,我们先不管人家MySQL优化器的问题,先来分析下,为什么走了idx_over_at索引之后,3秒都没返回数据呢?
那么idx_over_at索引本身是不是有问题呢?,果然,经过排查 , 是因为有个小兄弟上线的代码有bug,over_at字段被大量写成同一个值,导致我们原本比较均匀的over_at字段存在了大量重复值,索引检索行数指数上升,已经基本类似全表扫描 。
还了MySQL清白之后,我们继续来定位下,为什么优化器不使用更高效率的主键索引呢?在这个过程中,我们又发现一些奇怪的现象 。
2.3 问题延伸奇怪现象一:
准线上事故之MySQL优化器索引选错

文章插图
图片
惊奇的事情发生了,limit 由1 变更为3之后,走了主键索引 。
奇怪现象二:
准线上事故之MySQL优化器索引选错

文章插图
图片
惊奇的事情又发生了,order by 把主键ID加上之后,也走了主键索引 。
奇怪现象三:
准线上事故之MySQL优化器索引选错

文章插图
图片
惊奇的事情继续发生 , 套了一层子查询,也走了主键索引 。
2.4 问题分析MySQL:是不是很懵逼 , 如果碰到此类情况,请问阁下应该如何应对?
得,超出理解范畴了 , 没办法去翻文档吧 。MySql5.7官方文档
准线上事故之MySQL优化器索引选错

文章插图
图片
相对来说,官方的文档关于优化器的说明较为分散,想要快速上手的小伙伴,可以考虑观看阿里云藏经阁出版的深入MySQL实战一书 。


推荐阅读