mysql中set a=b, b=a未交换成功,因其单表update从左到右执行且右侧取已更新值;而postgresql等数据库先取原始快照,故能正确交换。

MySQL中SET A=B, B=A为什么没交换成功
因为MySQL的单表UPDATE是**从左到右顺序执行**,且右侧表达式引用的是“当前已更新后的值”,不是原始快照。写SET c1 = c2, c2 = c1时:
第一步把c1设为原c2值;
第二步的c1已是新值,所以c2被设成和c1一样的值——两列最终都等于原c2。
PostgreSQL和SQL Server为什么能直接用SET A=B, B=A
它们在UPDATE开始前会先对所有右侧表达式求值,形成一份“旧值快照”。c1 = c2和c2 = c1中的c1、c2都指向原始值,所以能真正交换。
这种行为符合SQL标准,但MySQL明确不遵循该标准(仅限单表更新)。
MySQL里安全交换两列的三种实操方式
必须绕过“左→右覆盖”问题:
- 用子查询强制读取原始值:
UPDATE t1 SET c1 = (SELECT c2 FROM t1 AS _t WHERE _t.id = t1.id), c2 = (SELECT c1 FROM t1 AS _t WHERE _t.id = t1.id) WHERE id = 1; - 分两步+事务:先
UPDATE t1 SET c1 = c2 + c1,再UPDATE t1 SET c2 = c1 - c2, c1 = c1 - c2(仅适用于数值,且需注意溢出) - 用临时列过渡(最稳):
ALTER TABLE t1 ADD COLUMN _tmp VARCHAR(255); UPDATE t1 SET _tmp = c1, c1 = c2, c2 = _tmp; ALTER TABLE t1 DROP COLUMN _tmp;
ORM场景下最容易被忽略的坑
Django、SQLAlchemy等默认把多列赋值拆成多次UPDATE,即使你写update(...).values(a=b, b=a),底层也会发两条语句——第一句改完a,第二句的b=a就拿不到原始a了。
此时必须显式使用text()或exec_driver_sql()绕过ORM,拼写原生SQL;否则交换必然失败,且无报错提示。










