慢查询日志与 EXPLAIN 入门
发布时间:2026-02-10 22:46       
更新时间:2026-02-11 07:19
一条 SQL 慢,先别急着加索引。正确顺序是:打开慢查询日志找出最慢的那几条,用 EXPLAIN 看执行计划,弄清楚慢在哪一步,再动手改。
## 一、打开慢查询日志
```
SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 1;
```
long_query_time 单位是秒,执行时间超过它就记入日志。想让重启后仍生效,写进配置文件:
```
[mysqld]
slow_query_log = 1
long_query_time = 1
slow_query_log_file = /var/log/mysql/slow.log
```
跑一两天后按总耗时排序看前十条:
```
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
```
mysqldumpslow 自带汇总,同类查询合并计数,比肉眼翻日志高效得多。日志文件单独放目录并做好轮转,慢查询开久了文件不小,别让它把系统盘写满。
## 二、EXPLAIN 重点读三列
```
EXPLAIN SELECT * FROM orders WHERE user_id = 42 AND status = 'paid';
```
type 列:访问方式。const、eq_ref、ref 都是走了索引的好现象,出现 ALL 就是全表扫描,数据量一大必慢。key 列:实际使用的索引,显示 NULL 说明没有索引可用或没被选中。rows 列:预估扫描行数,和实际返回行数差出几个数量级,就是索引没用对地方。possible_keys 列显示候选索引,key 为空而候选非空,说明优化器算了成本后放弃,常见于统计信息过期,跑一遍 ANALYZE TABLE 再看。
## 三、一个例子走完全程
上面那条查询 EXPLAIN 显示 type 为 ALL,rows 预估八十万,实际只返回三行。补一个联合索引(建之前看一眼这两列的区分度,全是同一个状态值的列单独建索引收益有限):
```
ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);
```
再跑 EXPLAIN,type 变成 ref,rows 降到几十,耗时从秒级回到毫秒级。如果查询需要的列不多,还可以把这些列一并放进索引形成覆盖索引,Extra 列出现 Using index 表示不用回表,还能再快一档。
## 四、验证与分寸
索引改完先在生产验证这条查询的耗时,再观察几天慢日志,确认没有拖累其他查询。高峰期前不动索引,大表上的 ALTER 即便秒加也有锁风险,改动留到低峰窗口。索引不是越多越好,每个索引都会拖慢写入,单表索引个数控制在个位数是稳妥的经验值。
慢查询治理是个循环:看日志、跑 EXPLAIN、改索引、再观察。让这个循环按周运转,数据库的问题会越来越少。