雷灵模板

MySQL 慢查询怎么查?从 slow log 到索引优化的实战步骤(新手可照抄)

avatar

雷灵

🤖AI摘要
本文介绍了如何排查MySQL慢查询问题,从开启慢查询日志、分析日志内容到进行索引优化。首先确认问题是否出在MySQL本身,然后开启慢查询日志记录可疑SQL,通过分析日志找出耗时最长的SQL语句,并根据执行计划进行索引优化或改写SQL。文章还提供了开启慢日志的命令和配置示例,适合MySQL新手学习和实践。

MySQL 慢查询怎么查?从 slow log 到索引优化的实战步骤(新手可照抄)

如果你现在的症状是:

  • 首页/列表页偶尔卡 3~10 秒
  • 后台“看起来没报错”,但用户就是觉得慢
  • 服务器 CPU 不一定很高,IO 也不一定爆,就是“说不清的慢”

我一般不建议第一时间“加机器/升配置”。MySQL 慢查询这事儿,很多时候是某几条 SQL 在偷偷拖后腿:一次慢 2 秒还好,关键是它可能每分钟跑几百次。

这篇文章我按我常用的排查顺序写:先抓证据 → 再锁定 Top SQL → 看执行计划 → 对症下药(索引/改写)→ 验证。你照着做,基本能把“慢在哪”说清楚,并且把它修掉。

默认场景:MySQL 5.7/8.0 + InnoDB。命令以 Linux 为例。


0)先确认:到底是不是 MySQL 在慢?(别一上来就开大招)

很多“数据库慢”其实是:反代超时、应用线程池堵住、磁盘满了写不进去、DNS 卡住……所以我会先花 2 分钟做个快速分流。

0.1 看 MySQL 有没有“堆积”的请求

mysql -e "SHOW FULL PROCESSLIST\G" | sed -n '1,120p'

你重点看:

  • Time 很高的行是不是重复出现
  • State 是否在 Sending data / Sorting result / Copying to tmp table
  • 有没有一堆 Locked / Waiting for ... lock

0.2 看机器是否被 IO 或内存拖住

top -c
free -h
# 没装 iostat 的话先装 sysstat
iostat -x 1 5
  • iostat 里如果 %util 长时间接近 100%,那是 IO 瓶颈
  • free 如果 swap 在狂用,那优化 SQL 也救不了太多(先把内存/缓存策略理顺)

如果你确实看到“某几条 SQL 反复出现 + 时间很长”,就可以进入正题:开慢日志。


1)开启慢查询日志(slow log):先抓证据

慢日志不是为了“炫技”,它就是为了回答一句话:到底哪条 SQL 在拖慢网站

1.1 临时开启(适合你现在就要定位)

-- 开启慢日志
SET GLOBAL slow_query_log = 1;

-- 慢阈值:先从 1 秒开始,站点很忙的话可以先 0.5
SET GLOBAL long_query_time = 1;

-- 不建议一上来就开,噪音太大
SET GLOBAL log_queries_not_using_indexes = 0;

-- 可选:只记录扫描行数足够多的查询(减少噪音)
-- SET GLOBAL min_examined_row_limit = 1000;

检查是否生效:

SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';

1.2 生产建议:配置文件里长期启用 + 做日志轮转

慢日志很容易把磁盘写满(尤其你把 long_query_time 调得很低的时候)。所以我的经验是:

  • 慢日志文件单独放到可控目录
  • 配合 logrotate,别等磁盘满了再哭

常见配置(示意,路径按你发行版调整):

[mysqld]
slow_query_log=1
slow_query_log_file=/var/log/mysql/slow.log
long_query_time=1
log_queries_not_using_indexes=0

如果你的站已经出现“磁盘占满”的问题,可以先看我站内这篇(排查思路类似):

  • 服务器磁盘占满怎么办?Linux 一键定位大文件与日志清理思路(含命令)

2)读慢日志:别只盯“最慢的一条”,要看“最耗时的一类”

慢日志抓到以后,很多人第一反应是找 Query_time 最大的那条。这个思路容易跑偏。

我更关注两个指标:

  1. 总耗时最高(出现次数 * 单次耗时)
  2. P95/P99(95%/99% 的请求耗时)

2.1 快速看个大概:mysqldumpslow

mysqldumpslow -s t -t 20 /var/log/mysql/slow.log
mysqldumpslow -s at -t 20 /var/log/mysql/slow.log
  • -s t:按单次时间排序
  • -s at:按平均时间排序

2.2 更推荐:pt-query-digest(真的省时间)

如果你能装 percona-toolkit,优先用它:

pt-query-digest /var/log/mysql/slow.log > /tmp/slow.report.txt

你看报告时先盯:

  • # Query 1: 38.2% 这种占比高的
  • Calls 很多的(出现频率高)
  • Rows_examined 很高的(多半在扫表/扫索引)

3)看执行计划:EXPLAIN / EXPLAIN ANALYZE 怎么读

定位到某条 SQL 后,下一步就是 EXPLAIN。

3.1 我最常看的 5 个字段

EXPLAIN SELECT ...;
  • type:越靠近 const/ref/range 越好;ALL 基本就是全表扫
  • key:实际用到的索引是什么(为空就危险)
  • rows:估算要扫多少行(越大越危险)
  • ExtraUsing filesort / Using temporary 往往是性能雷区
  • possible_keys:有索引但没用上,说明索引不匹配/写法不对/统计信息不准

3.2 MySQL 8.0 可以直接 EXPLAIN ANALYZE(更直观)

EXPLAIN ANALYZE SELECT ...;

它会给你“实际耗时”和“每步执行次数”,比纯 EXPLAIN 更接近真实。


4)最常见的 3 类慢 SQL(以及我通常怎么改)

下面这三类,我在建站/后台系统里见得最多。

4.1 列表页:WHERE + ORDER BY + LIMIT 但没合适索引

典型 SQL:

SELECT id, title, created_at
FROM posts
WHERE status = 1 AND category_id = 3
ORDER BY created_at DESC
LIMIT 20;

这种 SQL 想跑得快,核心是:过滤条件 + 排序字段要能被同一条索引覆盖。

通常我会这么建索引:

CREATE INDEX idx_posts_cat_status_created
ON posts(category_id, status, created_at DESC);

注意:索引顺序不是随便排的。这里先 category_id/status 是为了过滤,再 created_at 是为了排序。

4.2 大分页:LIMIT offset 很大,越翻页越慢

典型 SQL:

SELECT id, title
FROM posts
ORDER BY id DESC
LIMIT 10000, 20;

这种写法到后面就是“数据库替你数 10000 行再丢掉”。体验一定差。

我更常用 游标分页(keyset pagination)

-- 假设上一页最后一条 id=12345
SELECT id, title
FROM posts
WHERE id < 12345
ORDER BY id DESC
LIMIT 20;

前端/接口只要记住“上一页最后一条 id”,性能会稳很多。

4.3 过滤条件写法“挡住了索引”

比如你为了按天查,把字段包了一层函数:

-- 不推荐:函数会让索引失效
WHERE DATE(created_at) = '2026-03-12'

正确姿势是改成范围:

WHERE created_at >= '2026-03-12 00:00:00'
  AND created_at <  '2026-03-13 00:00:00'

5)加索引不是越多越好:我一般会做这 3 个检查

  1. 这条索引是否真的服务于高频查询?(看慢日志/调用次数)
  2. 有没有重复索引?(字段顺序不同但效果一样的)
  3. 写入压力会不会明显变大?(索引越多写入越慢)

很多站点“索引越加越慢”,不是幻觉:写入、更新、维护统计信息都有成本。


6)最后一步:验证你真的变快了(别凭感觉)

我通常会这样验证:

  • 用同一组参数跑优化前后的 SQL,对比耗时
  • 再看慢日志里这类 SQL 的占比是否下降
  • 观察接口 P95 是否下降(如果你有监控)

MySQL 8.0 的话,EXPLAIN ANALYZE 的“实际耗时”也能给你很直观的对比。


7)如果你只想抄一个最小清单(我给你一句话版本)

1) 开 slow log(先 1s)→ 2) 用 pt-query-digest 排序(看总耗时)→ 3) EXPLAIN 看 type/key/rows/Extra → 4) 先解决列表页索引和大分页 → 5) 验证 P95


相关阅读

黔ICP备2022004976号
powered by 雷灵模板