
你的网站变慢了。CPU 100%,用户说打不开。你重启了 PHP-FPM,好了几分钟,又慢了。
问题可能在数据库。你装了监控,看到数据库查询时间忽高忽低,但不知道哪条 SQL 在搞鬼。MySQL 自带一个功能叫慢查询日志。它会把执行时间超过阈值的 SQL 记录下来。打开它,找到那条 SQL,加个索引,或者改一下写法,问题就解决了。
先看一个数据
某电商网站,每天有几万订单。某天下午突然变慢,用户无法下单。开启慢查询日志后,发现一条 SQL 扫描了 200 万行数据,耗时 8 秒。这条 SQL 是统计某个用户的未读消息数,没加索引,用户越多越慢。
加上索引后,查询时间从 8 秒降到 0.01 秒。网站恢复正常。一条 SQL 能把整个网站拖垮,这不是危言耸听。
第一步:开启慢查询日志
MySQL 默认关闭慢查询日志。需要手动开启。
临时开启(重启后失效):
sql
SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 2; SET GLOBAL log_queries_not_using_indexes = ON;
永久开启:编辑 /etc/mysql/my.cnf 或 /etc/my.cnf:
ini
[mysqld] slow_query_log = 1 slow_query_log_file = /var/log/mysql/slow.log long_query_time = 2 log_queries_not_using_indexes = 1
重启 MySQL 生效。long_query_time = 2 表示超过 2 秒的 SQL 会被记录。刚开始可以设小一点,比如 1 秒,便于发现潜在问题。
查看慢查询日志状态:
sql
SHOW VARIABLES LIKE 'slow_query_log%'; SHOW VARIABLES LIKE 'long_query_time';
第二步:分析慢查询日志
直接用 cat 或 tail 看慢查询日志,信息很原始,不好读。MySQL 自带 mysqldumpslow 工具,能把日志汇总成可读的报告。
bash
# 按平均查询时间排序,取前10条 mysqldumpslow -s at -t 10 /var/log/mysql/slow.log
参数说明:
-s at:按平均查询时间排序(at= average time)-t 10:只显示前10条-g:按关键词过滤,比如-g "SELECT"只看 SELECT 语句
输出示例:
text
Count: 1234 Time=3.5s (4321s) Lock=0.0s (0s) Rows=100.0 (123456), root[root]@localhost SELECT * FROM orders WHERE status = 'S' ORDER BY created_at DESC
Count:这条 SQL 执行了多少次Time=3.5s (4321s):平均 3.5 秒,总共耗时 4321 秒Rows=100.0:平均返回 100 行
重点看那些 总耗时高、执行次数多、扫描行数大 的 SQL。
第三步:用 EXPLAIN 分析 SQL
找到慢 SQL 后,用 EXPLAIN 看执行计划。
sql
EXPLAIN SELECT * FROM orders WHERE status = 'S' ORDER BY created_at DESC\G
关键输出列:
| 列名 | 含义 | 危险信号 |
|---|---|---|
type | 访问类型 | ALL 表示全表扫描,必须优化 |
possible_keys | 可能用的索引 | 如果显示 NULL,说明没有可用索引 |
key | 实际用的索引 | NULL 表示没走索引 |
rows | 扫描的行数 | 数字越大越慢 |
Extra | 额外信息 | Using filesort、Using temporary 说明有额外的排序或临时表,通常意味着需要优化 |
如果 type=ALL:给 WHERE 条件里的字段加索引。
sql
ALTER TABLE orders ADD INDEX idx_status (status);
如果 Extra 里有 Using filesort:给 ORDER BY 的字段也加索引,或者建复合索引。
sql
ALTER TABLE orders ADD INDEX idx_status_created (status, created_at);
如果 rows 很大但不是全表扫描:可能是查询字段选择性太差,例如 status 字段只有两个值 ‘S’ 和 ‘F’,索引效果不好。这种情况需要考虑改查询逻辑或使用覆盖索引。
第四步:进阶分析——pt-query-digest
mysqldumpslow 功能有限。Percona Toolkit 里的 pt-query-digest 能生成更详细的报告,包括查询样例、响应时间分布、频率分析。
安装:
bash
sudo apt install percona-toolkit -y # Ubuntu sudo yum install percona-toolkit -y # CentOS
使用:
bash
pt-query-digest /var/log/mysql/slow.log
输出按总耗时降序排列,顶部的 SQL 就是最需要优化的。报告会把相似的 SQL 归类(参数不同的统一处理),显示每个类的执行次数、平均时间、扫描行数,并给出优化建议。
常见慢查询原因及优化
| 问题 | 典型表现 | 解决方案 |
|---|---|---|
| 无索引 | type=ALL,rows 巨大 | 给 WHERE 条件字段加索引 |
| 索引未命中 | key=城NULL,但 possible_keys 有值 | 检查字段类型是否匹配(如 WHERE id='123',id 是整型) |
| 隐式类型转换 | WHERE phone=13800138000(phone 是 VARCHAR) | 保持类型一致:WHERE phone='13800138000' |
| 函数操作索引列 | WHERE DATE(created_at) = '2024-01-01' | 改写为 WHERE created_at BETWEEN '2024-01-01' AND '2024-01-02' |
| 分页太深 | LIMIT 100000, 10 | 使用延迟关联或记录上次位置 |
| SELECT * | 返回大量不需要的列 | 只查询需要的字段 |
函数操作索引列是最容易被忽视的问题。WHERE DATE(created_at) = '2026-05-21' 不会走 created_at 索引,因为 MySQL 要对每一行先执行函数。改成范围查询即可用索引。
生产环境建议
- 长期开启慢查询日志:对性能影响极小,通常 < 1%。设置
long_query_time = 1,超过 1 秒的 SQL 都值得关注。 - 定期分析:每周跑一次
pt-query-digest,对比前后变化,及时发现新出现的慢 SQL。 - 设置告警:如果慢查询数量突增(比如每小时的慢查询数超过 100),触发告警,及时介入。
- 归档旧日志:慢查询日志文件可能很大,建议用
logrotate自动切分。
真实案例
某社交应用,用户动态列表加载越来越慢。用户从 1 万涨到 10 万,首页打开从 0.5 秒变成 5 秒。开启慢查询日志,发现 SELECT * FROM posts WHERE user_id = xxx ORDER BY created_at DESC 没加 (user_id, created_at) 复合索引,每次查询扫描几千行。
加索引后,查询扫描行数从 3000 降到 10 行,响应时间从 2 秒降到 0.02 秒。开发负责人感叹:“一个索引救了一个功能。”
最后一句
慢查询日志是 MySQL 的黑匣子。你不打开,就不知道问题在哪。
今天是周五,下班前把它打开。下周一回来,看看这一周系统记下了哪些慢 SQL。你会惊讶地发现,原来你的数据库一直在默默承受不该有的压力。
优化的第一步不是写代码,是找到要优化的代码。慢查询日志就是帮你找答案的。




