PG 19把JSON变成了“数据出口”标准
本期播客
从存储JSON到交付JSON, PG 19把JSON变成了“数据出口”标准
很多团队嘴上说 PostgreSQL 是数据库,
可现实里,它早就顺手干了半个数据中台的活。
现在,PostgreSQL 又往前迈了一步。
2026 年 3 月 16 日和 3 月 20 日合入的两个 commit,正式让 COPY TO 原生支持 JSON 导出,还补上了 force_array 选项。
对应提交:
7dadd38cda95bf5bc0c4715d9ab71766d16933794c0390ac53b745c2800f6aa3d9ee2515d6ab499b
表面看,这只是 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/jsonbJSON 操作符 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 在补齐短板,还是在越界吞掉中间层的活?