MySQL 线上性能问题的排查入口,九成从慢查询开始。慢查询日志记录执行时间超阈值的 SQL,EXPLAIN 展示执行计划,两者配合能定位绝大多数查询性能问题。本文按日志开启、采集、分析、索引优化、改写 SQL 的完整链路给出可执行的方案,命令在 MySQL 5.7 与 8.0 通用。
开启慢查询日志与阈值设置
生产环境建议持久化开启,避免每次重启丢失配置:
-- 查看当前状态
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
-- 动态开启(重启失效)
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = ON;
-- 持久化:写入 my.cnf [mysqld] 段
slow_query_log = ON
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 1
log_queries_not_using_indexes = ON
long_query_time 从业务角度取值:核心链路 1 秒即可视为慢,报表类查询可放宽到 3 秒。注意 long_query_time 为 0 会记录全部 SQL,日志量爆炸,生产别这么配。
慢查询日志分析:mysqldumpslow与pt-query-digest
日志文件按时间累积,直接看是灾难,用工具聚合才有价值:
-- 按平均耗时排序,取前20
mysqldumpslow -s at -t 20 /var/log/mysql/mysql-slow.log
-- 按执行次数排序,定位高频慢SQL
mysqldumpslow -s c -t 20 /var/log/mysql/mysql-slow.log
-- pt-query-digest 生成完整报告
pt-query-digest /var/log/mysql/mysql-slow.log > slow_report.txt
mysqldumpslow 会把数字参数归一化为 N,便于聚合同形态 SQL。percona-toolkit 的 pt-query-digest 给出更详细的分位数耗时分布,适合定期周报分析。
EXPLAIN执行计划核心字段解读
拿到具体慢 SQL 后,用 EXPLAIN 看执行计划,重点关注 type、key、rows、Extra 四列:
EXPLAIN SELECT o.id, o.amount, u.name
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.created_at > '2026-09-01' AND o.status = 'paid';
+----+-------------+-------+------+---------------+---------+---------+-------+-------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | rows | Extra |
+----+-------------+-------+------+---------------+---------+---------+-------+-------+---------------------------------+
| 1 | SIMPLE | o | range| idx_created | idx_created | 4 | 5000 | Using index condition |
| 1 | SIMPLE | u | ref | PRIMARY | PRIMARY | 4 | 1 | Using where |
+----+-------------+-------+------+---------------+---------+---------+-------+-------+---------------------------------+
type 从好到差:system > const > eq_ref > ref > range > index > ALL。ALL 表示全表扫描,是慢查询的重灾区;rows 是预估扫描行数,与实际情况对比能发现统计信息不准;Extra 出现 Using filesort、Using temporary 时优先处理排序与临时表。
索引优化:联合索引、覆盖索引与索引失效
多条件查询靠联合索引。建索引时遵循最左前缀,把等值条件放前面、范围条件放后面:
-- 最优:等值在前,范围在后
ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, created_at);
-- 覆盖索引:查询列全在索引内,避免回表
ALTER TABLE orders ADD INDEX idx_user_amount (user_id, amount);
-- 常见失效场景,逐个排查
EXPLAIN SELECT * FROM orders WHERE DATE(created_at) = '2026-09-08';
-- 函数包裹索引列导致索引失效,改写为范围:
EXPLAIN SELECT * FROM orders WHERE created_at >= '2026-09-08' AND created_at < '2026-09-09';
索引列上做函数运算、隐式类型转换、% 前缀模糊查询、OR 连接未全部命中索引,都会导致优化器放弃索引。加索引后一定要用 EXPLAIN 验证 type 从 ALL 变为 range/ref 才算生效。
分页深翻页与排序优化
LIMIT 1000000, 20 这类深分页,MySQL 要先扫描 100 万行再丢弃,性能极差。改造方案:
-- 慢:深分页全量扫描
SELECT id, amount FROM orders ORDER BY id LIMIT 1000000, 20;
-- 快:延迟关联,先取主键再回表
SELECT o.id, o.amount FROM orders o
JOIN (SELECT id FROM orders ORDER BY id LIMIT 1000000, 20) t
ON o.id = t.id;
-- 更快:游标方式,记住上一页最后一条 id
SELECT id, amount FROM orders WHERE id > 1000020 ORDER BY id LIMIT 20;
游标分页在数据持续增长时最稳定,代价是无法跳页。业务上支持“加载更多”的场景,直接改用游标模式。
从慢查询到性能验证的闭环
优化完成后用 EXPLAIN 对比执行计划,再用真实数据量压测确认:
-- 复查目标SQL是否仍出现在慢查询汇总中
SELECT DIGEST_TEXT, COUNT(*) AS cnt, SUM(SUM_TIMER_WAIT)/1e12 AS total_s
FROM performance_schema.events_statements_summary_by_digest
GROUP BY DIGEST_TEXT ORDER BY total_s DESC LIMIT 10;
performance_schema 的 digest 汇总能看全局 SQL 耗时分布,适合优化效果的定期跟踪。调优不是一次性工作,索引随数据分布和查询模式变化,建议每季度对 TOP 慢 SQL 做一次复盘。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mysql-man-cha-xun-ri-zhi-fen-xi-yu-explain-zhi-xing-ji-hua/