发布时间:2026-09-15 12:31 更新时间:2026-09-15 12:31 阅读量:0
网站跑了一段时间之后,页面偶尔变慢,第一反应往往是“服务器配置不够”。但真正的原因常常藏在数据库里:某条 SQL 没走索引,扫了几十万行,单次执行几百毫秒甚至更久。这类问题平时不显山不露水,一旦并发上来就会拖垮整站。好消息是,MySQL 自带了一套慢查询记录机制,只要打开它,就能把“拖后腿”的语句一条条揪出来。本文面向站长和运维新手,讲清楚怎么开启慢查询日志、怎么设置阈值、怎么用 mysqldumpslow 做汇总,以及怎么判断一条慢 SQL 是不是缺索引。
慢查询日志默认在多数 MySQL 版本里是关闭的,需要手动打开。这里涉及几个核心参数:slow_query_log 控制开关,slow_query_log_file 指定日志文件路径,long_query_time 是判定“慢”的时间阈值,单位是秒,可以带小数。log_queries_not_using_indexes 则会把未使用索引的查询也记下来,调试阶段可以临时开,但线上长期开启容易把日志撑爆,要谨慎。
修改方式有两种。临时生效可以直接在会话里执行,重启后失效;长期生效建议写进配置文件,比如 /etc/my.cnf 或 /etc/mysql/mysql.conf.d/mysqld.cnf,具体路径以实际环境为准。下面这段配置放在 [mysqld] 段落下:
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 0
改完配置需要重启 MySQL 服务,或者用 SET GLOBAL 动态生效。动态修改的写法如下,注意 long_query_time 对已建立的连接不会立即生效,新连接才会用新值:
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
SHOW VARIABLES LIKE 'slow_query_log%';
SHOW VARIABLES LIKE 'long_query_time';
阈值设多少合适?这取决于业务。刚开始排查问题可以设 0.5 秒甚至更低,先把可疑语句捞出来;稳定运行阶段一般设 1 到 2 秒,既能发现明显问题,又不会产生海量日志。要注意的是,慢查询日志记录的是“执行时间超过阈值”的语句,测试环境数据量小,同一条 SQL 可能跑得飞快,所以优化验证最好在接近生产的数据量下做。
日志文件打开一看,往往成百上千条,人眼逐条翻效率太低。MySQL 自带一个 Perl 脚本 mysqldumpslow,专门用来做聚合统计。它会按“相同语句结构”归并,把数字和字符串替换成占位符,只保留 SQL 骨架,这样同一类查询就能合并计数。
常用参数里,-s 指定排序方式,比如 c 按次数、t 按总耗时、at 按平均耗时;-t 表示只显示前 N 条;-g 支持按关键字过滤。一个典型的用法是看总耗时最高的前 10 条:
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
mysqldumpslow -s at -t 10 /var/log/mysql/slow.log
mysqldumpslow -s c -t 10 -g "SELECT" /var/log/mysql/slow.log
输出里会看到类似“Count: 120 Time=2.31s”这样的汇总行,说明这类 SQL 被记录了 120 次,平均耗时 2.31 秒。次数多、平均耗时又高的语句,就是优先要处理的对象。如果日志文件很大,mysqldumpslow 解析会慢一点,可以先按时间段切分日志,或者用 pt-query-digest(Percona Toolkit 提供)做更细的报告,具体以官方文档为准。
拿到慢 SQL 骨架之后,下一步是搞清楚它为什么慢。最常见的答案就是没走索引,或者索引用错了。把候选 SQL 拿出来,前面加上 EXPLAIN 执行,重点看几列:type 如果是 ALL,说明全表扫描;key 为 NULL 表示没用上索引;rows 估算扫描行数,数值越大越危险;Extra 里出现 Using filesort 或 Using temporary,通常意味着排序、分组开销偏高。
举个例子,假设有一张订单表 orders,字段 user_id、status、created_at,慢查询是按用户和状态过滤再按时间排序:
EXPLAIN SELECT id, amount, created_at
FROM orders
WHERE user_id = 10086 AND status = 'paid'
ORDER BY created_at DESC
LIMIT 20;
如果结果显示 type=ALL、key=NULL、rows 接近全表行数,就可以考虑建联合索引。按“等值条件在前、范围或排序在后”的思路,可以建 (user_id, status, created_at)。建索引之前建议先评估写放大和磁盘占用,索引不是越多越好,具体以业务查询模式为准。
还有几个容易踩的坑值得提醒。第一,函数或隐式类型转换会让索引失效,比如 WHERE DATE(created_at) = '2026-09-01' 这种写法,改成范围条件更好。第二,前缀模糊匹配 LIKE '%abc' 基本用不上 B+ 树索引。第三,联合索引要遵循最左前缀原则,跨列顺序不对可能只用上一部分。第四,慢查询日志本身也会带来一点 I/O 开销,日志文件要纳入 logrotate 轮转,避免无限增长。
总结一下操作路径:先打开 slow_query_log 并把 long_query_time 设到合理区间,让它稳定记录;再用 mysqldumpslow 按总耗时或平均耗时排序,锁定高频慢语句;最后用 EXPLAIN 看执行计划,判断是否缺索引并针对性加索引。优化完不要忘了对比优化前后的慢查询数量和平均耗时,用数据确认效果。如果站点数据量持续增长,后续还可以关注 performance_schema 里的语句统计、连接池配置以及缓存层的配合,一步步把数据库这块短板补齐。
| 📑 | 📅 |
|---|---|
| Docker Compose 部署多容器应用实战:环境变量、数据卷与重启策略配置要点 | 2026-09-15 |
| Nginx 日志按天切割与过期清理:logrotate 实战 | 2026-09-15 |
| Let's Encrypt 证书自动续期失败排查:日志、80端口校验与 reload 钩子 | 2026-09-13 |
| Docker 磁盘占用过高清理实战:overlay2、容器日志与悬空镜像 | 2026-09-12 |
| Nginx 502/504 排查实战:从 upstream 超时到 php-fpm 进程池 | 2026-09-12 |
| 服务器定时任务实战:crontab 语法与不生效排查 | 2026-09-15 |
| Nginx 499 状态码排查实战:客户端断连与 upstream 超时的区别 | 2026-09-16 |
| 容器内存超限被 OOM Kill 排查:指标、cgroup 与堆参数对应 | 2026-09-16 |
| MySQL 主从延迟排查:Seconds_Behind_Master 忽高忽低怎么定位 | 2026-09-16 |
| rsync 增量同步与断点续传实战:exclude、--delete 与限速 | 2026-09-16 |