0

所以我有两张表:JOBS 和TASKS。

TASKS 表有一个 TAKS_STATUS 列,它存储状态的进展(例如,“提交”、“已提交”、“已授权”、“已完成”)。

JOBS 表有一个JOB_STATUS 列,它是TASKS 表中TASK_STATUS 列的汇总(即最小状态)。JOBS 表还有一个 TASK_COUNT 值,其中包含与 Job 关联的 TASKS 的数量。

作业可以有一个或多个任务:每个表中的 JOB_ID 链接它们。

在 DB2 中,我有一系列简单的触发器来汇总这种状态;这是中间的一个:

create or replace trigger JOB_AUTHORED
  after update of TASK_STATUS on TASKS
  referencing NEW as N
  for each row
  when (TASK_STATUS = 'Authored')
    update JOBS
      set JOB_STATUS = 'Authored'
      where JOBS.JOB_ID = N.JOB_ID
        and TASK_COUNT=(
          select count(0) from TASKS
            where TASKS.JOB_ID = N.JOB_ID
              and TASKS.TASK_STATUS in ('Authored','Completed'))

这在 DB2 中工作得很好,因为触发器与触发事件在同一个工作单元中运行,因此它可以看到工作单元的未提交更改,并且可以计算刚刚发生的 TASK_STATUS 更改而不会遇到行锁。

这是 Oracle 中翻译后的触发器:

create or replace trigger JOB_AUTHORED
  after update of TASK_STATUS on TASKS
  for each row
  when (NEW.TASK_STATUS = 'Authored')
  BEGIN
    update JOBS
      set JOB_STATUS='Authored'
      where JOBS.JOB_ID = :NEW.JOB_ID and TASK_COUNT=(
        select count(0) from TASKS
          where TASKS.JOB_ID = :NEW.JOB_ID
            and TASKS.TASK_STATUS in ('Authored','Completed'));
  END;

在 Oracle 中,这失败了:

ORA-04091: table MYSCHEMA.TASKS is mutating, trigger/function may not see it#012ORA-06512: at "MYSCHEMA.JOB_AUTHORED", line 1#012ORA-04088: error during execution of trigger 'MYSCHEMA.JOB_AUTHORED'#012] [query: UPDATE TASKS SET TASK_STATUS=:1 where TASK_ID=:2

显然,Oracle 的触发器不在同一上下文中运行,看不到未提交的触发更新,因此永远无法计算包括触发行在内的特定状态的任务数。

我想我可以将 AFTER 触发器更改为 INSTEAD OF 触发器并更新触发器内的 TASK_STATUS(以及 JOB_STATUS)(因此作业更新可以看到任务更新),但我会遇到同样的错误吗?也许不是第一个任务更新,但是如果触发程序在提交之前更新了一堆任务怎么办:当第二个任务更新时会发生什么?

我还考虑过删除触发器并让程序扫描活动作业以了解其任务的状态,但这似乎不优雅。

在 Oracle 中这样的最佳实践是什么?

4

3 回答 3

2

最好的做法是尽可能避免触发
请参阅这些链接以了解为什么不使用触发器的答案:
http ://www.oracle.com/technetwork/testcontent/o58asktom-101055.html
http://rwijk.blogspot.com/2007/09/database-triggers- are-evil.html

使用过程(API)代替触发器 - 你可以创建一个包含几个过程的包,如,add_new_job等,并将所有逻辑放在这些过程中(检查、更改任务的状态、更改工作)州等)在一个地方。易于理解、易于维护、易于调试和易于跟踪错误。add_new_taskchange_task_status


如果您坚持使用触发器,那么您可以创建compound trigger,正如 Tom Kyte 在上面第一个链接中提到的那样workaround,例如:

create or replace TRIGGER JOB_AUTHORED
FOR UPDATE OF TASK_STATUS on TASKS
COMPOUND TRIGGER

  TYPE JOB_ID_TABLE_TYPE IS TABLE OF PLS_INTEGER INDEX BY PLS_INTEGER;  
  JOB_ID_TABLE JOB_ID_TABLE_TYPE;
  dummy CHAR;

    AFTER EACH ROW IS
     BEGIN
       -- SELECT null INTO dummy FROM JOBS WHERE job_id = :NEW.JOB_ID FOR UPDATE;
        JOB_ID_TABLE( :NEW.JOB_ID ) := :NEW.JOB_ID;
     END AFTER EACH ROW;

     AFTER STATEMENT IS
     BEGIN
       FORALL x IN INDICES OF JOB_ID_TABLE
         UPDATE jobs set JOB_STATUS='Authored'
         WHERE JOBS.JOB_ID = JOB_ID_TABLE( x )
           and TASK_COUNT=(
                  select count(0) from TASKS
                  where TASKS.JOB_ID = JOBS.JOB_ID
                    and TASKS.TASK_STATUS in ('Authored','Completed')
          );
     END AFTER STATEMENT;

END JOB_AUTHORED;

但是......
我不确定这个例子中是否有我们目前不知道的任何陷阱。

例如,在这种情况下存在一个陷阱:
假设有 18 个任务具有状态Authored

  • 在时间 X 用户 A 运行 UPDATE TASK SET status 'Authored' WHERE task_id = 2。触发器被触发并看到 18+1 个已提交的任务状态Authored
  • 在 X+10ms 时,用户 B 运行 UPDATE TASK1 SET status 'Authored' task_id = 4。触发器被触发并看到 18+1 个已提交任务的状态Authored
  • 在时间 X+20ms 用户 A 提交
  • 在时间 X+30ms 用户 B 提交
  • 最后,我们有 21 个带有 status 的任务authored。但作业的状态尚未更改为 Authored(但应更改为Authored如果任务数 = 20)。

为了避免这个陷阱,您可以SELECT null INTO dummy FROM JOBS WHERE job_id = :NEW.JOB_ID FOR UPDATE;在触发器的after each row一部分中使用,以便在 JOBS 表中的相应记录上放置一个锁,以便序列化访问(我在上面的示例中对此进行了注释)。
但我仍然不确定这是否是正确的解决方案 - 它可能会导致我目前无法想象和预测的场景中的一些死锁。

于 2015-05-25T23:49:34.790 回答
0

简而言之 - 在 Oracle 中,您无法在触发器中select from列出构建触发器的表。否则你可能/你会得到变异表错误。

你有几个选择:

1)根本没有触发器 - 在我看来这是最好的,我会这样做(但可能主题更广泛,我们并不知道一切)。创建视图,它取代了触发器的需要,例如:

create or replace view v_jobs_done as 
  select * from jobs where not exists 
    select 1 from tasks 
      where TASKS.JOB_ID = jobs.JOB_ID
        and TASKS.TASK_STATUS not in ('Authored','Completed')

2)而不是增加价值使用减少价值 - 所以当jobs.tasks_count达到零时,你知道一切都完成了。在这种情况下,您必须构建/重建您的其他触发器,

3)接近你的命题 - 你可以使用现代 复合触发器- 我怀疑这里的表现,但它有效:

create or replace trigger Job_Authored
for update of task_status on tasks compound trigger

  type t_ids   is table of tasks.job_id%type;
  type t_cnts  is table of number;
  type t_job_counts is table of number index by varchar2(10);
  v_ids        t_ids;
  v_cnts       t_cnts;
  v_job_counts t_job_counts;

before statement is
  begin
    select job_id, count(1) 
      bulk collect into v_ids, v_cnts
      from tasks where tasks.task_status in ('Authored','Completed')
      group by job_id;

    for i in 1..v_ids.count() loop
      v_job_counts(v_ids(i)) := v_cnts(i);
    end loop;
  end before statement;

after each row is
  begin
    if :new.task_status = 'Authored' then 
      update jobs set job_status='Authored'
        where job_id = :new.job_id
          and task_count = v_job_counts(:new.job_id);
    end if;
  end after each row;
end Job_Authored;
于 2015-05-25T23:21:34.630 回答
0

最好的做法是尽可能避免触发
请参阅此链接以了解为什么不使用触发器的答案:
http ://www.oracle.com/technetwork/testcontent/o58asktom-101055.html
http://rwijk.blogspot.com/2007/09/database-triggers- are-evil.html

使用过程(API)代替触发器 - 你可以创建一个包含几个过程的包,如,add_new_job等,并将所有逻辑放在这些过程中(检查、更改任务的状态、更改工作)州等)在一个地方。易于理解、易于维护、易于调试和易于跟踪错误。add_new_taskchange_task_status


如果您坚持使用触发器,那么您可以创建compound trigger,正如 Tom Kyte 在上面第一个链接中提到的那样workaround,例如:

create or replace TRIGGER JOB_AUTHORED
FOR UPDATE OF TASK_STATUS on TASKS
COMPOUND TRIGGER

  TYPE JOB_ID_TABLE_TYPE IS TABLE OF PLS_INTEGER INDEX BY PLS_INTEGER;  
  JOB_ID_TABLE JOB_ID_TABLE_TYPE;
  dummy CHAR;

    AFTER EACH ROW IS
     BEGIN
       -- SELECT null INTO dummy FROM JOBS WHERE job_id = :NEW.JOB_ID FOR UPDATE;
        JOB_ID_TABLE( :NEW.JOB_ID ) := :NEW.JOB_ID;
     END AFTER EACH ROW;

     AFTER STATEMENT IS
     BEGIN
       FORALL x IN INDICES OF JOB_ID_TABLE
         UPDATE jobs set JOB_STATUS='Authored'
         WHERE JOBS.JOB_ID = JOB_ID_TABLE( x )
           and TASK_COUNT=(
                  select count(0) from TASKS
                  where TASKS.JOB_ID = JOBS.JOB_ID
                    and TASKS.TASK_STATUS in ('Authored','Completed')
          );
     END AFTER STATEMENT;

END JOB_AUTHORED;

但是......
我不确定这个例子中是否有我们目前不知道的任何陷阱。

例如,在这种情况下存在一个陷阱:
假设有 18 个任务具有状态Authored

  • 在时间 X 用户 A 运行 UPDATE TASK SET status 'Authored' WHERE task_id = 2。触发器被触发并看到 18+1 个已提交的任务状态Authored
  • 在 X+10ms 时,用户 B 运行 UPDATE TASK1 SET status 'Authored' task_id = 4。触发器被触发并看到 18+1 个已提交任务的状态Authored
  • 在时间 X+20ms 用户 A 提交
  • 在时间 X+30ms 用户 A 提交
  • 最后,我们有 21 个带有 status 的任务authored。但作业的状态尚未更改为 Authored(但应更改为Authored如果任务数 = 20)。

为了避免这个陷阱,您可以SELECT null INTO dummy FROM JOBS WHERE job_id = :NEW.JOB_ID FOR UPDATE;在触发器的after each row一部分中使用,以便在 JOBS 表中的相应记录上放置一个锁,以便序列化访问(我在上面的示例中对此进行了注释)。
但我仍然不确定这是否是正确的解决方案 - 它可能会导致我现在无法想象和预测的场景中的死锁。

于 2015-05-25T23:55:11.840 回答