在事务期间禁用约束

Posted

技术标签:

【中文标题】在事务期间禁用约束【英文标题】:Disable constraints during transaction 【发布时间】:2011-02-20 13:22:29 【问题描述】:

我正在整理一个表格,用于向某些信息请求发送后续消息。向一组人发送请求并跟踪响应。如果一个人没有响应,则可能会发送零个或多个跟进。我创建了一个表:

FollowupId int primary key, 
RequestId int foreign key (outside this example), 
Follows int foreign key (FollowupId), 
Message varchar

如果一条消息是第一条后续消息,Follows 将为空。否则,它是其他一些后续的 id。我还在 Follows 上添加了一个独特的约束。也就是说,任何给定的消息后面只能有一条消息。

编辑:我还应该在 Follows 上突出显示外键。它引用此表中的 FollowupId。所以如果 A->B->C,仅仅删除 B 会使 C 中的外键失效。同样,不可能只更新 C 以跟随 A,因为 B 已经跟随 A,并且唯一约束禁止重复。

当然,问题是,如果该消息后面跟着另一个消息,那么现在删除后续条目会很困难。在我看来,应该可以禁用约束检查以删除中间跟进,“向上移动”后续跟进,然后重新启用检查。有没有办法只?

(另外,我知道在此表中包含 RequestId 可能会导致数据不一致。最好包含 Followups [FollowupId, Message]、InitialFollowups [FollowupId, RequestId] 和 FollowFollowups [FollowupId, Follows]表。我认为这不必要地使这个例子复杂化。)

【问题讨论】:

【参考方案1】:

我发现(至少在 SQL Server 上)不可能禁用唯一约束。可以禁用外键约束,将要删除的记录的 id 设置为无效且不可能的 id(例如在我的情况下为 -1),更改后续 id,删除有问题的记录,然后恢复约束检查。假设以下数据:

FollowId | RequestId | Follows | Message  
--------------------------------------------------------------------
       1 |        17 |    NULL | "First one"  
       2 |        17 |       1 | "Second one, delete this one"  
       3 |        17 |       3 | "Third one, but make it the second"  

我使用了以下策略:

BEGIN TRANSACTION;

ALTER TABLE RequestFollowups NOCHECK CONSTRAINT FK_Follows_FollowId;

UPDATE      RequestFollowups SET Follows = -1 WHERE FollowupId = 2;
UPDATE      RequestFollowUps SET Follows =  1 WHERE FollowupId = 3;
DELETE FROM RequestFollowups                  WHERE FollowupId = 2;

ALTER TABLE RequestFollowups WITH CHECK CHECK CONSTRAINT FK_Follows_FollowId;

COMMIT TRANSACTION;

(注意倒数第二行的CHECK CHECKis intentional and not a typo。)

【讨论】:

请注意,使用此方法的不利之处在于,当执行CHECK CHECK CONSTRAINT 步骤时,将对整个表 的约束进行验证,这会对性能产生重大影响。更好的方法是使用可延迟约束,但是 SQL Server 仍然不支持它们。【参考方案2】:

为某些修改禁用/启用约束通常是个坏主意,而且性能可能会很糟糕。每当您执行此操作时,请确保您的约束不仅已启用,而且在您完成后受信任。

在您的情况下,您需要删除一行并修改另一行。如果您已经使用 SQL 2008,则应该使用 MERGE,它允许您在一个命令中同时删除和更新。

【讨论】:

通常是个坏主意,但有时却是个好主意:How to truncate a foreign key constrained table?【参考方案3】:

先更新其他值,然后再删除。

所以如果订单是

A -> B -> C

而你正在删除 B,将 C 的 Follows 更新为 A,A 的 FollowedUp 更新为 C,然后删除 B。

【讨论】:

以上是关于在事务期间禁用约束的主要内容,如果未能解决你的问题,请参考以下文章

自治事务中的 Oracle DDL

索引视图是不是在事务期间更新?

为啥在更新事务期间休眠调用删除?

如何在 ABP 框架中禁用事务?

翻译自mos文章当并行事务恢复进程在执行时,禁用并行事务恢复的方法

如何仅在一个事务中禁用 PostgreSQL 触发器?