某业务通过存储过程来实现,过程中调用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后直接退出程序,但在执行程序时始终未能获取提交后的函数返回值,一直运行到循环结束;
而查询 T1 表数据量已经完成了数据写入,并且 T3 的 status 状态也已经修改为 0 ,说明 SP_THREAD 过程执行并没有问题
那么由于 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,因此也需要等待一轮循环);
参数 TRX_VIEW_POLICY 默认为0,动态系统级,该参数用于设置事务可见性视图策略。
0:脚本中的SQL语句不更新事务可见性视图;
1:脚本中的SQL语句更新事务可见性视图;
将参数调整为 1 后问题解决。
达梦在PL/SQL程序中也存在事务隔离控制的参数,因此要想在此类场景中完全实现事务的"读已提交"需要关注参数 TRX_VIEW_POLICY 的设置。
文章
阅读量
获赞
