PostgreSQL码农集散地

PG 19把JSON变成了“数据出口”标准

本期播客

从存储JSON到交付JSON, PG 19把JSON变成了“数据出口”标准

很多团队嘴上说 PostgreSQL 是数据库,
可现实里,它早就顺手干了半个数据中台的活。

现在,PostgreSQL 又往前迈了一步。
2026 年 3 月 16 日和 3 月 20 日合入的两个 commit,正式让 COPY TO 原生支持 JSON 导出,还补上了 force_array 选项。

对应提交:

  • 7dadd38cda95bf5bc0c4715d9ab71766d1693379
  • 4c0390ac53b745c2800f6aa3d9ee2515d6ab499b

表面看,这只是 COPY TO 多了个 FORMAT JSON。
但如果你是 DBA、架构师,或者天天和数据接口打交道的开发者,你该看到的不是“多一个格式”,而是这句更刺耳的话:

PostgreSQL 正在从“数据存储层”进一步下沉到“数据交换层”。

这件事,不小。

一、这两个 commit 到底干了什么?

先说事实。

第一个 commit 做了两件核心事:

  • COPY TO 支持 FORMAT JSON
  • 默认输出是每行一个 JSON 对象,也就是典型的 NDJSON / JSON Lines 风格

第二个 commit 紧接着补了一个关键选项:

  • force_array

开启后,输出会被包成一个顶层 JSON 数组,也就是:

  • 没开 force_array:适合流式、一行一行消费
  • 开了 force_array:整个导出结果是一个合法的单一 JSON 值

这两个 commit 组合起来,实际上解决的是 JSON 导出里两个长期冲突的诉求:

  • 工程侧想流式处理
  • 接口侧想拿到合法 JSON 文档

PostgreSQL 以前能不能导出 JSON?当然能。
你可以写:

COPY (  
SELECT row_to_json(t)  
FROM ...  
) TO STDOUT;  

但问题在于,这类方案一直都只是“SQL 技巧”,不是一等公民。
现在不一样了。现在 PostgreSQL 明确表态:

JSON 导出,不再只是用户自己拼。数据库内核正式接手。

二、为什么我说这不是“小语法”,而是在争“数据出口”?

因为绝大多数企业系统,真正复杂的从来不是“把数据存进去”,而是“把数据拿出去”。

数据库世界有个被严重低估的现实:

  • 写入是一条链路
  • 查询是一条链路
  • 导出和交换,往往是另一条更脏、更碎、更容易失控的链路

很多团队明明数据都在 PostgreSQL 里,却还在外围堆一层又一层转换逻辑:

  • 先 COPY CSV
  • 再脚本转 JSON
  • 再包装成接口格式
  • 再给消息系统、日志系统、对象存储或下游 ETL

这类流水线最大的问题不是“麻烦”,而是:

每多一段转换,就多一段失败点、多一段编码差异、多一段性能损耗、多一段责任模糊。

所以,从第一性原理看,COPY TO FORMAT JSON 的价值不是“省几十行 Python”,而是:

把原本分散在脚本、应用、中间层里的“行转 JSON”动作,收回数据库出口层。

这就是为什么我说,它在争“数据出口”的主导权。

三、默认 NDJSON,恰恰说明 PostgreSQL 这次想得很工程化

这次设计里最聪明的一点,不是支持 JSON,
而是默认没有输出一个大 JSON 数组,而是输出 NDJSON 风格。

这是非常成熟的工程判断。

为什么默认 NDJSON 更合理?

NDJSON 规范和 JSON Lines 文档都强调一个核心事实:

  • 每一行是一个独立 JSON 值
  • 适合流协议、管道、日志、逐行处理
  • 整个文件默认不是一个单一合法 JSON 文档

这听起来像缺点,实际上在大数据导出场景下反而是优点。

因为对大结果集来说,真正重要的是:

  • 能不能边导边消费
  • 能不能边传边解析
  • 能不能出错时快速定位到某一行
  • 能不能天然兼容 Unix pipeline、日志系统、对象流、消息流

而不是“文件长得像不像一个完美的 JSON 数组”。

这就是第一性原理:

只要数据规模够大,流式处理几乎总比一次性组织成单一文档更符合吞吐和稳定性。

所以 PostgreSQL 把默认行为做成 NDJSON,是对的。
它不是“偷懒没包数组”,而是在用最适合大规模导出的默认策略。

四、那为什么又要补 force_array?因为接口世界并不按数据库逻辑活

但工程世界还有另一半现实。

很多 API、SDK、前端、低代码平台、对象存储消费方,要的不是“很多个 JSON 对象”,
而是:

一个标准 JSON 文档。

这时 NDJSON 就会暴露它的天然短板:

  • 整个文件不是单一 JSON 值
  • 不能直接丢给只接受标准 JSON 的解析器
  • 某些 HTTP 接口、配置导出、前端下载、测试快照场景,更偏好数组

这就是 force_array 的意义。

它不是附加糖衣,而是把 JSON 导出分成了两个明确世界:

1. 默认模式:NDJSON / JSON Lines

适合:

  • 流式导出
  • shell 管道
  • 日志采集
  • 增量处理
  • 超大结果集
  • 下游逐行消费

2. force_array

适合:

  • 一次性拿完整结果
  • Web API 返回
  • 前端下载
  • 需要一个标准 JSON 文档的系统
  • 直接给只会读单个 JSON 值的库

这相当于 PostgreSQL 官方承认了一个常识:

JSON 不是一种需求,而是两种需求。

  • 一种叫“能流”
  • 一种叫“能当文档”

这次它两边都接住了。

五、这件事对 DBA 和架构师意味着什么?

观点一:数据库和数据交换层的边界,正在继续变模糊

很多架构师一直默认:

  • 数据库存数据
  • 应用层拼 JSON
  • ETL 层做交换格式转换

这个分层在逻辑上没错。
但现实里,性能、简洁性和可维护性经常逼着系统往更短路径走。

COPY TO FORMAT JSON 的出现,本质上是在说:

如果“行转 JSON”这件事已经足够通用、足够高频,那它就值得下沉到数据库内核。

这和当年很多人先手写 XML/JSON 拼接,后来数据库逐步内建 JSON 函数,是一脉相承的。

观点二:数据出口统一,往往比数据入口统一更值钱

为什么?

因为导出链路更容易失控。
入口通常有明确写入协议、校验规则、事务边界。
出口往往没有:

  • 谁都能导
  • 格式五花八门
  • 一个团队一套脚本
  • 每个下游都想要自己的口味

所以架构上最容易烂掉的,恰恰是数据出口。

这次 PostgreSQL 原生支持 JSON 导出,对 DBA 和架构师最大的吸引力在于:

把一类高频出口格式标准化。

一旦标准化,后面得到的是:

  • 更少的转换脚本
  • 更少的口径漂移
  • 更少的编码/转义事故
  • 更统一的接口习惯

这不是“方便”,这是治理收益。

六、对应用开发者,这次最该听懂的不是“更好用”,而是“少造轮子”

这次 commit 对开发者最直接的意义,是少造无效轮子。

以前你要把查询结果导成 JSON 文件或流,经常会有这些写法:

  • 程序里查库后再 marshal 一遍
  • psql 导 CSV,再二次转
  • row_to_json/json_agg 搞定结果,再额外包层逻辑
  • shell、Python、Node 各写一版转换器

问题是,这些方案没有一个真正优雅:

  • 程序层 marshal:占应用 CPU 和内存
  • json_agg:大结果集时不天然适合流式
  • CSV 再转 JSON:纯属重复劳动
  • 多语言脚本:维护成本高

所以对开发者,这两个 commit 最现实的价值就是:

把“导出查询结果为 JSON”从应用代码里挪回数据库原语。

你可以开始更干净地做这件事:

COPY (  
SELECTid, name, created_at  
FROMusers
WHERE created_at >= now() - interval'1 day'
) TO STDOUT (FORMATJSON);  

如果要单一 JSON 文档:

COPY (  
SELECTid, name, created_at  
FROMusers
) TO STDOUT (FORMATJSON, FORCE_ARRAY);  

这不只是好看。
这意味着职责边界更明确:

  • PostgreSQL 负责输出结构化记录
  • 应用负责传输、鉴权和业务流程
  • 不再让应用层重复做“数据库最懂的数据序列化”

七、但我要泼一盆冷水:这不是“PostgreSQL 以后就是 JSON API 网关”

这时候必须把边界讲清楚,不然就容易吹过头。

1. 目前只有 COPY TO,没有 COPY FROM JSON

commit 明确写了:

当前 JSON format 只支持 COPY TO,不支持 COPY FROM。

这意味着什么?

意味着它目前解决的是出口标准化,不是入口闭环。
所以如果有人说:

“以后 PostgreSQL 原生 JSON 导入导出全打通了。”

这就是错的。

现在更准确的说法是:

PostgreSQL 先把 JSON 导出做成一等公民,JSON 导入还没进入同一成熟层级。

2. 它也不是所有场景下都优于 json_agg

如果你的需求是:

  • 返回几百行数据
  • 直接供 API 调用
  • 需要在 SQL 内继续嵌套组合
  • 需要在数据库里构造复杂层级对象

那 json_agg/json_build_object/json_object/json_array 这套 SQL/JSON 工具依然很重要。

COPY TO FORMAT JSON 更适合的是:

  • 文件导出
  • 流式传输
  • 大批量交换
  • 工具链输出
  • 结果集直接外送

所以别把它看成完全替代 json_agg。
它替代的是另一类事情:外部化输出协议。

3. 它也不自动替代 Parquet / Arrow / Avro

这点对架构师尤其重要。

如果你的核心目标是:

  • 列式压缩
  • 分析型扫描
  • 跨大数据引擎互通
  • 严格 schema 演进
  • 极致体积和吞吐

那 Parquet、Arrow、Avro 这类格式依然有自己的物理层优势。

JSON 的强项是:

  • 通用性
  • 可读性
  • 生态广
  • 调试方便
  • 接口友好

不是最省空间,也不是最适合分析引擎。

所以正确判断不是:

“有了 COPY JSON,就不用别的格式了。”

而是:

“当你要面向应用、服务、流式消费方导出时,PostgreSQL 终于给了一个像样的原生 JSON 出口。”

八、如果前提条件崩塌,观点也要跟着变

这篇文章最重要的一点,不是给你一个结论,
而是告诉你:什么前提下这个结论成立。

前提成立时

如果你满足这些前提:

  • 数据本来就在 PostgreSQL
  • 下游就是要 JSON
  • 导出链路很多、格式混乱
  • 你重视流式处理和标准化出口
  • 你想减少中间脚本和二次转换

那么我的观点非常明确:

这两个 commit 很值钱。它们补的不是语法,而是数据交换层的原生能力。

前提崩塌时

如果你的场景是:

  • 下游主要吃 Parquet/Arrow
  • 你需要的是高压缩列式格式
  • 你本来就只导很小结果集
  • 你已经在 SQL 里用 json_agg 很顺
  • 你真正缺的是 JSON 导入而不是导出

那这两个 commit 的意义就该下调。

这时更合理的观点是:

它是 PostgreSQL 在出口层的一次重要补强,但不是所有数据交换问题的终极答案。

这不矛盾。
这叫把能力放回适用场景里判断。

九、最值得警惕的一点:团队会不会因为“更方便”而把数据库变成接口拼装厂?

这里我要故意说得尖一点。

COPY TO FORMAT JSON 很好,
但它也可能诱发另一种坏习惯:

什么都往数据库里塞。

一旦数据库能原生吐 JSON,有些团队就会本能地继续下沉职责:

  • 复杂对象装配放数据库
  • 接口格式逻辑放数据库
  • 表达层变换放数据库
  • 各种特供字段也放数据库

短期看很爽,长期很危险。

第一性原理很简单:

  • 数据库擅长数据选择、过滤、连接、序列化
  • 应用层擅长业务语义、权限编排、协议适配、版本演进

所以最合理的边界不是“全下沉”,而是:

把通用、稳定、高频的数据导出格式下沉到数据库;把业务特化的表示逻辑留在应用层。

这是这两个 commit 真正该落地的姿势。

十、我的结论:PostgreSQL 正在从“会存 JSON”升级为“会交付 JSON”

过去这些年,PostgreSQL 在 JSON 上的进化,很多人都看过:

  • json/jsonb
  • JSON 操作符
  • SQL/JSON 函数
  • JSON_TABLE

但这些主要还是“库内处理 JSON”。

而这次 COPY TO FORMAT JSON + force_array,意义不一样。
它补的是库外交付 JSON。

这意味着 PostgreSQL 的角色又往前走了一步:

不只是存储层、查询层、处理层,还是越来越像一个标准化的数据交付端点。

这件事最有力量的地方,不是技术酷炫,
而是它切中了现代系统里一个最现实的问题:

数据出口,比数据入口更混乱。

谁先把出口标准化,谁就更接近控制系统复杂度。

结尾

所以,这两个 commit 最值得记住的一句话是:

PostgreSQL 不是突然会导 JSON 了,而是开始正式接管“如何把关系结果交付给 JSON 世界”这件事。

对 DBA 和架构师来说,这意味着更统一的数据出口治理。
对开发者来说,这意味着更少的转换代码和更短的交付路径。
但同时也别忘了边界:

  • 现在只有 COPY TO
  • 默认更偏流式 NDJSON
  • force_array 才是标准单文档 JSON
  • 它补的是交换层,不是替代所有数据格式和所有接口职责

你怎么看

你们团队现在导出 JSON,还是数据库查完后在应用里再拼一遍吗?
如果 PostgreSQL 原生支持 COPY TO FORMAT JSON,你会优先拿它替代脚本层转换,还是依然坚持“数据库只出表格,JSON 留给应用层”?
评论区聊聊:你觉得这一步,是 PostgreSQL 在补齐短板,还是在越界吞掉中间层的活?