- 文档
- 教程
- 知识库
【免费下载链接】til
:memo: Today I Learned
EXPLAIN是 PostgreSQL 中探查查询执行计划与性能表现的核心命令,而EXPLAIN ANALYZE常被当作它的"加强版"在对话中混用。本篇基于 til 仓库中 postgres/difference-between-explain-and-explain-analyze.md 的实战记录,厘清两者的本质差异——EXPLAIN ANALYZE会真正执行查询,EXPLAIN不会,并围绕 INSERT/UPDATE/DELETE 等写操作给出可复现的验证示例,帮助你安全、正确地用它们定位查询瓶颈。
核心区别:执行与不执行
EXPLAIN语句可以让你对一条查询的性能表现获得洞察。日常交流中,EXPLAIN和EXPLAIN ANALYZE经常被混着叫,但它们都能用来探查查询如何执行的同时,有一个关键区别必须清楚:
EXPLAIN ANALYZE会执行查询;EXPLAIN不会执行查询。
对于SELECT查询,这个区别可能感觉并不重要——毕竟 SELECT 本身没有副作用。但对于INSERT、UPDATE和DELETE这类写语句,你就必须搞清楚自己用的是哪一个了,因为两者的数据影响完全不同。
仅输出成本估算:EXPLAIN
EXPLAIN只输出规划器基于统计信息生成的成本估算(cost estimates),不触碰真实数据。下面针对books表执行一条 INSERT 的 EXPLAIN:
> explain insert into books (title, author) values ('Fledgling', 'Octavia Butler'); QUERY PLAN ---------------------------------------------------- Insert on books (cost=0.00..0.01 rows=1 width=76) -> Result (cost=0.00..0.01 rows=1 width=76) > select count(*) from books; count ------- 0可以看到,执行完EXPLAIN insert ...之后再次select count(*),books表依然是0 行——查询并没有真正执行,你得到的只是这条 INSERT 语句的成本估算值。cost=0.00..0.01中的起始成本与总成本、rows=1的估算行数、width=76的估算元组宽度,全部来自规划器的代价模型与表统计信息。
输出实际执行数据:EXPLAIN ANALYZE
EXPLAIN ANALYZE会真实执行这条 INSERT,并输出成本估算 + 实际执行数据(actual numbers):
> explain analyze insert into books (title, author) values ('Fledgling', 'Octavia Butler'); QUERY PLAN ---------------------------------------------------------------------------------------------- Insert on books (cost=0.00..0.01 rows=1 width=76) (actual time=0.285..0.285 rows=0 loops=1) -> Result (cost=0.00..0.01 rows=1 width=76) (actual time=0.012..0.012 rows=1 loops=1) Planning time: 0.021 ms Execution time: 0.309 ms > select count(*) from books; count ------- 1对照两次输出可以看到三组关键差异:
- 多出
actual time/rows/loops字段:每个计划节点都附带了真实耗时(单位毫秒)、实际行数与循环次数; - 多出
Planning time与Execution time两行:分别对应规划阶段与实际执行阶段的耗时; - 副作用确实发生:执行完
EXPLAIN ANALYZE insert ...之后,books表中真实多出了一行(count 从 0 变为 1)。
也就是说,EXPLAIN ANALYZE给你的是"预测 + 实测"的完整画像,但代价是它会像正常执行语句一样修改数据。同理,EXPLAIN ANALYZE用在UPDATE、DELETE上也会真实改动或删除数据,必须谨慎。
什么时候用哪一个
基于上述差异,可以给出明确的选用原则:
| 场景 | 推荐命令 | 原因 |
|---|---|---|
| 只读查询(SELECT)调优 | EXPLAIN ANALYZE | 无副作用,且能拿到实际耗时与行数,判断是否与估算严重偏离 |
| 写操作(INSERT/UPDATE/DELETE)排查 | 先用EXPLAIN | 只读执行计划,不产生任何数据改动 |
| 需要验证写操作的真实代价 | EXPLAIN ANALYZE(配合事务回滚) | 确认实际耗时,但要接受数据会被修改 |
对于写操作,如果想安全地获得真实执行数据,一个常用技巧是把EXPLAIN ANALYZE放在事务中执行并回滚:
begin; explain analyze delete from books where title = 'Fledgling'; rollback;这样既能拿到真实执行指标,又不会让数据被永久改动(注意:即便回滚,语句仍会真实执行、占用锁与资源,行为与正常 DML 一致)。
从仓库延伸:EXPLAIN 的实战配套用法
这条 TIL 记录是 til 仓库 PostgreSQL 分类下查询性能探查系列的一部分,仓库内还有若干与之直接配套的实战笔记,组合使用可以覆盖从"看懂计划"到"量化对比"的完整链路。
多种输出格式:TEXT / JSON / YAML / XML
EXPLAIN(或EXPLAIN ANALYZE)默认输出为供人阅读的TEXT格式。在 postgres/output-explain-query-plan-in-different-formats.md 中展示了如何切换到程序可解析的标准化格式,例如:
> explain (analyze, format json) select title from books where created_at > now() - '1 year'::interval; QUERY PLAN ---------------------------------------------------------------- [ + { + "Plan": { + "Node Type": "Seq Scan", + "Parallel Aware": false, + "Async Capable": false, + "Relation Name": "books", + "Alias": "books", + "Startup Cost": 0.00, + "Total Cost": 1.28, + "Plan Rows": 5, + "Plan Width": 32, + "Actual Startup Time": 0.008, + "Actual Total Time": 0.014, + "Actual Rows": 22, + "Actual Loops": 1, + "Filter": "(created_at > (now() - '1 year'::interval))",+ "Rows Removed by Filter": 0 + }, + "Planning Time": 0.050, + "Triggers": [ + ], + "Execution Time": 0.023 + } + ] (1 row)explain (analyze, format json)中的format选项可取值TEXT(默认)、JSON、YAML、XML,适合接入监控、自动化分析或生成可视化工具。
用 EXPLAIN ANALYZE 做量化性能对比
仓库中的 postgres/lower-is-faster-than-ilike.md 展示了EXPLAIN ANALYZE的另一典型用法——量化对比两种写法的真实耗时。该记录通过对比:
select * from users where email ilike 'some-email@example.com';与
select * from users where lower(email) = lower('some-email@example.com');发现lower()写法耗时约 12ms,ilike写法约 17ms;而为lower(email)建立函数索引create unique index users_unique_lower_email_idx on users (lower(email));之后,lower()写法骤降至约 0.08ms。这正是EXPLAIN ANALYZE的价值所在:它提供的实际执行时间让不同实现方案的优劣一目了然,也验证了索引是否被真正利用。
在应用层(Rails)调用 EXPLAIN
PostgreSQL 之外,应用框架也常把EXPLAIN暴露给开发者。仓库 rails/perform-sql-explain-with-activerecord.md 记录了在 Pry 会话中通过 ActiveRecord 对任意ActiveRecord::Relation直接调用#explain:
Recipe.all.joins(:ingredient_amounts).explain Recipe Load (0.9ms) SELECT "recipes".* FROM "recipes" INNER JOIN "ingredient_amounts" ON "ingredient_amounts"."recipe_id" = "recipes"."id" => EXPLAIN for: SELECT "recipes".* FROM "recipes" INNER JOIN "ingredient_amounts" ON "ingredient_amounts"."recipe_id" = "recipes"."id" QUERY PLAN ---------------------------------------------------------------------------- Hash Join (cost=1.09..26.43 rows=22 width=148) Hash Cond: (ingredient_amounts.recipe_id = recipes.id) -> Seq Scan on ingredient_amounts (cost=0.00..21.00 rows=1100 width=4) -> Hash (cost=1.04..1.04 rows=4 width=148) -> Seq Scan on recipes (cost=0.00..1.04 rows=4 width=148) (5 rows)注意 ActiveRecord 的#explain只产生成本估算(等价于EXPLAIN),不会执行查询,这与本文强调的核心区别一致——在 ORM 层面默认就选择了无副作用的版本。
配合 psql 工具:\timing与statement_timeout
探查性能时还可以借助 psql 与 PostgreSQL 自身的配套设施:
- 仓库 postgres/turn-timing-on.md 记录在
psql中执行\timing可以开关每条查询的耗时显示(毫秒级),适合在平时随手观察查询速度; - 仓库 postgres/prevent-a-query-from-running-too-long.md 记录通过
set statement_timeout to '500';(或带单位如'15s')为当前连接设置语句超时,避免调优过程中一条失控的EXPLAIN ANALYZE长时间占用资源。
小结
EXPLAIN与EXPLAIN ANALYZE唯一的本质区别就是"是否真实执行查询":前者只输出规划器的成本估算,后者会真实执行并附上实际耗时、行数等实测数据。对 SELECT 而言选哪个问题不大;对 INSERT、UPDATE、DELETE 等写语句,务必先想清楚——你只是想看执行计划(用EXPLAIN),还是愿意接受数据被真实改动(用EXPLAIN ANALYZE)。配合事务回滚、JSON/YAML/XML 格式输出、\timing与statement_timeout,你就能在安全的前提下获得最准确的执行画像。
- 文档
- 教程
- 知识库
【免费下载链接】til
:memo: Today I Learned
相关推荐
TDengine 查询执行计划分析实战:使用 EXPLAIN 与 EXPLAIN ANALYZE 定位慢查询瓶颈
TDengine 查询执行计划分析实战:使用 EXPLAIN 与 EXPLAIN ANALYZE 定位慢查询瓶颈 TDengine 的 EXPLAIN / EX
数据库时序数据库物联网大数据实时分析云原生终极指南:7分钟掌握VisionAgent视觉AI代码生成的核心技术
终极指南:7分钟掌握VisionAgent视觉AI代码生成的核心技术 VisionAgent作为LandingAI推出的革命性视觉AI助手,通过智能代码生成技术
人工智能AI Agent代码智能体计算机视觉AI 应用Java Programming Tutorial for Beginners:函数式编程基础与Lambda表达式
Java Programming Tutorial for Beginners:函数式编程基础与Lambda表达式 Java函数式编程是现代Java开发中的重要
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考