帮助中心 >  技术知识库 >  云服务器 >  服务器教程 >  MySQL 慢 SQL 排查实战:从 EXPLAIN 到 EXPLAIN ANALYZE

MySQL 慢 SQL 排查实战:从 EXPLAIN 到 EXPLAIN ANALYZE

2026-07-10 17:28:37 281

MySQL 慢 SQL 排查实战:从 EXPLAIN 到 EXPLAIN ANALYZE

生产环境中,数据库响应变慢时,很多运维人员第一反应就是增加索引。但实际上,索引并非万能,只有准确分析 SQL 的执行过程,才能真正定位性能瓶颈。

下面以一个典型的订单表为例,介绍慢 SQL 的排查思路。

CREATE TABLE orders (

    id BIGINT PRIMARY KEY AUTO_INCREMENT,

    user_id BIGINT NOT NULL,

    product_id BIGINT NOT NULL,

    status TINYINT NOT NULL,

    amount DECIMAL(10,2),

    create_time DATETIME NOT NULL,

    INDEX idx_user(user_id),

    INDEX idx_create_time(create_time)

) ENGINE=InnoDB;

业务查询:

SELECT *

FROM orders

WHERE user_id = 10001

AND status = 1

ORDER BY create_time DESC

LIMIT 20;

随着数据增长到数千万行,该 SQL 响应时间超过 3 秒。

使用 EXPLAIN 分析执行计划

首先查看执行计划:

EXPLAIN

SELECT *

FROM orders

WHERE user_id = 10001

AND status = 1

ORDER BY create_time DESC

LIMIT 20;

输出示例:

type: ref

key: idx_user

rows: 385214

Extra: Using where; Using filesort

重点关注几个字段:

type

访问方式

 

rows

预计扫描行数

 

key

实际使用索引

 

Extra

是否存在额外开销

这里最大的信号是:

Using filesort

说明排序无法利用索引,需要额外排序。

创建联合索引

当前只有单列索引:

user_id

create_time

对于上述 SQL,更合适的是联合索引:

ALTER TABLE orders

ADD INDEX idx_user_status_time (

    user_id,

    status,

    create_time

);

再次查看执行计划:

EXPLAIN

SELECT *

FROM orders

WHERE user_id = 10001

AND status = 1

ORDER BY create_time DESC

LIMIT 20;

输出变为:

type: ref

key: idx_user_status_time

rows: 20

Extra: Using index

扫描行数从几十万降到几十行,排序也由索引完成。

使用 EXPLAIN ANALYZE

MySQL 8.0 提供了更直观的执行分析。

EXPLAIN ANALYZE

SELECT *

FROM orders

WHERE user_id = 10001

AND status = 1

ORDER BY create_time DESC

LIMIT 20;

示例输出:

-> Limit: 20 row(s)

(actual time=0.842..0.857 rows=20 loops=1)

 

-> Index lookup

(actual time=0.824..0.841 rows=20)

相比普通 EXPLAIN,EXPLAIN ANALYZE 会显示:

真实执行时间

实际扫描行数

每一步耗时

循环次数

能够快速判断预估值与真实执行情况是否一致。

检查是否发生回表

如果查询:

SELECT *

即使使用索引,也可能发生回表。

例如:

SELECT id,user_id,status,create_time

FROM orders

WHERE user_id=10001

ORDER BY create_time DESC;

如果查询字段全部包含在联合索引中,就有机会使用覆盖索引。

执行计划:

Extra: Using index

意味着无需回表读取数据页,性能通常更高。

判断是否存在索引失效

下面的 SQL 很容易导致索引无法使用:

SELECT *

FROM orders

WHERE DATE(create_time)='2026-07-01';

由于对索引列进行了函数计算,MySQL 无法利用索引。

推荐改写:

SELECT *

FROM orders

WHERE create_time >= '2026-07-01 00:00:00'

AND create_time < '2026-07-02 00:00:00';

执行计划通常会恢复为索引范围扫描。

利用慢查询日志定位问题

开启慢查询日志:

slow_query_log = ON

slow_query_log_file = /var/lib/mysql/slow.log

long_query_time = 1

log_queries_not_using_indexes = ON

查看最耗时 SQL:

mysqldumpslow -s t -t 20 /var/lib/mysql/slow.log

按平均耗时排序:

mysqldumpslow -s at -t 20 /var/lib/mysql/slow.log

如果需要更详细的分析,可使用:

pt-query-digest /var/lib/mysql/slow.log

它会统计:

执行次数

平均耗时

95%响应时间

扫描行数

锁等待时间

非常适合持续优化数据库性能。

 


提交成功!非常感谢您的反馈,我们会继续努力做到更好!

这条文档是否有帮助解决问题?

非常抱歉未能帮助到您。为了给您提供更好的服务,我们很需要您进一步的反馈信息:

在文档使用中是否遇到以下问题:
XML 地图