让我们假设(出于理论上的原因)有一个更新后的行级别触发器,它会调用一个运行很长时间,5分钟的程序包。 在第一次执行仍然运行时,是否有任何方法可以再次执行此触发器?
如果您从会话A触发一次然后立即触发,会发生什么情况?
如果您从会话A触发一次触发器并且再次从会话B触发触发器会发生什么?
它们会并行运行还是在第一轮运行结束前等待5分钟?
如果我们在谈到这些问题时讨论行级别触发器或语句级别触发器会有什么不同吗?
感谢。
答案 0 :(得分:3)
这很容易测试,例如通过在过程中显示开始和结束时间。 (示例代码如下所示)。在会话A中,如果你这样做:
update <table> set ...;
update <table> set ...;
...触发器将针对每个受影响的行连续触发,作为该声明的一部分进行第一次更新;然后它将执行第二次更新,触发器将再次针对每个受影响的行触发。该过程只会在任何时间运行一次,但可能会更新,但每行都会受到影响。
更改为语句级触发器仍然只能让程序在任何时间运行一次,但如果更新影响多行,则每次更新会运行一次 - 每次更新一次,而不是每次更新一次。< / p>
但是如果你同时从会话B运行第二次更新(并且它触及表中的不同行),它将不受第一个会话的影响,然后你将运行该过程两次在同一时间。如果这是行级或语句级触发器并不重要;它会在每个会话中运行一次。
如果你想避免从不同的会话中运行两次的过程,你需要实现某种锁定机制,例如:在过程开始时更新另一个表中的控制标志。当会话提交或回滚时会释放,然后另一个会话将继续并获得自己的锁定。
有一个触发器调用一个花了那么长时间的过程似乎非常错误。听起来你在错误的地方有业务和应用逻辑......
演示代码,用于在会话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
该过程总共运行四次,时间戳显示过程执行是串行的。如果我们同时在会话A和会话B中运行特定于ID的更新,则会话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
并且会话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;
然后在会话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;
同时在会议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;
现在来自两个会话的呼叫也被序列化了。在会话A回滚之前,会话B更新的过程调用无法启动,因此如果会话A执行了多个操作,它可能会阻塞更长时间。
答案 1 :(得分:3)
触发器在条件发生时立即执行 - 因此,如果在会话A中触发两次,则触发器将在第一次出现时运行一次,然后再次运行第二次出现,因此执行将按顺序执行并且将执行总共10分钟。如果触发器从会话A触发一次,然后立即从会话B触发,则两次触发将同时运行。语句触发器只会在每个语句中触发一次,而不是每行触发一次(这是具有语句与行触发器的整点)。 BTW - 触发器运行5分钟是太长了 - 需要触发触发器,完成工作,并让h * ll不受影响。如果您需要运行需要运行五分钟的内容,则创建一个表,使用触发器将数据插入到表中,该表描述/定义需要处理的内容,并退出触发器。您需要有一个程序从表中提取数据并对其进行适当处理 - 但IMO在任何情况下都不应允许触发器运行五分钟。
因人而异。
分享并享受。
答案 2 :(得分:2)
从一个会话中,您一次只能触发一个动作,不是吗?除非是后台工作,否则没有多线程。所以第一个问题场景是不可能的。
两者将同时执行
如果第二个触发器来自不同的会话,则依次并行。
以上几点适用于所有情景。
答案 3 :(得分:1)
所有问题的答案 - 触发器总是立即执行,没有理由让它们同步。事实上,对于高度并发的系统来说,这将是一个巨大的问题。请参阅here Oracle RDBMS如何管理并发。
顺便说一下,在严重依赖触发器的情况下还有另一项重大挑战:mutating error