Oracle PL/SQL 触发器:更新语句后自动重置列值
Oracle PL/SQL Trigger: Reset column value automatically after an update statement
出于测试目的,我希望将以下场景自动化:
将我的 table 的列 Status 设置为值 closed (使用更新语句)
提交更新,以便其他用户可以看到 Status
的新值
等待1分钟
将我的 table 的列 Status 重置为其原始值 init (使用触发器)
提交更新,以便其他用户可以看到 Status
的原始值
我试过使用这个触发器,但它不起作用,我得到了
ORA-04091: table name is mutating, trigger/function may not see it
CREATE OR REPLACE TRIGGER RESET_COLUMN
AFTER UPDATE OF STATUS ON MY_TABLE
FOR EACH ROW
WHEN (NEW.STATUS != 'INIT')
BEGIN
DBMS_LOCK.SLEEP(60);
UPDATE MY_TABLE SET STATUS = 'INIT';
COMMIT;
END;
/
既然在trigger里面是不允许commit的,请问还有什么办法可以解决这个问题吗?使用基于事件的作业?
这似乎是个坏主意。 sleep()
触发器内部?那只会复合锁并占用资源。
相反,您可以使用视图或虚拟列。将 closeDate
作为列存储在 table 中(您可以根据需要使用触发器来设置它)。
alter table my_table
add new_status as (case when closeDate > sysdate - 1 / (24*60) then 'closed' else status end);
要运行将此作为一项工作,您需要一个可以调用的过程。我假设您想为特定记录而不是整个 table 执行此操作,因此是 ID 参数。
create or replace procedure reset_my_table_status
( p_id in number )
is
begin
update my_table
set status = 'INIT'
where id = p_id;
commit;
end;
/
然后,从您的触发器提交作业以在 60 秒后调用该过程:
CREATE OR REPLACE TRIGGER RESET_COLUMN
AFTER UPDATE OF STATUS ON MY_TABLE
FOR EACH ROW
WHEN (NEW.STATUS != 'INIT')
DECLARE
jn number;
pragma autonomous_transaction;
BEGIN
dbms_job.submit(jn
, what=>'reset_my_table_status('||:new.id||');'
, next_date => sysdate + 60/86400
);
commit;
END;
/
设置next_date
参数意味着作业将在六十秒内触发,因此不需要sleep()
调用。请记住,对于 运行 的作业,您需要 JOB_QUEUE_PROCESSES 初始参数的值 > 0。我们必须承诺提交作业;所以我们需要有一个自治事务,因为通常我们不能从触发器发出提交。
或者,您可以只构建一个过程(甚至是一个匿名块)。
create or replace procedure my_table_status_test
( p_id in number )
is
begin
update my_table
set status = 'MEH'
where id = p_id;
commit;
DBMS_LOCK.SLEEP(60);
update my_table
set status = 'INIT'
where id = p_id;
commit;
end;
/
然后 运行 您想要测试的任何 ID 的过程。
出于测试目的,我希望将以下场景自动化:
将我的 table 的列 Status 设置为值 closed (使用更新语句)
提交更新,以便其他用户可以看到 Status
的新值
等待1分钟
将我的 table 的列 Status 重置为其原始值 init (使用触发器)
提交更新,以便其他用户可以看到 Status
的原始值
我试过使用这个触发器,但它不起作用,我得到了
ORA-04091: table name is mutating, trigger/function may not see it
CREATE OR REPLACE TRIGGER RESET_COLUMN
AFTER UPDATE OF STATUS ON MY_TABLE
FOR EACH ROW
WHEN (NEW.STATUS != 'INIT')
BEGIN
DBMS_LOCK.SLEEP(60);
UPDATE MY_TABLE SET STATUS = 'INIT';
COMMIT;
END;
/
既然在trigger里面是不允许commit的,请问还有什么办法可以解决这个问题吗?使用基于事件的作业?
这似乎是个坏主意。 sleep()
触发器内部?那只会复合锁并占用资源。
相反,您可以使用视图或虚拟列。将 closeDate
作为列存储在 table 中(您可以根据需要使用触发器来设置它)。
alter table my_table
add new_status as (case when closeDate > sysdate - 1 / (24*60) then 'closed' else status end);
要运行将此作为一项工作,您需要一个可以调用的过程。我假设您想为特定记录而不是整个 table 执行此操作,因此是 ID 参数。
create or replace procedure reset_my_table_status
( p_id in number )
is
begin
update my_table
set status = 'INIT'
where id = p_id;
commit;
end;
/
然后,从您的触发器提交作业以在 60 秒后调用该过程:
CREATE OR REPLACE TRIGGER RESET_COLUMN
AFTER UPDATE OF STATUS ON MY_TABLE
FOR EACH ROW
WHEN (NEW.STATUS != 'INIT')
DECLARE
jn number;
pragma autonomous_transaction;
BEGIN
dbms_job.submit(jn
, what=>'reset_my_table_status('||:new.id||');'
, next_date => sysdate + 60/86400
);
commit;
END;
/
设置next_date
参数意味着作业将在六十秒内触发,因此不需要sleep()
调用。请记住,对于 运行 的作业,您需要 JOB_QUEUE_PROCESSES 初始参数的值 > 0。我们必须承诺提交作业;所以我们需要有一个自治事务,因为通常我们不能从触发器发出提交。
或者,您可以只构建一个过程(甚至是一个匿名块)。
create or replace procedure my_table_status_test
( p_id in number )
is
begin
update my_table
set status = 'MEH'
where id = p_id;
commit;
DBMS_LOCK.SLEEP(60);
update my_table
set status = 'INIT'
where id = p_id;
commit;
end;
/
然后 运行 您想要测试的任何 ID 的过程。