两张表,users 和 orders,orders 里已经存了收货城市,要把值同步回 users.city。第一反应是写子查询,MySQL 直接甩来 ERROR 1093,说不能在子查询里引用正在被更新的表。

MySQL 用 JOIN 绕开

UPDATE users u JOIN orders o ON o.user_id = u.id SET u.city = o.city WHERE o.city IS NOT NULL;

JOIN 版本不触发 1093,执行计划也清楚。要限制范围就加 WHERE,别一次全表刷,几百万行的更新会长时间占锁,线上容易出事。分批跑,每批带主键区间,压力小很多。

PostgreSQL 和 SQL Server 的写法

PostgreSQL 把来源表放在 FROM 后面:

UPDATE users u SET city = o.city FROM orders o WHERE o.user_id = u.id;

SQL Server 则是把 JOIN 写进 FROM,SET 里用别名。Oracle 一般用 MERGE INTO,按主键匹配上就更新,匹配不上可以选择插入,这也是它比 UPDATE 好用的地方。

一对多时取哪一条

一个用户有多条订单,直接 JOIN 会让同一行被更新多次,最后落哪条取决于执行顺序,结果不确定。得先聚合,比如子查询取每个用户最新的一条,再拿这份结果去更新。这一步省掉,数据当场看着对,过几天就乱了。

动手之前

先把 UPDATE 改成 SELECT,看会影响多少行,抽几个样本比对值。更新包在事务里,确认无误再提交;线上库先备份,或者建一张临时表留底。条件字段没索引的话,先加索引再说,否则一次更新就是一次全表扫描加锁。

还有个细节:来源表对应值是 NULL 时,SET 会把目标字段也写成 NULL,覆盖掉原有数据。不想被覆盖就套一层 COALESCE,或者在 WHERE 里先过滤掉。空字符串是另一回事,它不算 NULL,别混着看。

跨库的情况别硬写,中间表的正确率比一条复杂 SQL 更值得信任。来源表同时在被人改的话,先锁住或者读一份快照,别让更新读到半截数据。