当需要在主业务事务中执行一个独立操作,并确保该操作的成功或失败不影响主事务时,可使用自治事务PRAGMA AUTONOMOUS_TRANSACTION。自治事务会启动一个完全独立的事务,拥有自己的提交和回滚逻辑。该特性适合记录审计日志或错误信息等场景:即使主业务流程因错误而需要整体回滚,通过自治事务记录的日志依然能够被成功保存,确保操作的可追溯性。
工作原理
自治事务的核心在于“隔离”。调用包含 PRAGMA AUTONOMOUS_TRANSACTION 的程序时,会暂停当前的父事务,并启动一个全新的、独立的子事务。
隔离性:自治事务看不到父事务中未提交的更改,同样,父事务也看不到自治事务中未提交的更改。
独立提交/回滚:自治事务内的
COMMIT或ROLLBACK只影响其自身,完全独立于父事务的最终COMMIT或ROLLBACK。连接消耗:每次调用自治事务会在后台新建一个独立的数据库连接。当自治事务结束时,该连接被释放。
适用范围
PRAGMA AUTONOMOUS_TRANSACTION 指令可以用于以下SPL程序单元的声明部分:
独立的存储过程和函数。
匿名块。
包中声明的存储过程和函数。
触发器。
对象类型方法。
在Oracle语法兼容 2.0版本中,出于系统稳定性与事务一致性的考虑,目前不支持在触发器中使用自治事务。
PRAGMA AUTONOMOUS_TRANSACTION指令必须放在声明区域的起始位置。-- 存储过程中使用自治事务的正确语法 CREATE OR REPLACE PROCEDURE procedure_name IS PRAGMA AUTONOMOUS_TRANSACTION; BEGIN -- 业务逻辑 COMMIT; END; -- 触发器中使用自治事务的正确语法,只在兼容 Oracle 1.0使用 CREATE OR REPLACE TRIGGER trigger_name AFTER INSERT OR UPDATE OR DELETE ON table_name DECLARE PRAGMA AUTONOMOUS_TRANSACTION; BEGIN -- 业务逻辑 COMMIT; END;
应用场景
自治事务匿名块记录审计日志
此场景确保无论主业务操作最终是提交还是回滚,相关的审计日志都能被可靠地记录下来。
创建员工表
emp和审计日志表empauditlog。CREATE TABLE emp ( emp_id INT, emp_name VARCHAR(50), job VARCHAR(50) ); CREATE TABLE empauditlog ( audit_date DATE, audit_user VARCHAR2(20), audit_desc VARCHAR2(100) );开启一个事务,插入数据,然后回滚该事务。
BEGIN; INSERT INTO emp (emp_id, emp_name, job) VALUES (101, '张三', '工程师'); DECLARE PRAGMA AUTONOMOUS_TRANSACTION; BEGIN INSERT INTO empauditlog VALUES (SYSDATE, USER, 'Added employee(s)'); COMMIT; END; ROLLBACK;由于主事务执行了
ROLLBACK,emp表中的数据插入操作已被撤销,数据未持久化。而empauditlog表通过自治事务独立提交,审计日志成功写入,不受主事务回滚影响。-- 查询 emp 表,应为空,因为主事务已回滚 SELECT * FROM emp WHERE emp_id = 101; -- 查询 empauditlog 表,应包含一条审计记录,因为是自治事务并已提交 SELECT * FROM empauditlog;
自治事务存储过程记录失败日志
本场景用于实现:当库存充足时,正常创建订单;当库存不足时,回滚订单操作,但仍记录用户的“下单尝试”日志。
建立
orders(订单)、inventory(库存)和operation_log(操作日志)三张表。初始化库存为 5,当下单数量为 10 时,触发库存不足异常,并记录失败日志。-- 1. 订单表(主业务数据) CREATE TABLE orders ( order_id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, user_id NUMBER, product_id NUMBER, qty NUMBER, order_time TIMESTAMP DEFAULT SYSDATE ); -- 2. 库存表 CREATE TABLE inventory ( product_id NUMBER PRIMARY KEY, stock NUMBER ); -- 3. 操作日志表(用于自治事务写入) CREATE TABLE operation_log ( log_id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, user_id NUMBER, product_id NUMBER, action VARCHAR2(50), -- 如 'ORDER_ATTEMPT' status VARCHAR2(20), -- 'SUCCESS' / 'FAILED' reason VARCHAR2(200), -- 失败原因 log_time TIMESTAMP DEFAULT SYSDATE ); -- 4. 初始化数据 INSERT INTO inventory (product_id, stock) VALUES (1001, 5); -- 商品1001 只有5件创建支持自治事务的存储过程,确保在主事务回滚时仍能持久化操作日志。
CREATE OR REPLACE PROCEDURE log_order_attempt( p_user_id IN NUMBER, p_product_id IN NUMBER, p_reason IN VARCHAR2 ) IS PRAGMA AUTONOMOUS_TRANSACTION; -- 过程内部声明区 BEGIN INSERT INTO operation_log ( user_id, product_id, action, status, reason, log_time ) VALUES ( p_user_id,p_product_id,'ORDER_ATTEMPT','FAILED',p_reason,SYSDATE ); COMMIT; -- 独立提交,不受主事务影响 END;用户尝试下单,因库存不足失败,自治事务确保日志独立提交。
DECLARE v_user_id NUMBER := 123; v_product_id NUMBER := 1001; v_order_qty NUMBER := 10; v_stock NUMBER; BEGIN SELECT stock INTO v_stock FROM inventory WHERE product_id = v_product_id FOR UPDATE; IF v_stock < v_order_qty THEN log_order_attempt( p_user_id => v_user_id, p_product_id => v_product_id, p_reason => 'Insufficient stock: need ' || v_order_qty || ', available ' || v_stock ); RAISE_APPLICATION_ERROR(-20001, '库存不足,订单创建失败'); END IF; INSERT INTO orders (user_id, product_id, qty) VALUES (v_user_id, v_product_id, v_order_qty); UPDATE inventory SET stock = stock - v_order_qty WHERE product_id = v_product_id; COMMIT; EXCEPTION WHEN OTHERS THEN ROLLBACK; RAISE; END;验证结果:订单表为空,库存未变,因主事务已回滚;而操作日志中已有记录,得益于自治事务的成功提交。
SELECT * FROM orders; SELECT * FROM inventory; SELECT * FROM operation_log;
操作建议
连接消耗风险
每个自治事务在执行期间会消耗一个独立的数据库连接。 在高并发场景下,滥用自治事务可能迅速耗尽数据库的 max_connections,导致新的连接请求失败,从而引发服务中断。
风险场景:在循环中或高频调用的函数里使用自治事务。
嵌套风险:自治事务可以嵌套,每嵌套一层就会多占用一个连接。例如,一个嵌套了3层的自治事务会话会临时占用4个连接(1个主会话 + 3个自治事务)。
容量规划:使用前,请评估自治事务对数据库连接数的额外需求,并相应调整
max_connections参数。建议进行压力测试。
推荐做法
显式提交或回滚:在自治事务代码块的末尾使用
COMMIT或ROLLBACK,以明确事务的结束方式。保持简短:自治事务应只包含必要的、快速完成的操作(如
INSERT一条日志),避免执行耗时长的复杂查询。用于特定场景:仅用于审计、日志记录、更新统计信息等与主事务逻辑解耦的辅助性任务。
兼容性说明
PolarDB PostgreSQL版(兼容Oracle) 的自治事务实现与 Oracle 数据库存在一个关键行为差异,这可能在数据库迁移时引入潜在风险。
特性
| Oracle 行为 | PolarDB PostgreSQL版(兼容Oracle)行为 | 风险说明 |
未显式提交/回滚 | 抛出 | 隐式提交。如果代码块正常结束且没有 | 高风险。这种行为可能导致数据在非预期的情况下被提交,破坏事务的原子性。为保证代码在不同环境下的行为一致性,强烈建议始终显式地使用 |
常见问题
如何排查 “sorry, too many clients already” 错误?
此错误表明数据库的活动连接数已达到 max_connections 上限。如果怀疑是自治事务引起的,可按以下步骤排查:
检查当前连接数和最大连接数配置。
-- 查看总连接数 SELECT count(*) FROM pg_stat_activity; -- 查看当前配置的最大连接数 SHOW max_connections;识别长时间运行的自治事务。使用以下查询找出执行时间超过5分钟的自治事务相关查询。
SELECT pid, query_start, now() - query_start AS duration, query FROM pg_stat_activity WHERE state = 'active' AND query ILIKE '%PRAGMA AUTONOMOUS_TRANSACTION%' AND now() - query_start > interval '5 minutes';解决方案:
优化代码:检查是否存在循环调用或长时间运行的自治事务,并进行优化。
增加连接数:如果使用合理但连接仍然不足,评估后可适当增加
max_connections的值。设置超时:为会话设置
statement_timeout,以自动终止长时间运行的查询,防止连接被永久占用。