使用自引用外键的死锁

Posted

技术标签:

【中文标题】使用自引用外键的死锁【英文标题】:Deadlock Using self-referential foreign key 【发布时间】:2011-05-05 13:46:15 【问题描述】:

使用 SQL Server 2005:我有一个具有自引用外键的表。

当此表上的更新和插入同时发生但仅在以下情况下会发生死锁:

第一次更新 更新是针对插入语句的外键引用的记录

在更新期间,会在主键上创建 X 锁,主键是表的聚集索引。我已经尝试了几种方法来尝试防止这种情况发生,例如:

我已尝试更改事务隔离级别 提供表格提示,即 WITH(nolock) 我尝试在表的主键上创建一个非聚集索引,以便可以使用它来代替聚集索引。

外键需要在那里,所以删除它不是一个选项。关于如何防止阻止插入的锁或允许插入围绕锁工作的任何建议都会非常有帮助。

谢谢。

【问题讨论】:

您是否遇到了死锁(其中一个事务因错误1205 而失败)或锁等待? 【参考方案1】:

您是否尝试过通过 BEGIN TRANSACTION 和 COMMIT 分隔 UPDATE 和 INSERT 事务。这样您就可以远离死锁。

类似的东西;

BEGIN TRANSACTION insert
  <INSERT SQL>
  COMMIT TRANSACTION insert
  BEGIN TRANSACTION update
     <UPDATE SQL>
  COMMIT TRANSACTION update
END

【讨论】:

不幸的是,这些查询发生在不同的存储过程中,这些过程在不同的线程中执行,所以我不可能一个接一个地同步提交。 有一个常见的 RDBMS 概念,称为两阶段提交 (2PC)。您可以利用它来实现您想要的。但是您仍然需要使用 BEGIN/COMMIT 状态。以下是一些参考资料; SQL Server:msdn.microsoft.com/en-us/library/aa754091(v=bts.10).aspxOracle:download.oracle.com/docs/cd/E14072_01/server.112/e10595/… mysql:dev.mysql.com/doc/refman/5.0/en/xa.html 两阶段提交与这个问题有什么关系? Quassnoi,你说得对。这个问题与2PC无关。我只是偏离了主题。谢谢...【参考方案2】:

请提供 DDL 和您的 UPDATE / INSERT 语句,我无法重现此内容。

CREATE TABLE T
(
id int identity(1,1) primary key,
refid int references T(id),
filler char(10)
)

INSERT INTO T (refid)
select number 
FROM master..spt_values where number between 1 and 2248

连接 1

BEGIN TRAN
UPDATE T SET filler = 'A' WHERE id=500  

连接 2

BEGIN TRAN
INSERT INTO T (refid) VALUES (500) /*Blocked - No deadlock*/

【讨论】:

99% 的用户在谈到deadlock 时表示lock。他们应该称它为lock resolution error 或其他名称。 你是对的,当从 sql server 的查询窗口运行过程时,我们看到了等待锁。但是,我们在这部分代码中确实存在死锁,并且跟踪日志突出显示了这些过程是原因。我们目前的想法是,这可能是导致僵局的原因。 @Stew52 - 你有死锁图吗?如果是这样,您能否将 XML 编辑到您的问题中?【参考方案3】:

当发现符合UPDATE 条件的记录时,会在其(或其页面等,取决于引擎选择的锁定粒度)上放置X 锁。

这可以防止将S 锁定在需要检索其值的受影响资源上。

由于向子表中插入值需要对照父表进行检查,INSERT 语句将不得不等到UPDATE 事务提交或回滚。

如果您将PRIMARY KEY 设为非集群,UPDATE 应该不会影响它(除非您正在更新PRIMARY KEY 本身,在正常情况下您不应该这样做),因此INSERT 将成功。

以下命令对我有用:

交易1:

CREATE TABLE parent (id INT NOT NULL PRIMARY KEY NONCLUSTERED, value INT NOT NULL, parentId INT REFERENCES parent)

INSERT
INTO    parent
VALUES  (1, 1, NULL)

BEGIN TRANSACTION
UPDATE  parent
SET     value = 2
WHERE   id = 1

事务 2:

INSERT
INTO    parent
VALUES  (2, 1, 1)

【讨论】:

我们的问题是只有一张表,它是自引用的。孩子不可能在单独的桌子上。 @Stew52:起初没有注意到,我的错 :) 但是,这没有什么区别。请查看帖子更新。【参考方案4】:

我最近遇到了同样的问题。正如this post 中提到的,我的解决方案是简单地在自引用列(而不是 pk)上添加一个索引。之后,僵局就彻底消失了。

【讨论】:

以上是关于使用自引用外键的死锁的主要内容,如果未能解决你的问题,请参考以下文章

Java死锁范例以及如何分析死锁(转载自ImportNew)

死锁的理解

创建原子引用计数的尝试因死锁而失败。这是正确的方法吗?

进程死锁

nagios 添加自定义监控项目监控mysql数据库死锁

如何让 jOOQ 生成不引用主键的复合外键?