Oracle触发器 - 变异表的问题

时间:2010-01-26 09:08:15

标签: oracle triggers ora-04091 mutating-table

我的桌子:

TableA (id number, state number)
TableB (id number, tableAId number, state number)
TableC (id number, tableBId number, state number)

因此TableC中的项是TableB的子项,TableB中的项是TableA的子项。反之亦然 - TableA中的项是TableB的父项,TableB中的项是TableC的父项。

我想控制父项的状态......比方说,我们有这些数据:

TableA (id, state): 
1, 40

TableB (id, tableAId, state): 
1, 1, 40
2, 1, 60

TableC (id, tableBId, state): 
1, 1, 40
2, 1, 50
3, 2, 60
4, 2, 70

父母国家应该始终保持孩子最小的状态。所以如果我们现在像这样更新TableC:

update TableC set state = 50 where Id = 1;

我的触发器应自动更新TableB(设置状态= 50,其中id = 1),然后更新TableA(设置状态= 50,其中id = 1)

我想用触发器(在表A,表B,表C上更新,插入,删除后)执行此操作,以便在执行每个操作后执行此步骤:

  1. 获取父ID
  2. 从当前父母的所有孩子中找到最小的状态
  3. 如果所有孩子的最小状态大于父母的状态,则更新父母
  4. 如何避免'改变表错误'?在此示例中是否可以节省使用自治事务?我看到了一些意见,即变异表错误表明应用程序逻辑存在缺陷 - 这是真的吗?如何更改逻辑以防止出现此错误?

    由于


    编辑: 谢谢你们所有的精彩答案!

    最后,我使用了触发器(感谢Vincent Malgrat,他指出了Tom Kyte的文章)。


    编辑: 在REAL END中,我使用了存储过程并删除了触发器:)

8 个答案:

答案 0 :(得分:12)

您已经注意到,使用触发器很难回答您的业务需求。原因是Oracle 可能为单个查询(并行DML)同时更新/插入具有多个线程的表。这意味着您的会话无法在更新发生时查询更新的表格

如果你真的想用触发器做这件事,你必须遵循kind of logic shown in this article by Tom Kyte。你可以看到它并不简单。

还有另一种更简单,更优雅,更易于维护的方法:使用程序。撤消对应用程序用户的更新/插入权限,并编写一组允许应用程序更新状态列的过程。

这些过程将锁定父行(以防止多个会话修改同一组行),并以高效,可读和易于维护的方式应用您的业务逻辑。

答案 1 :(得分:5)

你应该不要使用触发器来处理复杂的业务逻辑。将其移动到存储过程(PL / SQL包)或客户端代码。具有大量触发器的应用程序变得难以驾驭,因为您很快就会失去任何“行动序列”的感觉。

使用自治事务绝对不安全,仅将自治事务用于日志记录,跟踪,调试和审计。

阅读:http://www.oracle.com/technetwork/issue-archive/2008/08-sep/o58asktom-101055.html

在这里,您可以阅读如何在不使用自治事务时使用触发器来解决问题:http://www.procaseconsulting.com/learning/papers/200004-mutating-table.pdf

答案 2 :(得分:3)

您是否可以重构解决方案以包含视图以执行计算?

CREATE VIEW a_view AS
SELECT a.Id, min(b.State) State FROM tableA,tableB
WHERE a.Id=b.tableAId
GROUP BY a.Id;

我同意存储过程(如其他帖子中所示)也是一个很好的候选者 - 但请注意,视图将自动保持最新,而我相信你必须安排运行存储过程到保持数据“同步”:这可能没问题 - 这取决于您的要求。

我想另一个选择是创建一些函数来进行计算,但我个人会选择视图方法(所有条件都相同)。

答案 3 :(得分:3)

  

我看到了一些意见,变异表错误表明了逻辑上的缺陷    应用程序 - 这是真的,我怎样才能改变我的逻辑以防止这种情况发生    错误?

我不知道你在哪里看到这一点,但我知道已多次发表这种意见。

为什么我认为变异表通常表明数据模型存在缺陷?因为驱动ORA-4091的代码的那种“要求”经常与糟糕的设计相关联,尤其是规范化不足。

你的场景是一个典型的例子。您收到ORA-04091,因为您在插入或更新时选择TableC。但是你为什么要从TableC中选择?因为您“需要”更新其父级TableB上的列。但该栏目是多余的信息。在完全标准化的数据模型中,该列不存在。

非规范化通常被吹捧为提高查询性能的机制。不幸的是,非正规化的支持者掩盖了它的成本,当我们插入,更新和删除时,它会以过度复杂的货币支付。

那么,你怎么能改变你的逻辑呢?简单的答案是删除列,而不是按父ID存储最小的状态。相反,只要您需要该信息,就执行MIN()查询。如果您经常需要它并且执行查询会很昂贵,那么您构建了存储数据的物化视图(请务必使用ENABLE QUERY REWRITE

答案 4 :(得分:2)

  

您可以同时使用触发器和   完整性约束来定义和   强制执行任何类型的完整性规则。   但是,Oracle公司强烈要求   建议您使用触发器   仅在数据中约束数据输入   以下情况:

     

在执行时强制引用完整性   子表和父表已打开   分布式的不同节点   数据库执行复杂的业务   使用完整性无法定义的规则   约束当需要时   参照完整性规则不可能   使用以下完整性强制执行   约束:

     
      
  • NOT NULL,UNIQUE
  •   
  • PRIMARY KEY
  •   
  • FOREIGN KEY
  •   
  • 检查
  •   
  • 删除CASCADE
  •   
  • DELETE SET NULL
  •   

source: Oracle9i Database Concepts

答案 5 :(得分:2)

作为你的逻辑失败的一个例子,采取一个场景,其中父母A有记录1和CHILD记录1A和1B。 1A的状态是10,1B是15,所以你希望你的父母是10。

现在有人将1A的状态更新为20,同时有人删除1B。因为1B的删除是未提交的,所以事务更新1A仍将看到1B并且将想要将父级的状态设置为15,而删除1B的事务将看到旧的未提交值1A并且将希望父状态为10。

如果你对此进行去规范化,你必须非常小心锁定,以便在插入/更新/删除任何子记录之前,父记录被锁定,执行你的更改,选择所有子记录,更新parent,然后提交释放锁。虽然可以使用触发器完成,但最好使用存储过程。

答案 6 :(得分:1)

做这样的事情是一个很大的诱惑,如果你遵循其他人引用的Tom Kyte文章中的建议,这是可能的。但是,仅仅因为可以完成某事并不意味着应该完成。我强烈建议您实现这样的存储过程/函数/包。尽管有明显的诱惑,但不应使用触发器执行此类复杂逻辑,因为它会大大提高系统的复杂性而不会相应增加实用程序。我必须偶尔处理这样的代码,这不是快乐。

祝你好运。

答案 7 :(得分:1)

不要使用自主交易,否则你会得到非常有趣的结果。

为避免变异表问题,您可以执行以下操作:

在AFTER INSERT OR UPDATE OR DELETE FOR EACH ROW触发器中,找出父ID并将其保存在PL / SQL集合中(在PACKAGE中)。 然后,在AFTER INSERT或UPDATE OR DELETE TRIGGER(语句级别,没有“for each row”部分)中,从PL / SQL集合中读取父ID并相应地更新父表。