EXPLAIN ANALYZE 能直接执行 UPDATE,但会真实修改数据,必须用 BEGIN 和 ROLLBACK 包裹以确保安全;它返回含 actual time、rows、buffers 等真实指标的执行计划,可暴露锁等待、索引失效、全表扫描等深层问题。

EXPLAIN ANALYZE 能不能直接跑 UPDATE?
不能直接执行,但可以安全地“试跑”——EXPLAIN ANALYZE 对 UPDATE 语句是实际执行的,会真实修改数据。这不是“预估”,而是真干。所以千万别在生产库上裸跑。
正确做法是套一层事务并立刻回滚:
BEGIN; EXPLAIN ANALYZE UPDATE users SET status = 'active' WHERE id = 123; ROLLBACK;
这样既拿到完整执行计划(含真实耗时、行数、缓冲区读写),又不污染数据。
常见错误现象:有人漏掉 BEGIN/ROLLBACK,导致生产数据被意外更新;也有人只用 EXPLAIN(没加 ANALYZE),结果看到的是估算值,完全看不出锁等待或 I/O 瓶颈。
重点关注执行计划里的哪几行?
EXPLAIN ANALYZE 输出中,UPDATE 的执行计划通常包含三类关键节点:ModifyTable(顶层)、Seq Scan 或 Index Scan(定位行)、以及可能隐含的 Bitmap Heap Scan 或 LockRows。你需要盯住这些字段:
-
actual time:看“启动耗时”和“总耗时”是否严重偏离(比如启动 0.1ms,总耗时 2s)——说明大部分时间花在等锁或 I/O 上 -
Rows Removed by Filter:如果这个值很大(比如扫描 10 万行,只更新 100 行),说明 WHERE 条件没走索引,正在全表扫 -
Buffers(需加BUFFERS选项):shared read高 → 缓存未命中;shared dirtied高 → 更新引发大量脏页刷盘;shared hit低 → 索引或表缓存率差 - 出现
LockRows节点且耗时长 → 正在等行锁,可能是并发 UPDATE 冲突,或被长事务 hold 住
为什么有时加了索引,EXPLAIN 还显示 Seq Scan?
不是索引没建对,而是优化器判断“走索引不如全表扫快”。常见原因有:
- WHERE 条件用了函数,比如
WHERE lower(email) = 'a@b.com'→ 标准 B-tree 索引失效 - 统计信息过期:
pg_class.reltuples和实际行数偏差大,优化器误判成本;运行ANALYZE table_name可修复 - 更新字段本身在索引里(比如复合索引
(status, created_at)),但 WHERE 只查status,而该字段区分度极低(如 95% 值都是'pending')→ 优化器弃用索引 - 表太小(
验证方法:强制走索引测试性能,用 SET enable_seqscan = off; 再跑 EXPLAIN ANALYZE,对比耗时。但别长期关,只是诊断用。
UPDATE 慢的真正元凶往往藏在“看不见”的地方
执行计划里看不到,但 EXPLAIN ANALYZE 的 Buffers 和 Actual Total Time 会暴露线索:
- 高
shared read+ 低shared hit→shared_buffers配得太小,或表/索引没被热数据覆盖 - 高
shared dirtied→ 每次 UPDATE 都生成新版本(MVCC),旧版本堆积 → 触发VACUUM压力,甚至引发autovacuum频繁抢占资源 - 总耗时远高于各节点
actual time之和 → 时间花在锁等待、WAL 写入或检查点同步上(这时要查pg_stat_activity和pg_locks)
最容易被忽略的一点:UPDATE 的性能瓶颈,80% 不在 SQL 本身,而在 MVCC 机制引发的后续连锁反应——死元组膨胀、索引膨胀、autovacuum 延迟、甚至触发 wraparound 风险。光看 EXPLAIN 只能定位“第一公里”,后面得靠 pg_stat_all_tables 和 pg_stat_progress_vacuum 继续追。


















