oracle存储过程默认不自动提交事务,未显式commit时dml仅在当前会话可见;退出连接会隐式提交,ddl会立即触发隐式提交,循环内commit性能差且无法整体回滚,异常时需显式rollback,savepoint支持局部回滚。
存储过程中不写commit,事务就一直没提交
oracle 存储过程默认不自动提交事务。只要没显式执行 commit,所有 dml(insert/update/delete)操作都只在当前 session 的事务上下文中暂存,其他 session 看不到变更,你自己也能用 select 查到“已改”的结果,但这只是脏读(当前 session 可见)。
关键点:
- 如果过程执行完没
COMMIT,也没ROLLBACK,你退出 SQL*Plus 或断开连接(DISCONNECT),Oracle 会**隐式提交**——这是很多人踩坑的根源; - 但如果在同一个 session 中接着执行
ROLLBACK,就能撤回全部未提交操作; - DDL(如
CREATE TABLE)在过程里一执行,就会**立即触发隐式 COMMIT**,前面的 DML 就再也回滚不了了。
显式 COMMIT 放在循环里,每轮都落库
常见批量插入场景中,有人把 COMMIT 写在循环体内,比如:
FOR i IN 1..10000 LOOP INSERT INTO test1 VALUES(i, 'leng' || i); COMMIT; -- 每插一条就提交一次 END LOOP;
这会导致:
- 每次
COMMIT都要刷盘、写 redo、释放锁,性能极差; - 一旦中途报错(比如主键冲突),前面已
COMMIT的数据无法整体回滚; - 如果在
COMMIT后加了ROLLBACK(如IF i = 20 THEN ROLLBACK; EXIT;),那第 20 次的INSERT已提交,ROLLBACK对它无效,只回滚第 20 次之后未提交的部分——但通常这时已经晚了。
异常分支里必须配对使用 ROLLBACK
存储过程里用 EXCEPTION 捕获错误时,不能只靠 Oracle 自动回滚:它只回滚当前语句级失败(如唯一约束),但不会自动回滚整个事务上下文。
正确做法是手动加 ROLLBACK:
BEGIN
INSERT INTO employees (...) VALUES (...);
UPDATE departments SET manager_id = ...;
COMMIT;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
ROLLBACK; -- 显式回滚,避免残留未提交状态
RAISE_APPLICATION_ERROR(-20001, '员工ID重复');
END;
注意:
-
ROLLBACK必须出现在EXCEPTION块里,且最好在RAISE之前; - 如果过程里混合了
COMMIT和ROLLBACK,它们各自生效范围互不影响——COMMIT之前的改动能落库,COMMIT和ROLLBACK之间的修改会被撤掉; - 没有
COMMIT的过程,异常后其实也会自然回滚(因为 session 未提交就结束了),但显式写出来更可控、可读性更强。
SAVEPOINT 是局部回滚的唯一可靠方式
当一个存储过程要做多步 DML,又想在某步失败时只撤回最近几步(而非全部),必须用 SAVEPOINT:
BEGIN INSERT INTO orders VALUES (...); -- 步骤1 SAVEPOINT sp_order; <p>INSERT INTO order_items VALUES (...); -- 步骤2 SAVEPOINT sp_items;</p><p>UPDATE inventory SET qty = qty - 1 WHERE ...; -- 步骤3 IF stock_insufficient THEN ROLLBACK TO sp_items; -- 只撤步骤3,保留订单头和明细 END IF; END;</p>
要点:
-
SAVEPOINT不消耗资源,可以多层嵌套,但名字不能重复; -
ROLLBACK TO SAVEPOINT后,该保存点之后定义的其他保存点自动失效; - 不能跨 session 使用保存点,也不能在自治事务(
AUTONOMOUS_TRANSACTION)里共享。
最易被忽略的是:DDL 语句(CREATE/ALTER/DROP)在存储过程中执行时,会立刻隐式提交——此时前面所有 DML 都不可逆。哪怕你后面写了 ROLLBACK,也撤不回来。











