6

我的桌子:

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)

我想使用触发器(在 TableA、TableB、TableC 上的 AFTER UPDATE、INSERT、DELETE )来执行此操作,以便在每个操作之后执行此步骤:

  1. 获取父母ID
  2. 从当前父母的所有孩子中找到最小的状态
  3. 如果所有子节点的最小状态大于父节点的状态,则更新父节点

如何避免“变异表错误”?在这个例子中使用自治事务是否节省?我看到了一些意见,变异表错误表明应用程序的逻辑存在缺陷 - 这是真的吗?如何更改我的逻辑以防止出现此错误?

谢谢


编辑:感谢所有伟大的答案!

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


编辑:在真正的结局中,我使用了存储过程并删除了触发器:)

4

8 回答 8

12

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

如果您真的想使用触发器执行此操作,则必须遵循Tom Kyte 的这篇文章中显示的那种逻辑。如您所见,这并不简单。

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

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

于 2010-01-26T09:29:19.783 回答
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

于 2010-01-26T09:19:22.597 回答
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;

我同意存储过程(如其他帖子中所建议的)也是一个不错的候选者 - 但请注意,视图将自动保持最新,而我相信您必须安排运行存储过程以保持数据'in-sync':这可能很好 - 这取决于您的要求。

我想另一种选择是创建一些函数来进行计算,但我个人会选择视图方法(所有事情都是平等的)。

于 2010-01-26T09:36:39.110 回答
3

我看到了一些意见,变异表错误表明
应用程序的逻辑存在缺陷 - 这是真的吗?如何更改我的逻辑以防止出现此
错误?

我不知道你在哪里看到的,但我知道之前已经多次发表过这种观点。

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

你的场景就是一个典型的例子。您得到 ORA-04091 是因为您TableC在插入或更新它时进行选择。但是,您为什么要从中选择TableC?因为您“需要”更新其父列上的列,TableB. 但那一栏是多余的信息。在完全规范化的数据模型中,该列将不存在。

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

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

于 2010-01-26T15:09:23.050 回答
2

您可以使用触发器和完整性约束来定义和实施任何类型的完整性规则。但是,Oracle Corporation 强烈建议您仅在以下情况下使用触发器来约束数据输入:

当子表和父表位于分布式数据库的不同节点上时强制执行参照完整性 强制使用完整性约束无法定义的复杂业务规则 当使用以下完整性约束无法强制执行所需的参照完整性规则时:

  • 非空,唯一
  • 首要的关键
  • 外键
  • 查看
  • 删除级联
  • 删除设置空

来源:Oracle9i 数据库概念

于 2010-01-26T09:24:56.330 回答
2

作为您的逻辑为什么会失败的示例,请以父母 A 的记录 1 与 CHILD 记录 1A 和 1B 为例。1A 的状态是 10,1B 是 15,所以你希望你的父母是 10。

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

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

于 2010-01-26T21:51:26.680 回答
1

做这样的事情是一个很大的诱惑,如果你按照其他人引用的 Tom Kyte 文章中的建议进行操作是可能的。然而,仅仅因为某事可以做并不意味着它应该做。我强烈建议您将这样的东西实现为存储过程/函数/包。尽管有明显的诱惑,但不应使用触发器执行此类复杂逻辑,因为它大大提高了系统的复杂性,而没有相应地增加效用。我不得不偶尔处理这样的代码,这并不令人愉快。

祝你好运。

于 2010-01-26T13:07:55.700 回答
1

不要使用自治事务,否则你会得到非常有趣的结果。

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

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

于 2010-01-26T13:17:47.470 回答