PostgreSQL的UPDATE...FROM语法要求目标表仅出现在UPDATE后,不得重复出现在FROM中,否则关联失效导致全表误更新;必须通过显式等值条件(如t.id=src.target_id)建立连接,且FROM后只能是一个表、子查询或嵌套JOIN结构。

PostgreSQL 16 的 UPDATE ... FROM 语法和 9.5 以来所有版本完全一致,没有新增特性,但错误写法在 16 中依然会静默失效——尤其容易全表误更新。
为什么 UPDATE ... FROM 有时不报错却没更新?
常见现象是执行后返回 UPDATE 0 或 UPDATE 10000(远超预期),但数据没变或全表被改。根本原因是目标表被重复引入:FROM 子句里又写了目标表名,比如:
UPDATE a SET x = b.y FROM a JOIN b ON a.id = b.a_id WHERE ...
PostgreSQL 解析时把 FROM a 当作独立源表,导致目标表 a 出现在两个位置,引擎放弃 JOIN 逻辑,退化为无关联扫描——WHERE 若没严格限制,就变成全表更新。
- 目标表只能出现在
UPDATE后,不能出现在FROM列表中(除非自更新,且必须加防死循环条件) -
FROM后必须是一个单一结构:一张表、一个子查询、或括号包裹的JOIN套娃 - 用
EXPLAIN看执行计划,如果出现Seq Scan on a而没有Hash Join,基本就是关联失效了
两表更新的最小安全模板(直接抄)
这是你能在 PostgreSQL 16 里放心复用、不会翻车的写法:
UPDATE target t
SET col1 = src.val1,
col2 = src.val2
FROM source_table src
WHERE t.id = src.target_id;
三个硬性检查点:
-
target没出现在FROM里 -
WHERE中有且仅有一个显式等值关联(t.id = src.target_id),不能只靠src.status = 'done'这类过滤条件 -
src是别名,不是表名;列引用必须带别名前缀(src.val1),避免歧义
三张表怎么安全 JOIN 更新?
不能写 FROM a, b, c 或 FROM a JOIN b JOIN c 平铺——PostgreSQL 会报错或行为不可控。正确做法是把多表逻辑压进一个子查询或嵌套 JOIN:
UPDATE orders o SET status = 'shipped' FROM ( SELECT DISTINCT ON (o2.id) o2.id AS order_id FROM customers c JOIN addresses a ON c.id = a.customer_id AND a.city = '北京' JOIN shipments s ON s.order_id = o2.id AND s.status = 'delivered' JOIN orders o2 ON o2.customer_id = c.id ) AS src WHERE o.id = src.order_id;
关键细节:
-
FROM后只有一个东西:(...)子查询,带别名src -
DISTINCT ON (o2.id)防止一对多(如一个订单多个发货记录)导致重复匹配 -
WHERE o.id = src.order_id是强制关联点,漏掉就会笛卡尔积 - 所有过滤条件(
a.city = '北京',s.status = 'delivered')都放在子查询里,不放主WHERE
容易被忽略的性能与事务陷阱
看似语法跑通,实际可能卡住或锁表:
- 没给
src.target_id或o.customer_id加索引 → 全表扫描 + Nested Loop,几万行就明显变慢 - 在长事务里跑大范围
UPDATE ... FROM→ 锁住目标表所有被扫描行,阻塞并发读写 - 用
RETURNING *返回大量字段 → 网络和内存开销陡增,调试时建议只选关键列 - 分区表上更新涉及分区键(如
UPDATE ... SET time = now()改了分区字段)→ 可能触发行迁移(delete + insert),并引发序列化失败
最危险的不是语法错,而是关联条件写漏或写松——它不会报错,只会默默把整张表刷成同一值。

















