PostgreSQL的UPDATE...FROM本质是单目标表更新,非多表JOIN;错误写法如UPDATE a FROM a JOIN b会导致静默失效,因目标表重复出现在FROM中引发歧义。正确写法为UPDATE t SET ... FROM (SELECT ...) AS src WHERE t.id = src.t_id,且WHERE必须含显式关联条件,否则易全表误更新。

PostgreSQL 的 UPDATE ... FROM 不是“多表 JOIN 更新”,而是“单目标表 + 单源结构”更新;写错关联逻辑会直接导致全表误更新或零更新。
为什么 UPDATE ... JOIN 会静默失效?
PostgreSQL 不支持标准 SQL 的 UPDATE t1 JOIN t2 ON ... SET ... 语法。如果你强行写成:
UPDATE a SET value = 'test' FROM a JOIN b ON a.b_id = b.id WHERE a.key = 'test';
这条语句能通过解析,但 JOIN 部分完全被忽略——FROM a 让目标表 a 又出现在源列表里,造成自引用歧义,实际行为不可控(常表现为 0 行或全表更新)。
- 错误本质:把
FROM当成了SELECT的FROM,忽略了 UPDATE 的目标表只能有一个,且不能在 FROM 中重复出现 - 正确姿势:目标表只写在
UPDATE后;所有关联表必须包裹在FROM后的单个表达式内(子查询或嵌套 JOIN) - 验证方法:把整个
FROM ... WHERE拆出来跑一个等价SELECT,看返回行数是否与预期更新行数一致
FROM 后只能跟一个“东西”,但它可以是 JOIN 套娃
你不能写 FROM b, c 或 FROM b JOIN c ... JOIN d ... 平铺三张表,但可以写:
FROM (b JOIN c ON b.c_id = c.id) AS bc
或者更清晰地用子查询封装:
FROM (SELECT b.id, b.val, c.city FROM b JOIN c ON b.c_id = c.id) AS src
关键点在于:这个“东西”必须有明确别名,且 WHERE 中必须包含目标表与它的至少一个关联字段。
- 漏掉关联条件 → 笛卡尔积 → 全表更新(最常见翻车点)
- 多对一关系(如一个订单对应多个地址)→ 可能重复匹配 → 更新结果不可预测 → 应加
DISTINCT ON或聚合 - 推荐始终给源结构起别名(如
AS src),避免列名冲突,也方便调试
两表更新的最小安全模板
这是你每天都会用到、且不会出错的基础写法:
UPDATE target_table t
SET col1 = src.val1,
col2 = src.val2
FROM source_table src
WHERE t.id = src.target_id;
注意三个硬性约束:
-
target_table不能出现在FROM子句中(除非你要自更新,那得另加条件防死循环) -
WHERE必须包含t.xxx = src.yyy这类显式连接条件,不能只靠src内部过滤 -
SET右侧的表达式可直接引用src列,但左侧列名前**不能加表别名**(UPDATE t SET t.col1 = ...是语法错误)
容易被忽略的性能与一致性细节
看似一条 SQL 就搞定,但背后有几个隐形坑:
- 没在
WHERE关联字段上建索引 → 全表扫描 + 嵌套循环 → 大表更新卡死 - 源表数据有变更(比如另一事务正在 INSERT/UPDATE
source_table)→UPDATE FROM读取的是事务快照,但若 WHERE 条件依赖未提交数据,可能漏更新 - 想用
RETURNING *查看哪些行被改了 → 必须确保FROM结构不放大行数,否则返回结果会重复 - 跨库或大字段更新时,WAL 日志暴涨 → 建议分批加
WHERE t.id BETWEEN x AND y控制单次更新量
真正难的从来不是写出来,而是确认它只改了该改的那些行——每次执行前,先跑一遍等效 SELECT,比任何文档都管用。










