4

我有一张表可以为一个帐户保存许多记录:不同的金额。

ACCOUNTID | AMOUNT
id1       | 1
id1       | 2
id2       | 3
id2       | 4

每次插入/更新/删除此表中的记录时,我们都需要评估总量,以便知道是否应该触发事件(通过将数据插入另一个表)。金额是根据此表中存在的记录总和(每个帐户)计算的。

金额的计算应该使用记录的新值,但我们还需要旧值来检查某些条件(例如旧值是 X - 新值是 Y:如果 [X<=threshold and Y>threshold] then trigger事件通过将记录插入另一个表)。

所以为了计算和触发事件,我们在这个表上创建了一个触发器。像这样的东西:

CREATE OR REPLACE TRIGGER <trigger_name>
  AFTER INSERT OR UPDATE OR DELETE OF MOUNT ON <table_name>
  FOR EACH ROW
  DECLARE
BEGIN
  1. SELECT SUM(AMOUNT) INTO varSumAmounts FROM <table_name> WHERE accountid = :NEW.accountid; 
  2. varAmount :=  stored_procedure(varSumAmounts);
END <trigger_name>;

问题是语句 1. 引发以下错误:“ORA-04091:表正在变异,触发器/函数可能看不到它”。

我们尝试了以下但没有成功(相同的异常/错误)来选择所有 rowId 与当前 rowId 不同的记录:

(SELECT SUM(AMOUNT) 
 INTO varSumAmounts 
 FROM <table_name> 
 WHERE accountId = :NEW.accountid 
       AND rowid <> :NEW.rowid;)

为了将数量计算为当前行旁边所有行的数量之和 + 当前行的数量(我们在触发器的上下文中拥有)。

我们搜索了其他解决方案,我们找到了一些,但我不知道它们中的哪一个更好,它们每个的缺点是什么(尽管它们在某种程度上相似)

  1. 使用复合触发器

  2. http://www.oracle-base.com/articles/9i/mutating-table-exceptions.php

  3. http://asktom.oracle.com/pls/asktom/ASKTOM.download_file?p_file=6551198119097816936

为了避免基于解决方案 1 和 2 的“表正在变异”错误,我将复合触发器与全局临时表结合使用。

现在我们有一个复合触发器,它使用一些全局临时表来存储来自 :OLD 和 :NEW 伪记录的相关数据。基本上我们做接下来的事情:

CREATE OR REPLACE TRIGGER trigger-name
FOR trigger-action ON table-name
COMPOUND TRIGGER
-------------------
BEFORE STATEMENT IS
BEGIN
-- Delete data from global temporary table (GTT) for which source is this trigger
-- (we use same global temporary tables for multiple triggers).
END BEFORE STATEMENT;
-------------------
AFTER EACH ROW IS
BEGIN
-- Here we have access to :OLD and :NEW objects.
-- :NEW and :OLD objects are defined only inside ROW STATEMENTS.
-- Save relevant data regarding :NEW and :OLD into GTT table to use it later.
END AFTER EACH ROW;
--------------------
AFTER STATEMENT IS
BEGIN
-- In this block DML operations can be made on table-name(the same table on which 
--the trigger is created) safely.
-- Table is mutating error will no longer appear because this block is not for EACH ROW specific.
-- But we can't access :OLD and :NEW objects. This is the reason why in 'AFTER EACH ROW' we saved them in GTT.
-- Because previously we saved :OLD and :NEW data, now we can continue with our business logic.
-- if (oldAmount<=threshold && newAmount>threshold) then 
--    trigger event by inserting record into another table
END AFTER STATEMENT;
END trigger-name;
/

使用的全局临时表是使用选项“ON COMMIT DELETE ROWS”创建的,这样我可以确保在事务结束时清除该表中的数据。然而,这个错误发生了:'ORA-14450:试图访问一个已经在使用的事务临时表'。

问题在于该应用程序使用分布式事务,并且在 oracle 文档中提到:“将全局临时表 (GTT) 与分布式或 XA 事务结合使用时,可能会报告各种内部错误。......

在任何分布式事务中不支持临时表,因此 XA 协调事务。最安全的选择是不在分布式或 XA 事务中使用临时表,因为官方不支持在这种情况下使用临时表。...

如果数据库中只有单个分支事务使用全局临时表,则可以安全地使用它,但如果存在环回数据库链接或涉及多个分支的 XA 事务,则可能会出现问题,包括按照错误 5344322 的块损坏。”

值得一提的是,我无法避免 XA 事务或在作为触发器主题的同一张表上进行 DML(修复数据模型不是可行的解决方案)。我尝试使用触发器变量而不是全局临时表 - 一个集合(对象表),但我不确定这种方法。分布式事务是否安全?

在这种情况下,哪些其他解决方案适合解决初始问题:'ORA-04091:表名正在变异,触发器/函数可能看不到它',或第二个:'ORA-14450:尝试访问事务临时表已经在使用'?

4

2 回答 2

0

您应该仔细检查您的代码是否不使用自治事务来访问临时表数据:

SQL> create global temporary table t (x int) on commit delete rows
  2  /

SQL> insert into t values(1)
  2  /

SQL> declare
  2   pragma autonomous_transaction;
  3  begin
  4  insert into t values(1);
  5  commit;
  6  end;
  7  /
declare
*
error in line 1:
ORA-14450: attempt to access a transactional temp table already in use 
ORA-06512: error in line 4 
于 2014-02-28T14:14:33.393 回答
0

如果你做了一个DELETE FROM <temp-table-name>inBEFORE STATEMENT和is ,如果你的 GTT 是用orAFTER STATEMENT定义的,那应该没关系。ON COMMIT PRESERVE ROWSON COMMIT DELETE ROWS

在您的触发器中,您可以定义一个 RECORD/TABLE 变量。您可以在块中初始化此变量并在BEFORE STATEMENT块中循环它BEFORE STATEMENT

会是这样的:

CREATE OR REPLACE TRIGGER TRIGGER-NAME
FOR TRIGGER-action ON TABLE-NAME
COMPOUND TRIGGER

TYPE GTT_RECORD_TYPE IS RECORD (ID NUMBER, price NUMBER, affected_row ROWID);
TYPE GTT_TABLE_TYPE IS TABLE OF GTT_RECORD_TYPE;
GTT_TABLE GTT_TABLE_TYPE;

-------------------
BEFORE STATEMENT IS
BEGIN
    GTT_TABLE := GTT_TABLE_TYPE(); -- init the table variable   
END BEFORE STATEMENT;
-------------------
AFTER EACH ROW IS
BEGIN
    GTT_TABLE.EXTEND;
    GTT_TABLE(GTT_TABLE.LAST) := GTT_RECORD_TYPE(:OLD.ID, :OLD.PRICE, :OLD.ROWID);
END AFTER EACH ROW;
--------------------
AFTER STATEMENT IS
BEGIN
    FOR i IN GTT_TABLE.FIRST..GTT_TABLE.LAST LOOP
        -- do something with values
    END LOOP;
END AFTER STATEMENT;
END TRIGGER-NAME;
/
于 2014-02-28T15:34:07.893 回答