PostgreSQL EXPLAIN 和 EXPLAIN ANALYZE 有什么区别?
在 PostgreSQL 中,EXPLAIN 和 EXPLAIN ANALYZE 都是用于数据库性能调优的强大工具,但它们之间有一个最核心的区别:
EXPLAIN只做预测(不执行 SQL)。EXPLAIN ANALYZE既做预测,又实际执行(真刀真枪跑一遍)。
以下是详细的区别和对比:
1. EXPLAIN:理论与估算
当你运行 EXPLAIN <查询语句> 时:
- 不执行查询:PostgreSQL 的查询优化器(Planner)会根据数据库收集到的统计信息(如表大小、索引分布等)生成一个执行计划,但绝不会真正去执行这条 SQL。
- 输出内容:只显示估算值(Estimated)。包括估算的启动成本(cost)、总成本、返回行数(rows)和单行宽度(width)。
- 速度:瞬间返回结果,因为它不需要真的去读取大量数据。
- 安全性:绝对安全。你可以放心地对
UPDATE、DELETE或极其耗时的SELECT语句使用EXPLAIN,它不会修改任何数据,也不会卡死数据库。
2. EXPLAIN ANALYZE:实践与真实数据
当你运行 EXPLAIN ANALYZE <查询语句> 时:
- 实际执行查询:PostgreSQL不仅会生成执行计划,还会真正把这条 SQL 语句执行一遍。
- 输出内容:在
EXPLAIN估算值的基础上,增加了真实运行时的统计数据(Actual)。包括实际花费的时间(actual time)、实际返回的行数(actual rows)、循环次数(loops),以及底部的“规划时间(Planning Time)”和“执行时间(Execution Time)”。 - 速度:取决于 SQL 本身的执行速度。如果一条 SQL 原本要跑 10 分钟,
EXPLAIN ANALYZE也要跑 10 分钟以上(因为还要额外记录性能指标)。 - 安全性(⚠️高危警告):如果不加处理,它会真实改变数据! 如果你执行
EXPLAIN ANALYZE DELETE FROM users;,表里的数据就真的被删除了。
输出结果对比示例
使用 EXPLAIN:
EXPLAIN SELECT * FROM users WHERE age > 30;
输出:
Seq Scan on users (cost=0.00..18.50 rows=300 width=40)
Filter: (age > 30)
(注意:只有 cost 和 rows 的估算值)
使用 EXPLAIN ANALYZE:
EXPLAIN ANALYZE SELECT * FROM users WHERE age > 30;
输出:
Seq Scan on users (cost=0.00..18.50 rows=300 width=40) (actual time=0.025..0.120 rows=280 loops=1)
Filter: (age > 30)
Rows Removed by Filter: 720
Planning Time: 0.085 ms
Execution Time: 0.150 ms
(注意:括号里多出了 actual time 和真实的 rows,并且底部有精确的毫秒级耗时。这能让你看出优化器估算的 300 行和实际的 280 行是否差距过大。)
总结对比表
| 特性 | EXPLAIN |
EXPLAIN ANALYZE |
|---|---|---|
| 是否执行 SQL | ❌ 否(只生成计划) | ✅ 是(真实执行) |
| 耗时 | 毫秒级返回 | 等同于甚至略慢于 SQL 实际执行时间 |
| 返回数据类型 | 仅估算值(Cost, Estimated Rows) | 估算值 + 真实值(Actual Time, Actual Rows) |
| 排查“统计信息过期” | 无法排查(不知道真实行数) | 非常有用(对比估算 rows 和实际 rows 即可发现) |
| 对写操作(DML)的影响 | 安全(不修改数据) | 危险(会真实 INSERT/UPDATE/DELETE 数据) |
💡 进阶避坑与最佳实践
如何安全地
EXPLAIN ANALYZE更新/删除语句?
由于EXPLAIN ANALYZE会真跑数据,对写操作一定要包裹在事务中并回滚:sqlBEGIN; EXPLAIN ANALYZE DELETE FROM orders WHERE status = 'expired'; ROLLBACK;这样既能看到真实的执行性能,又不会真的删除数据。
终极排查利器:BUFFERS
在实际调优时,光看时间通常不够,还需要看内存/磁盘的命中率。强烈建议日常排查慢查询时使用:sqlEXPLAIN (ANALYZE, BUFFERS) SELECT ...这会额外输出
Buffers: shared hit=... read=...,明确告诉你这条 SQL 是从内存里读的数据(hit快),还是从磁盘读的数据(read慢)。