DuckDB 的物化 CTE 不再阻塞管道了
用 DuckDB 写过复杂查询的人都知道:一旦用了 MATERIALIZED CTE,后面的所有操作都得等它先全部算完。
WITH materialized_cte AS MATERIALIZED (
SELECT ... FROM huge_table WHERE ...
)
SELECT a.*, b.*
FROM materialized_cte a
JOIN materialized_cte b ON a.id = b.id;
这个查询里,DuckDB 会先把 materialized_cte 完整写进内存(ColumnDataCollection),写完了,两个 JOIN 消费者才能开始读。管道在这里被彻底打断。
这个行为从 DuckDB 诞生起就没变过——直到昨天。
PR #23750 给物化 CTE 加了流式扇出执行(streaming fanout execution)。逻辑语义不变,但物理执行完全不一样了:
- 01生产者边跑边推——CTE 的 chunk 直接喂给下游管道,不用等全部算完
- 02交换层缓存——下游消费者可以从交换层读取已产生的 chunk
- 03落盘兜底——超出内存的部分仍然走 ColumnDataCollection
用作者的话说:物化 CTE 从 pipeline breaker 变成了 pipeline participant。
这个 PR 改了 3701 行代码,主要在 join hashtable 和 physical operator 层。合并之后,任何用 MATERIALIZED CTE 的查询都可能变快——你自己什么也不用改。
同一天合并的另一个改动:PostgreSQL 扩展(duckdb-postgres)现在支持标准 INSERT 语法了。
之前 duckdb-postgres 通过 COPY ... FROM STDIN (FORMAT BINARY) 写数据。大部分 Postgres 兼容数据库都支持,但部分 wire-compatible 数据库(没有实现 COPY 协议)会报错。
现在可以这样写:
ATTACH 'dbname=postgres user=...' AS pg (TYPE postgres); INSERT INTO pg.schema.table SELECT * FROM local_data;
底层自动切成批量 INSERT 语句。对于连接那些不支持 COPY 的 Postgres 兼容数据库,这是个实打实的可用性提升。
两个 PR 都已经合并到主分支。预计会在下一个版本里可用(目前最新稳定版是 v1.5.4)。
试试你的物化 CTE 查询——等下一个版本出来后跑一下,看看快了多少。
相关链接:
PR #23750: github.com/duckdb/duckdb/pull/23750
PR #35ee7df2: github.com/duckdb/duckdb-postgres/commit/35ee7df2