先给个结论:在动手执行 UPDATE 语句之前,核心原则就是“先查后改”,但这里面有不少细节和坑,不同数据库的处理方式也不一样,下面来逐一拆解。

为什么不能直接用 UPDATE 加 WHERE 来预览?
道理非常简单,UPDATE 这个语句天生就不返回被修改的行(除了一些支持 RETURNING 的数据库)。你敲下回车,改动就生效了,根本来不及确认。最稳妥的办法,就是把 UPDATE 语句里的 WHERE 条件,原封不动地复制到 SELECT 语句里去查一遍。但这里有个关键前提:条件逻辑必须完全一致,否则预览结果和实际影响范围就是两码事。
实际工作中最容易翻车的点包括:复制时漏了括号,搞混了 AND/OR 的优先级,或者在 UPDATE 里用了函数(比如 UPPER(name)),结果在 SELECT 里给忘了。最省事的做法,就是直接从 UPDATE 语句里把 WHERE 子句整个摘出来,粘贴过去后只把开头的 UPDATE 改成 SELECT *。
PostgreSQL 的杀手锏:用 RETURNING 同时看改前改后
PostgreSQL 在这方面确实方便,它支持 RETURNING 子句,可以在执行 UPDATE 的同时,把被修改的行返回给你。这比先 SELECT 再 UPDATE 靠谱得多,因为能有效避免并发修改导致的“预览与实际不一致”问题。
- 基本用法:
UPDATE users SET status = 'active' WHERE id IN (1,2,3) RETURNING id, name, status;—— 这会直接返回修改后的字段值。 - 想看修改前的值? PostgreSQL 不支持直接
RETURNING OLD.*,所以得曲线救国。一个经典做法是先用 CTE(公用表表达式)把旧数据存起来,再执行更新:
WITH old_data AS ( SELECT id, name, status FROM users WHERE id IN (1,2,3))UPDATE users SET status = 'active' WHERE id IN (SELECT id FROM old_data)RETURNING (SELECT json_agg(old_data.*) FROM old_data);
必须提醒一句:RETURNING 返回的内容只在当前事务内有效,而且它并不会跳过实际的写操作。所以,它不是传统意义上的“预览”,更准确的描述应该是“带反馈的执行”。
MySQL 和 SQL Server:没有 RETURNING,安全预览靠事务
这两者没有 RETURNING,那就只能老老实实分两步走:先 SELECT,再 UPDATE。中间的桥梁就是事务隔离,这是保证前后一致性的唯一办法。
- 开启事务(
BEGIN TRANSACTIONfor SQL Server,START TRANSACTIONfor MySQL) - 执行预览查询:
SELECT id, name, email FROM users WHERE created_at < '2023-01-01'; - 确认无误后执行
UPDATE - 检查影响行数(
SELECT ROW_COUNT();for MySQL,SELECT @@ROWCOUNT;for SQL Server) - 最后提交(
COMMIT)或回滚(ROLLBACK)—— 这一步千万别忘,否则事务锁一直挂着,会给别人造成困扰。
这里有个很容易掉进去的坑:没开事务就直接 SELECT。结果你预览完,正准备执行 UPDATE 的这当口,别人改了数据,导致你的更新范围变得不可控。再或者,开完事务忘了关,把表锁住了。
当 WHERE 条件里藏着子查询或函数时,预览要加倍小心
像 UPDATE orders SET processed = true WHERE id IN (SELECT order_id FROM logs WHERE event = 'paid') 这种语句,就不能简单地把 WHERE 条件复制到 SELECT 里。因为子查询可能返回空值、重复值,或者受权限限制。
- 先把子查询单独跑一遍:
SELECT order_id FROM logs WHERE event = 'paid';,确认返回结果是否合理。 - 然后把子查询的结果直接硬编码到预览查询里:
SELECT * FROM orders WHERE id IN (101, 102, 105);。这样更直观可控。 - 如果子查询特别慢或者不稳定,可以加个
LIMIT先测试前几条:SELECT * FROM orders WHERE id IN (SELECT order_id FROM logs WHERE event = 'paid' LIMIT 10); - 涉及时间函数(如
NOW()、CURRENT_DATE)的 WHERE 条件要特别当心,预览和执行之间哪怕只差 1 秒,结果都可能不一样。这种场景,建议直接用具体的时间字面量来测试。
真正棘手的,是那些嵌套多层、带窗口函数或依赖会话变量的条件。这种情况下,预览只能无限逼近,无法做到 100% 精确。别为了省那几秒钟,手动生成一个测试数据集来验证逻辑,这才是真正靠谱的做法。
