线上接口突然慢 5 倍?从慢查询日志、EXPLAIN 到索引修复,一次完整复盘
导读:某个列表接口稳定跑了半年,某天突然从 80ms 涨到 400ms,监控报警。没有上线新代码、没有数据暴涨,就是慢。最后靠慢查询日志 + EXPLAIN 定位到一个索引失效,改完恢复 70ms。这篇把整个排查过程复盘一遍。
先描述现场。零工系统的"岗位列表"接口,日常 P99 在 80ms 左右,某天监控显示突然涨到 400ms,持续不退。当天没有发版、没有大促、数据量也没暴增,这就很蹊跷——八成是执行计划变了。
排查的第一步不是看代码,是看慢查询日志。
第一步:开慢查询日志,用 mysqldumpslow 找凶手
生产库默认慢查询阈值是 10 秒,显然没开够。先确认现状,再调阈值:
-- 查看当前配置SHOWVARIABLESLIKE'slow_query_log%';SHOWVARIABLESLIKE'long_query_time';然后把阈值降到 0.5 秒,方便抓这种"不算太慢但明显劣化"的查询:
SETGLOBALslow_query_log=ON;SETGLOBALlong_query_time=0.5;SETGLOBALlog_queries_not_using_indexes=ON;跑个半小时后,用mysqldumpslow按平均耗时排序,直接看 TOP 语句:
mysqldumpslow-sat-t10/var/lib/mysql/mysql-slow.log输出里job_post的列表查询赫然在列,平均执行 0.4 秒、出现了 3000+ 次。这就是凶手。
第二步:EXPLAIN 看执行计划,问题一眼暴露
把慢 SQL 拿出来 EXPLAIN:
EXPLAINSELECTid,title,salary,city,create_timeFROMjob_postWHEREcity='赣州'ANDpost_status=1ORDERBYcreate_timeDESCLIMIT20;执行计划返回:
type: ALL -- 全表扫描,这是根源 key: NULL -- 没有用任何索引 rows: 235000 -- 扫了 23 万行全表扫描 + 23 万行,400ms 一点不冤。但奇怪的是,表上明明有(city, post_status)的联合索引,为什么没走?
第三步:定位索引失效,问题出在排序
继续深挖。把 ORDER BY 去掉再 EXPLAIN:
EXPLAINSELECTid,titleFROMjob_postWHEREcity='赣州'ANDpost_status=1;这次type: ref,走了索引。问题就出在ORDER BY create_time DESC上。
原来的联合索引是(city, post_status),排序字段create_time不在索引里。MySQL 有两种选择:先按索引过滤出结果,再临时表 filesort;或者干脆全表扫。数据量一大,优化器觉得全表扫 + 自己排反而更"划算",就走了ALL。
解法不是加一个(city, post_status, create_time)联合索引就完事——排序字段要放在等值条件的后面,而且 DESC 方向要匹配:
ALTERTABLEjob_postADDINDEXidx_city_status_time(city,post_status,create_timeDESC);MySQL 8 支持降序索引,8.0 之前的老版本加不了DESC,那就加普通(city, post_status, create_time),让排序走索引正序扫描,效果一样。
踩坑记录:索引加了,还是没走
问题现象:按上面方案加了联合索引,EXPLAIN 一看key还是 NULL,索引根本没被用上。
排查过程:检查发现慢 SQL 的 WHERE 里city是从接口参数拼进来的,类型是 VARCHAR,而表里city字段也是 VARCHAR,看起来没毛病。再细看,接口代码里对city做了String.trim(),但传参时某条链路把空字符串传成了 NULL,WHERE city = NULL永远不成立……等等,这不对。
定位思路:真正的坑是字符集排序规则不一致。job_post.city建表时是utf8mb4_general_ci,而另一个表 JOIN 出来的临时列是utf8mb4_0900_ai_ci,MySQL 做比较时字符集排序规则不同会导致索引失效,直接退化成全表。
最终解决:用SHOW FULL COLUMNS FROM job_post LIKE 'city'对比两边排序规则,统一改成utf8mb4_general_ci后,EXPLAIN 立刻type: ref+rows: 130。恢复 70ms,报警解除。
可直接复用的清单
- 接口劣化先看慢查询日志,
mysqldumpslow -s at按耗时排; - EXPLAIN 重点看三列:type(别是 ALL)、key(别是 NULL)、rows(别是十几万);
- 联合索引排序字段放等值条件之后,方向要匹配 ORDER BY;
- MySQL 8 用降序索引,老版本退而求其次用普通联合索引;
- WHERE 字段字符集排序规则不一致也会让索引失效,统一成一种;
- 排查链路最后一步用
SHOW FULL COLUMNS核对排序规则。
这套 SQL 优化实践支撑着零工系统的岗位查询。相关项目源码:https://gitee.com/gzqkl/xllg