一条SQL是否“慢”取决于业务链路中的预期耗时而非固定阈值;需结合监控P95/P99分布设long_query_time,区分环境,并检查log_output、log_queries_not_using_indexes及MySQL 8.0默认关闭等隐藏开关。

怎么确认一条SQL算“慢”?别凭感觉设long_query_time
直接设成1秒不是万能解法。真实业务里,一个报表查询跑5秒可能合理,而一个用户登录校验查用户表超过50ms就该报警。关键看它在链路中的预期耗时——比如用SkyWalking或Zipkin看到某接口SQL占了总耗时80%,哪怕只跑了300ms,它就是瓶颈。
设long_query_time前先做两件事:
- 查历史监控,看P95/P99 SQL耗时分布,取75分位值作基线
- 区分环境:测试库可设0.1秒触发日志,生产库建议从1秒起步,避免日志爆炸
- MySQL 5.7+ 支持微秒级设置,如
SET GLOBAL long_query_time = 0.3;,但注意该变量对已连接会话不生效
慢查询日志开了却没记录?检查三个隐藏开关
常见现象:执行SET GLOBAL slow_query_log = ON;后,明明SQL卡住好几秒,日志文件还是空的。大概率是以下三处被忽略:
-
slow_query_log只是总开关,还必须确保log_output设为FILE(默认)或TABLE(写入mysql.slow_log表) - 如果用了
log_queries_not_using_indexes = ON,但SQL走了索引却仍慢,它就不会进慢日志——这不是bug,是设计如此 - MySQL 8.0 默认关闭
slow_query_log,且log_slow_admin_statements和log_slow_slave_statements默认关着,主从场景下DDL或从库查询可能漏捕获
EXPLAIN输出里哪些字段一眼就能看出问题?
不用逐行读完所有列,盯住这四个关键字段:
-
type:出现ALL(全表扫描)或index(全索引扫描)基本等于没走对索引;range以上才算健康 -
key:显示实际用的索引名,如果是NULL,说明优化器放弃使用索引(常见于WHERE里对字段用了函数,如WHERE DATE(create_time) = '2026-04-26') -
rows:预估扫描行数,如果比表总行数还大,说明统计信息过期,需执行ANALYZE TABLE -
Extra:出现Using filesort或Using temporary是危险信号,意味着排序/分组没走索引,正在磁盘上临时建结构
为什么EXPLAIN说走了索引,但SQL还是慢?
这是最易掉坑的地方。索引“被选中”不等于“被高效使用”。典型情况:
- 索引区分度极低:比如给性别字段建索引,
EXPLAIN显示key非空,但实际扫描了50%的行,不如全表扫 - 回表代价高:覆盖索引没建全,
SELECT *导致大量随机IO去聚簇索引捞数据,尤其在SSD上延迟更敏感 - 隐式类型转换:
WHERE user_id = '123'(字符串)对比INT字段,触发全表扫描,EXPLAIN可能仍显示key有值,但rows暴增 - 统计信息陈旧:
SHOW INDEX FROM table_name看到索引存在,但EXPLAIN的rows严重偏离实际,先ANALYZE TABLE再重试
真正要验证索引效果,得看SHOW PROFILE FOR QUERY N或performance_schema.events_statements_history_long里的实际wait/io/file和cpu耗时,而不是只信EXPLAIN的预估。











