4

我有一个表可以容纳一个帐号的多个记录:不同的金额。“ORA-14450:尝试访问已在使用的事务性临时表”在复合触发器中

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

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

金额的计算应该使用记录的新值,但为了检查某些条件,我们也需要旧值(例如,旧值为X - 新值为Y:如果[X < = threshold并且Y>阈值],然后通过将记录插入另一个表中来触发事件)。

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

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

为了避免基于解决方案的'table is mutating'错误,我使用了复合触发器与全局临时表的组合。

现在我们有一个复合触发器,它使用一些全局临时表来存储来自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:尝试访问一个事务性临时表已经在使用'?

+0

什么在你的'AFTER触发器STATEMENT'的 “商业逻辑” 呢?以不需要临时表的另一种方式实现它是否可行(比如可能使用物化视图)?如果不是,那么是否可行(即使有点“黑客”)使用“真实”表而不是临时表(可能使用重复作业删除不再需要的行)。 –

+0

处理每行之后保存的数据,并根据某些条件将记录插入另一个触发其他进程(通过JMS,这些进程有时与第三方连接)的表中。我不知道是否物化视图,黑客或真正的表可以是一个解决方案,通常我们尽量保持它尽可能简单。 –

回答

0

你应该carefuly检查你的代码不使用自治事务来访问临时表中的数据:

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 
0

如果您在BEFORE STATEMENTAFTER STATEMENTDELETE FROM <temp-table-name>如果GTT与定义是不应该的问题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; 
/
+0

我已经提到过“我尝试过使用全局临时表而不是全局临时表触发器变量 - 一个集合(对象表),但我不确定这种方法,对分布式事务来说安全吗?” –

+0

关于你的第一个建议,你确定吗? –

+0

对于我的第一个建议,我没有看到任何冲突的原因,但我不确定。但是,采用可变方法时,无论如何您都应该保持安全。 –

相关问题