Oracle异常处理中ROLLBACK与手动ROLLBACK的差异及回滚范围疑问
Oracle PL/SQL 事务回滚:块级回滚 vs 全事务回滚
首先咱们先理清你观察到的现象背后的逻辑,再解决你的核心问题——怎么只回滚到PL/SQL块开头,保留块外的DML操作。
为什么两种回滚行为不一样?
1. 事务的边界问题
Oracle的事务是从第一个DML语句开始,直到你执行COMMIT或ROLLBACK才结束。你在PL/SQL块之前执行的insert into mytable values(1);,和块内的所有插入操作,都属于同一个未提交的事务。
2. 未捕获异常的自动回滚
当PL/SQL块中发生未被捕获的异常(比如你的例子里的唯一键冲突),Oracle会自动回滚当前PL/SQL块内执行的所有DML操作,但不会触及块外的事务操作。这就是为什么自动处理异常时,表中还留着1——块外的插入没被回滚,块内的insert 3、insert 2、insert 1都被撤销了。
3. 手动ROLLBACK的行为
手动执行ROLLBACK是针对整个事务的,它会回滚从上一次COMMIT之后的所有操作,不管这些操作是在PL/SQL块内还是块外。所以你手动回滚后,表就空了——块外的1和块内的所有插入都被撤销了。
怎么实现回滚到PL/SQL块开头?
答案是用保存点(SAVEPOINT)。你可以在PL/SQL块的开头创建一个保存点,然后在异常处理中回滚到这个保存点,这样就只会撤销块内的操作,保留块外的DML。
修改后的代码示例:
create table mytable (num int not null primary key); insert into mytable values(1); -- 这个操作会被保留下来 begin -- 在块开头创建保存点,标记要回滚的起点 SAVEPOINT start_of_plsql_block; insert into mytable values(3); begin insert into mytable values(2); insert into mytable values(1); -- 触发唯一键异常 end; exception when dup_val_on_index then -- 回滚到块开头的保存点,只撤销块内的所有操作 ROLLBACK TO start_of_plsql_block; end; /
执行这段代码后,mytable里的数据会是1——块外的插入被保留,块内的所有操作都被回滚了。
异常处理器中的ROLLBACK vs 手动ROLLBACK的差异
- 如果你的异常处理器里写的是
ROLLBACK;(不带保存点),那和手动执行的ROLLBACK完全一样,都会回滚整个事务,把块外的操作也撤销掉。 - 但如果写的是
ROLLBACK TO SAVEPOINT xxx;,就只会回滚到指定的保存点,保留保存点之前的所有事务操作(也就是你块外的insert 1)。 - 另外,当PL/SQL块发生未捕获的异常时,Oracle的自动回滚是一种隐式的块级回滚——它相当于自动回滚到块开始的一个隐式保存点,但这个隐式保存点你没法手动引用,只能由Oracle在异常发生时触发。
额外注意点
- 保存点只在当前事务内有效,一旦你执行了
COMMIT或ROLLBACK(全事务回滚),所有保存点都会消失。 - 嵌套块中也可以使用保存点,你可以在子块中回滚到外层块创建的保存点,灵活控制回滚范围。
内容的提问来源于stack exchange,提问作者sql_dummy
相关产品推荐
相关产品推荐

