【问题标题】:Oracle Trigger Parallel ExecutionOracle 触发器并行执行
【发布时间】:2013-08-09 01:27:11
【问题描述】:

让我们假设(出于理论上的原因)有一个更新后的行级触发器,它调用一个运行很长时间(5 分钟)的包的过程。 有没有什么办法可以在第一次执行仍在运行时第二次执行此触发器?

如果您从会话 A 触发一次触发器然后立即再次触发会发生什么?

如果您从会话 A 触发一次触发器并立即从会话 B 再次触发会发生什么?

它们是并行运行还是在第一次运行完成前等待 5 分钟?

当我们谈到这些问题时,如果我们谈论行级触发器或语句级触发器会有什么不同吗?

谢谢。

【问题讨论】:

  • 您可以(理论上)将作业推送到触发器内的队列中,以解决大量长期运行的执行问题(理论上)。

标签: oracle plsql


【解决方案1】:

这很容易测试,例如通过在程序中显示开始和结束时间。 (演示的示例代码如下)。在会话 A 中,如果您这样做:

update <table> set ...;
update <table> set ...;

...触发器将为每个受影响的行触发,作为该语句的一部分,对于第一次更新;然后它将执行第二次更新,触发器将为每个受影响的行再次触发。该过程在任何时候都只会运行一次,但是可能会更新您所做的更新,但是每个行都会影响许多行。

更改为语句级触发器仍然只会让该过程在任何时候运行一次,但如果更新影响不止一行,它将运行更少的总次数 - 每次更新一次,而不是每个更新的行一次。

但是,如果您同时从会话 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;

现在来自两个会话的调用也被序列化了。会话 B 更新的过程调用在会话 A 回滚之前无法启动,因此如果会话 A 执行多个操作,它可能会阻塞更长时间。

【讨论】:

  • 谢谢,很全面的例子
【解决方案2】:

触发器在条件发生时立即执行 - 因此,如果在会话 A 中触发两次,触发器将在第一次出现时运行一​​次,然后在第二次出现时再次运行,因此执行将按顺序执行并需要一个总共10分钟。如果触发器从会话 A 触发一次,然后立即从会话 B 触发,则两个事件将同时运行。每个语句只会触发一次语句触发器,而不是每行触发一次(这是拥有语句与行触发器的全部意义)。顺便说一句 - 5 分钟对于触发器运行来说太长了 - 触发器需要被触发,完成他们的工作,并让 h*ll 不碍事。如果你有需要运行五分钟的东西,那么创建一个表,使用触发器将数据插入到描述/定义需要处理的表中,然后退出触发器。您需要有一个程序从表中提取数据并对其进行适当处理 - 但 IMO 在任何情况下都不应允许触发器运行五分钟。

YMMV。

分享和享受。

【讨论】:

    【解决方案3】:
    1. 从一个会话中,您一次只能触发一个操作,不是吗?除非是后台作业,否则没有多线程。所以第一个问题场景是不可能的。

    2. 两者将同时执行

    3. 如果第二个触发器来自不同的会话,则按顺序并行。

    4. 以上几点适用于所有场景。

    【讨论】:

      【解决方案4】:

      所有问题的答案 - 触发器总是立即执行,根本没有理由让它们同步。事实上,这对于高度并发的系统来说将是一个巨大的问题。请参阅here Oracle RDBMS 如何管理并发。

      顺便说一句,严重依赖触发器还有另一个重大挑战:mutating error

      【讨论】:

        猜你喜欢
        • 2015-06-06
        • 2015-07-17
        • 2021-04-30
        • 2015-10-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-11-25
        相关资源
        最近更新 更多