不能。oracle 的 returning into 只能返回更新后的值(new 值),不支持直接获取旧值(old 值);需通过 select ... for update + update ... returning 组合或 pl/sql 批量操作实现旧值获取。

RETURNING INTO 能否返回更新前的值?
不能。Oracle 的 RETURNING INTO 只能返回更新**后**的行值(即 NEW 值),它不提供直接获取旧值(OLD)的语法支持。这是常见误解,很多人尝试写 RETURNING old_col INTO :old_val,结果报错 ORA-00904: "OLD_COL": invalid identifier。
想拿到旧值,必须先 SELECT 再 UPDATE 吗?
不一定。最稳妥的方式确实是显式先查再更新,但存在竞态风险;更安全的做法是用 SELECT ... FOR UPDATE 锁住目标行,再执行 UPDATE ... RETURNING INTO 获取新值——这样你手上有旧值(来自 SELECT)和新值(来自 RETURNING),且全程行级锁保障一致性。
- 先执行:
SELECT col1, col2 INTO :old1, :old2 FROM t WHERE id = 1 FOR UPDATE - 再执行:
UPDATE t SET col1 = col1 + 1 WHERE id = 1 RETURNING col1, col2 INTO :new1, :new2 - 注意:两次语句需在同一个事务中,否则锁会释放,失去原子性
能否用触发器或物化视图绕过限制?
触发器可以捕获 :OLD 值并写入临时表或绑定变量,但无法直接传回客户端;物化视图不适用此场景。真正可行的替代方案只有两种:
- 使用
SELECT ... FOR UPDATE+UPDATE ... RETURNING组合(推荐) - 改用 PL/SQL 块,在
UPDATE前用SELECT ... BULK COLLECT INTO拿旧值,再更新并 RETURNING 新值——适合批量操作,但代码更重 - 如果只是审计用途,可考虑开启 Oracle 的
FLASHBACK VERSION QUERY查历史快照,但依赖 undo 保留时间,不实时
RETURNING INTO 的实际写法和常见坑
RETURNING INTO 必须与 DML(UPDATE/DELETE/INSERT)在同一语句中,且只能返回表达式结果,不能是子查询或复杂函数调用。容易出错的地方包括:
- 列数、类型、顺序必须严格匹配
INTO变量列表,否则报ORA-06502或ORA-06511 - 对空结果集执行时,若未处理
NO_DATA_FOUND异常,PL/SQL 会报错;纯 SQL(如 JDBC)则返回零行,INTO变量保持原值 - 不能在普通 SQL*Plus 或某些 ORM 中直接使用绑定变量接收 RETURNING 值,需通过支持该特性的驱动(如 Oracle JDBC 的
executeUpdate()+getUpdateCount()不够,要用prepareStatement配合registerOutParameter)
旧值这件事,本质上不是语法限制,而是 Oracle 设计上把“读旧”和“写新”视为两个分离动作——你得自己串起来,而且得串对时机和事务边界。











