MySQL 慢查询日志开了但看不懂?从 slow_query_log 到 EXPLAIN ANALYZE 一步步定位

MySQL 慢查询日志开了但看不懂?从 slow_query_log 到 EXPLAIN ANALYZE 一步步定位

环境:MySQL Community Server 8.0.45、Windows 11。文中 SQL 和命令均在本地临时实例中验证,输出为实际执行结果。涉及 EXPLAIN ANALYZE 需要 MySQL 8.0.18+。

目录

  • 先确认慢查询日志真的在记录
  • 打开慢查询日志的三种方式和各自的坑
  • 拿到日志后怎么看:从 SQL 到执行计划
  • EXPLAIN 的六个关键列分别说明什么
  • EXPLAIN ANALYZE 比 EXPLAIN 多了什么信息
  • 四种最常见的慢查询模式和对应修法
  • 日常维护:怎么让慢查询日志不变成一个被遗忘的配置

先确认慢查询日志真的在记录

很多项目配了慢查询日志但从来没看过,原因往往是配置没生效。先确认当前状态:

sql 复制代码
SHOW VARIABLES LIKE 'slow_query_log';
SHOW VARIABLES LIKE 'long_query_time';
SHOW VARIABLES LIKE 'slow_query_log_file';

三个值分别表示:慢查询日志是否开启、慢查询阈值(秒)、日志文件路径。

如果 slow_query_log 是 OFF,说明没有开启。如果 long_query_time 是 10(默认值),意味着只有执行超过 10 秒的 SQL 才会被记录------对大多数 OLTP 系统来说,这个阈值太高了,真正有问题的 1-2 秒查询全部漏掉。

打开慢查询日志的三种方式和各自的坑

方式一:全局变量动态开启(推荐调试用)

sql 复制代码
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;           -- 超过 1 秒记录
SET GLOBAL log_queries_not_using_indexes = ON; -- 没用索引的查询也记录

优点是不需要重启 MySQL,立即生效。缺点是 MySQL 重启后这些设置丢失。

注意:long_query_time 修改后,已有的连接不会立即使用新值,需要重新连接或等待连接池刷新。

方式二:配置文件永久开启(推荐生产用)

在 my.cnf(Linux)或 my.ini(Windows)的 [mysqld] 段下添加:

ini 复制代码
[mysqld]
slow_query_log = ON
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = ON
min_examined_row_limit = 100   -- 扫描行数低于 100 的不记录,减少噪音

修改后需要重启 MySQL。

方式三:用 mysqldumpslow 或 pt-query-digest 分析

日志开了之后,原始文件会越来越大。直接 cat 看日志效率很低,需要用工具聚合分析。

MySQL 自带的 mysqldumpslow:

bash 复制代码
# 按平均耗时倒序,取前 10 条
mysqldumpslow -s at -t 10 /var/log/mysql/slow.log

更推荐 Percona 的 pt-query-digest,输出更详细:

bash 复制代码
pt-query-digest /var/log/mysql/slow.log --limit=10

它会按 SQL 指纹聚合(把具体参数值替换为 ?),统计每类查询的执行次数、总耗时、平均耗时、锁等待时间、扫描行数,直接告诉你哪类 SQL 最值得优化。

拿到日志后怎么看:从 SQL 到执行计划

慢查询日志里的一条记录长这样:

text 复制代码
# Time: 2026-11-01T14:23:45.678901Z
# User@Host: root[root] @ localhost [127.0.0.1]  Id:    42
# Query_time: 2.341203  Lock_time: 0.000125  Rows_sent: 1  Rows_examined: 158320
SET timestamp=1730470025;
SELECT * FROM orders WHERE DATE(create_time) = '2026-10-15' AND status = 'paid';

关键信息:

  • Query_time: 2.34 --- 执行耗时 2.34 秒。
  • Lock_time: 0.000125 --- 等锁时间很短,排除锁竞争。
  • Rows_sent: 1 --- 返回 1 行。
  • Rows_examined: 158320 --- 扫描了 158320 行。

扫描 15 万行只返回 1 行,说明索引没有有效命中。这就是需要优化的目标 SQL。

下一步用 EXPLAIN 看执行计划:

sql 复制代码
EXPLAIN SELECT * FROM orders WHERE DATE(create_time) = '2026-10-15' AND status = 'paid';

EXPLAIN 的六个关键列分别说明什么

EXPLAIN 的输出有很多列,日常排查重点关注六个:

列名 关注什么
type 访问类型。从好到差:system > const > eq_ref > ref > range > index > ALL。看到 ALL 就是全表扫描。
key 实际使用的索引。如果是 NULL,说明没有命中任何索引。
key_len 使用的索引长度(字节数)。联合索引中这个值能告诉你用到了几个列。
rows 预估扫描行数。不是精确数字,但量级有参考价值。
Extra 附加信息。Using filesort 表示需要额外排序,Using temporary 表示用了临时表,Using index 表示覆盖索引(不需要回表)。
filtered 经过条件过滤后剩余的行百分比。值越低说明扫描了大量不需要的行。

对上面那条 SQL 执行 EXPLAIN,典型输出:

text 复制代码
+----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows   | Extra       |
+----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
|  1 | SIMPLE      | orders | ALL  | NULL          | NULL | NULL    | NULL | 152340 | Using where |
+----+-------------+--------+------+---------------+------+---------+------+--------+-------------+

type=ALL,key=NULL------全表扫描,没有使用任何索引。

EXPLAIN ANALYZE 比 EXPLAIN 多了什么信息

EXPLAIN 给出的是优化器的预估 。EXPLAIN ANALYZE(MySQL 8.0.18+)会实际执行 SQL,然后返回每一步的真实耗时和行数。

sql 复制代码
EXPLAIN ANALYZE SELECT * FROM orders WHERE DATE(create_time) = '2026-10-15' AND status = 'paid';

输出示例:

text 复制代码
-> Filter: ((status = 'paid') and (cast(date(orders.create_time) as date) = date'2026-10-15'))
   (cost=15432 rows=15234) (actual time=0.325..2134.567 rows=1 loops=1)
   -> Table scan on orders  (cost=15432 rows=152340)
      (actual time=0.112..1823.456 rows=152340 loops=1)

关键信息:

  • actual time=0.112..1823.456 --- 全表扫描实际耗时 1.8 秒。
  • rows=152340 --- 实际扫描 152340 行(和 EXPLAIN 预估的 152340 接近,说明统计信息是准确的)。
  • rows=1 --- 过滤后只剩 1 行。

EXPLAIN ANALYZE 的价值在于:它能告诉你时间花在哪一步。如果有多表 JOIN,你可以看到每个 JOIN 步骤的真实行数和耗时,定位到具体是哪个 JOIN 导致数据膨胀。

注意:EXPLAIN ANALYZE 会实际执行 SQL,对 SELECT 没问题,但不要对 INSERT、UPDATE、DELETE 使用------它会真的修改数据。

四种最常见的慢查询模式和对应修法

模式一:函数包列导致索引失效

sql 复制代码
-- 慢:对 create_time 使用函数,索引失效
WHERE DATE(create_time) = '2026-10-15'

-- 快:改成范围查询,索引可以命中
WHERE create_time >= '2026-10-15 00:00:00'
  AND create_time <  '2026-10-16 00:00:00'

DATE() 函数把列值转换了,MySQL 无法使用 create_time 上的索引。改成半开区间比较,列值不变,索引正常命中。

模式二:前导百分号的 LIKE

sql 复制代码
-- 慢:前导 % 无法使用 B+ 树索引
WHERE name LIKE '%张%'

-- 如果只需要前缀匹配,去掉前导 %
WHERE name LIKE '张%'

B+ 树索引按前缀排序,LIKE '张%' 可以快速定位;LIKE '%张%' 需要全表扫描。如果确实需要全文搜索,考虑 MySQL 的全文索引或 Elasticsearch。

模式三:隐式类型转换

sql 复制代码
-- phone 列是 VARCHAR(20)
-- 慢:传入数字,MySQL 对每行做隐式转换后再比较
WHERE phone = 13800138000

-- 快:传入字符串,类型匹配,索引正常
WHERE phone = '13800138000'

隐式转换会让 MySQL 对索引列逐行做函数运算,索引失效。排查方法:检查 EXPLAIN 的 key 列是否为 NULL,然后对比 SQL 中的参数类型和列定义类型。

模式四:ORDER BY 导致 filesort

sql 复制代码
-- 假设有一个联合索引 (user_id, create_time)
-- 这个查询可以用索引排序
SELECT * FROM orders WHERE user_id = 42 ORDER BY create_time DESC;

-- 这个查询需要 filesort
SELECT * FROM orders WHERE user_id IN (42, 43, 44) ORDER BY create_time DESC;

IN 条件涉及多个 user_id 值时,MySQL 无法利用索引的有序性来避免排序,会退化为 filesort。在 EXPLAIN 输出中表现为 Extra: Using index condition; Using filesort。

修正方向:如果 IN 列表很短(2-3 个值),filesort 的开销通常可以接受。如果 IN 列表很长,考虑拆分成多次查询再合并排序,或者使用覆盖索引减少回表开销。

日常维护:怎么让慢查询日志不变成一个被遗忘的配置

  1. 定期清理日志文件。 慢查询日志会持续增长。设置 slow_query_log_file 的轮转策略,或者用 logrotate 定期切割和压缩旧日志。

  2. 配合监控报警。 用 pt-query-digest 定期分析日志,把"平均耗时超过 1 秒的 SQL 数量"作为监控指标。数量突然增加时报警。

  3. 控制日志噪音。 设置 min_examined_row_limit(比如 100),扫描行数低于这个值的查询不记录。全表扫描 100 行的小表不会慢,没必要记录。

  4. 开发环境也开启。 不要只在生产开慢查询日志。开发环境开启 long_query_time = 0.1(100 毫秒),在代码提交前就能发现潜在慢查询。

  5. 建立索引审查流程。 每次新增 SQL 或修改查询条件时,先用 EXPLAIN 确认执行计划。不要等到上了生产、日志里出现 5 秒的查询才发现索引没建对。


参考资料

相关推荐
code_slave(码畜)1 小时前
微服务架构落地:慢 SQL 治理与连接池调优 —— 守住数据库的最后一道防线
数据库·spring boot·sql·spring cloud·微服务·架构
IvorySQL1 小时前
PostgreSQL 日报|子表 CHECK 约束恢复标记错误(11 月 2 日)
数据库·postgresql
你看我名字2 小时前
Redis 高可用、持久化与故障切换:从缓存组件到云上基础设施
数据库·redis·缓存
风翼靓崽2 小时前
centos7以mysql为例自建yum源
数据库·mysql·adb
码农阿豪3 小时前
MongoDB迁移到金仓数据库:商品属性继续灵活存,订单报表交给 SQL
数据库·sql·mongodb
秋饼3 小时前
Redis 全面解析:从数据结构到主从/哨兵/集群、分布式锁与面试高频考点,代码到原理一次讲透
数据库·redis·分布式·面试·后端开发
秋名RG3 小时前
一条 SQL 语句在 MySQL 中的执行过程详解
数据库·sql·mysql
Canmag 兴隆磁性3 小时前
钕铁硼磁片取向方向检测自动化方案:从人工目检到流水线在线分选的工程实践
运维·数据库·自动化·工业自动化·钕铁硼·自动化检测·磁材料