我们假设(出于理论上的原因)存在一个更新后行级触发器,该触发器调用运行5分钟的长时间包的过程。
在第一次执行仍然运行时,有什么办法可以再次执行该触发器?
如果您一次从 session A触发触发器,然后立即再次触发,将会发生什么情况?
如果您一次从 session A触发触发器,又立即从 session B触发触发器,将会发生什么情况?
他们会并行运行还是在第一轮运行之前等待5分钟?
如果我们在谈到这些问题时谈论行级触发器或语句级触发器,会有所不同吗?
谢谢。
最佳答案
这相当容易测试,例如通过在过程中显示开始时间和结束时间。 (下面演示的示例代码)。在 session A中,如果您这样做:
update <table> set ...;
update <table> set ...;
...作为该语句的一部分,触发器将为每个受影响的行依次触发,以进行第一次更新;然后它将执行第二次更新,触发器将为每个受影响的行再次触发。该过程在任何时候都只会运行一次,但是可能会更新,并且每一行都会影响很多行。
更改为语句级触发器仍将使该过程在任何时候都只运行一次,但是如果更新影响多于一行,则该过程在整体上运行的次数会更少-每个更新一次,而不是每个更新行一次。
但是,如果您同时从 session B运行第二个更新(并且它接触表中的不同行),则它不会受到第一个 session 的影响,然后您将使该过程同时运行两次。不管是行级触发器还是语句级触发器;在每个 session 中它将运行一次。
如果您想避免该过程在不同的 session 中运行两次,则需要实现某种锁定机制,例如在过程开始时更新另一个表中的控制标志。该 session 在提交或回滚时将被释放,然后另一个 session 将继续并获得其自己的锁。
但是,让触发器调用需要花费这么长时间的过程似乎是非常错误的。听起来您在错误的地方有业务和应用程序逻辑。
演示代码,用于在 session A中运行两次:
create table t42 (id number);
insert into t42 values (1);
insert into t42 values (2);
create package p42 as
procedure busy;
end p42;
/
create package body p42 as
procedure busy is
x number;
begin
dbms_output.put_line('Started ' || systimestamp);
for i in 1..200000 loop -- takes about 4s on my system
select 1 into x from dual;
end loop;
dbms_output.put_line('Finished ' || systimestamp);
end busy;
end p42;
/
create trigger trig42
after update on t42
for each row
begin
p42.busy;
end;
/
然后运行两个更新:
update t42 set id = id + 1;
update t42 set id = id + 1;
获取输出:
2 rows updated.
Started 08-AUG-13 18.17.49.184770000 +01:00
Finished 08-AUG-13 18.17.53.041916000 +01:00
Started 08-AUG-13 18.17.53.042109000 +01:00
Finished 08-AUG-13 18.17.56.841698000 +01:00
2 rows updated.
Started 08-AUG-13 18.17.57.027777000 +01:00
Finished 08-AUG-13 18.18.01.172613000 +01:00
Started 08-AUG-13 18.18.01.172730000 +01:00
Finished 08-AUG-13 18.18.04.963734000 +01:00
该过程总共运行四次,时间戳显示该过程是串行执行的。如果我们在 session A和 session B中同时运行特定于ID的更新,则 session A会看到以下情况:
update t42 set id = id + 1 where id = 1;
1 rows updated.
Started 08-AUG-13 18.21.09.098922000 +01:00
Finished 08-AUG-13 18.21.16.355744000 +01:00
session B看到以下内容:
update t42 set id = id + 1 where id = 2;
Started 08-AUG-13 18.21.09.500643000 +01:00
Finished 08-AUG-13 18.21.16.204506000 +01:00
1 row updated.
如您所见,时间戳重叠,因此该过程同时运行两次。
添加一个非常简单的锁定机制:
create table t43 (id number);
insert into t43 values(null);
create package body p42 as
procedure busy is
x number;
begin
update t43 set id = 1;
dbms_output.put_line('Started ' || systimestamp);
...
end p42;
然后在 session A中:
update t42 set id = id + 1 where id = 1;
1 rows updated.
Started 08-AUG-13 18.22.35.058741000 +01:00
Finished 08-AUG-13 18.22.39.288557000 +01:00
rollback;
同时在 session B中:
update t42 set id = id + 1 where id = 2;
Started 08-AUG-13 18.22.40.385602000 +01:00
Finished 08-AUG-13 18.22.43.995601000 +01:00
1 row updated.
rollback;
现在,来自两个 session 的调用也被序列化了。在 session A回滚之前, session B更新的过程调用无法启动,因此,如果 session A执行了多个操作,则可能会阻塞更长的时间。