postgresql 15中子查询默认内联展开而非物化,cte亦不自动物化;仅当显式使用materialized关键字、多次引用、含volatile函数或代价估算有利时,优化器才可能物化。

PostgreSQL 15 中子查询不会被自动物化 —— 它默认始终是“内联展开”执行的,除非你显式用 WITH(CTE)且触发了 planner 的物化决策。
为什么 WITH 子句不等于物化
很多人误以为写 WITH 就一定把结果存起来。实际上 PostgreSQL 12 及之后版本默认对 CTE **不物化**(non-materialized),planner 会根据代价估算决定是否内联展开。只有当它判断物化更便宜(比如子查询被多次引用、或含 volatile 函数),才会真正物化。
-
WITH是语法结构,不是强制物化指令 - 加
MATERIALIZED关键字(PostgreSQL 12+)才能明确要求物化:WITH cte AS MATERIALIZED (SELECT ...) - 不加
MATERIALIZED时,planner 可能完全忽略WITH,直接把子查询塞进主查询树里 - 即使物化了,也只是临时内存/磁盘缓存,不是持久存储,查询结束即销毁
IN/EXISTS/ANY 子查询几乎从不物化
这类标量子查询或半连接子查询,planner 一律走嵌套循环、哈希或合并连接策略,不会先算出完整结果集再比对。
PostgreSQL 18.4 官方 Ubuntu 安装包现已发布,这是目前最新的稳定版本。推荐通过官方 APT 仓库安装:先执行 sudo apt update 更新索引,再运行 sudo apt install postgresql-18 即可完成部署。新版本引入了异步 I/O 子系统,在顺序扫描与 VACUUM 场景下性能提升显著,同时支持 UUID v7 原生生成函数与虚拟生成列。
-
WHERE id IN (SELECT id FROM large_table WHERE status = 'active')→ planner 通常转成Hash Semi Join,不缓存子查询结果 -
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.created_at > '2025-01-01')→ 多数情况走Anti Join或索引嵌套循环,不物化 -
salary > ANY(SELECT salary FROM dept_80)→ 转成hashed Subplan,但仍是运行时逐行 probe,不是提前物化整个结果集
什么情况下 planner 会真的物化子查询
仅限以下少数场景,且依赖代价估算,不是行为保证:
- 同一个
WITH查询被主查询引用 ≥ 2 次,且 planner 估得重复计算代价 > 缓存代价 - 子查询含
random()、clock_timestamp()等 volatile 函数,必须物化以保证语义一致(否则每次引用都重算) - 子查询有
LIMIT+ 外部ORDER BY,planner 可能物化中间结果来避免排序失效 - 使用
MATERIALIZED显式声明(PostgreSQL 12+)——这是唯一确定性手段
别指望子查询替代物化视图
子查询物化是瞬时、不可控、无索引、不持久的;而 MATERIALIZED VIEW 是物理表,可建索引、可 CONCURRENTLY 刷新、可统计分析。
- 想复用复杂聚合结果?用
CREATE MATERIALIZED VIEW,别靠子查询“碰运气” - 子查询里写
GROUP BY+JOIN多次?性能波动大,explain 看到SubPlan或CTE Scan就说明没物化 - 需要跨会话共享预计算结果?子查询做不到,只有物化视图或普通表才行
真正可控的物化只发生在你明确定义的地方:物化视图、临时表、或带 MATERIALIZED 的 CTE。子查询的执行路径永远由 planner 动态决定,别把它当缓存用。










