注册
PL/SQL中的事务可见性问题
专栏/技术分享/ 文章详情 /

PL/SQL中的事务可见性问题

PYZ 2026/05/22 249 1 0
摘要

问题背景

某业务通过存储过程来实现,过程中调用dbms_job调度一个后台并行任务,执行程序中通过判断事务执行状态,循环等待后台任务执行完毕,然而实测中发现后台任务已经执行完,执行程序却一直在循环直到loop结束,程序执行流程如下:

执行流程

主会话:循环检查后台任务状态(t3.status=1 表示未执行,0 表示执行完毕)
若状态未执行,调用 DBMS_JOB.SUBMIT 提交一个后台并行任务
主会话进入循环,每 5 秒轮询一次任务状态
当后台任务完成并将状态更新为 0 时,主会话退出循环

存储过程代码(简化版)

--创建基表 create table t1 as select * from dba_objects where 1=2; create table t2 as select * from dba_objects; create table t3(status varchar2(2)); insert into t3 values('1'); --插入状态初始值 1 commit; --创建包 CREATE OR REPLACE PACKAGE PKC_JOB_TEST AS PROCEDURE SP_MAIN; PROCEDURE SP_THREAD(INDATE VARCHAR2); PROCEDURE SET_STATUS(I_STATUS VARCHAR2); FUNCTION F1 RETURN T3.STATUS%TYPE; END; / CREATE OR REPLACE PACKAGE BODY PKC_JOB_TEST AS PROCEDURE SP_MAIN AS v_job number; v_cmd varchar2(200); cnt number; BEGIN v_cmd := 'pkc_job_test.sp_thread('||'''20251028'''||');'; dbms_job.submit(v_job,v_cmd); END SP_MAIN; PROCEDURE SP_THREAD(indate varchar2) AS BEGIN INSERT INTO t1 SELECT * FROM t2 WHERE to_char(created,'yyyymmdd') = indate; set_status(i_status => '0'); COMMIT; END SP_THREAD; PROCEDURE SET_STATUS(i_status varchar2) AS BEGIN UPDATE t3 SET status = i_status; END SET_STATUS; FUNCTION F1 RETURN t3.status%type AS flag t3.status%type; BEGIN SELECT status INTO flag FROM t3; RETURN flag; END F1; END PKC_JOB_TEST; /

调用程序

declare v_status t3.status%type; cnt number; begin v_status := pkc_job_test.f1; execute immediate 'truncate table t1'; dbms_output.enable(); if (v_status = 1) then pkc_job_test.sp_main(); --调用主过程,主过程中发起后台任务调用过程SP_THREAD进行插入数据,插入完成后修改状态标志为0 end if; for i in 1..5 loop v_status := pkc_job_test.f1; select count(*) into cnt from t1; dbms_output.put_line('T1 cnt is :'||cnt||' v_status is :'||v_status); if (v_status = 0) then exit; else dbms_lock.sleep(5); -- PRINT I; end if; end loop; end;

问题分析

参数 TIMER_TRIG_CHECK_INTERVAL 控制了DBMS_JOB 后台任务的调度时间,默认为60s(取值范围1-60),即当 DBMS_JOB.SUBMIT 执行后需等待60s后执行,该参数已经修改为1,即等待1s后执行,而 SP_THREAD 过程中的插入行数仅有817条行记录,因此在for循环中预期应该输出 cnt = 817,v_status=0,if中判断v_status = 0后直接退出程序,但在执行程序时始终未能获取提交后的函数返回值,一直运行到循环结束;
image.png
而查询 T1 表数据量已经完成了数据写入,并且 T3 的 status 状态也已经修改为 0 ,说明 SP_THREAD 过程执行并没有问题
image.png
那么由于 DBMS_JOB.SUBMIT 是后台运行的并发任务,数据写入的过程会另外开启一个会话通过系统触发器运行,出现这种现象说明虽然数据库是读已提交机制,但在当前运行程序的主会话中并没有查到已提交的事务信息,很有可能是后台事务对当前事务不可见,修改函数进行验证;

FUNCTION F1 RETURN t3.status%type AS flag t3.status%type; BEGIN COMMIT; -- 查询前提交一次当前事务, SELECT status INTO flag FROM t3; RETURN flag; END F1;

修改后发现在主会话的执行程序也提交了当前查询事务后,经过了一次循环就查询到了后台事务的结果(这里由于发起后台执行到循环查询状态的时间不到1s,因此也需要等待一轮循环);
image.png

问题原因

参数 TRX_VIEW_POLICY 默认为0,动态系统级,该参数用于设置事务可见性视图策略。
0:脚本中的SQL语句不更新事务可见性视图;
1:脚本中的SQL语句更新事务可见性视图;
将参数调整为 1 后问题解决。

总结

达梦在PL/SQL程序中也存在事务隔离控制的参数,因此要想在此类场景中完全实现事务的"读已提交"需要关注参数 TRX_VIEW_POLICY 的设置。

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服