【问题标题】:Dead Lock occur when foreign key exists on table当表上存在外键时发生死锁
【发布时间】:2014-07-02 15:59:22
【问题描述】:

DB中有两个表,Audit和AuditField,创建表代码如下:

-- Primary key: ID
CREATE TABLE [dbo].[Audit](
[ID] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,
[TypeName] [varchar](50) NOT NULL
) 
GO

-- Primary key: ID
CREATE TABLE [dbo].[AuditField](
[ID] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,
[AuditID] [int] NOT NULL,
[Field1] [varchar](50) NOT NULL
)

GO
-- Set foreign key on AuditField table 
ALTER TABLE [dbo].[AuditField] 
ADD  CONSTRAINT [FK_AuditFiled_Audit] FOREIGN KEY([AuditID])
REFERENCES [dbo].[Audit] ([ID])
GO

然后我准备了一些测试数据:

DECLARE @audit TABLE
(
ID int not null,
TypeName varchar(50)
)

DECLARE @auditField TABLE
(
AuditID int not null,
Field1 varchar(50)
)

-- ADD TEST DATA
DECLARE @i int = 1
DECLARE @rowCount int = 500
WHILE @i<=@rowCount
BEGIN
INSERT INTO @audit
VALUES(@i, 'SomeTypeName')

INSERT INTO @auditField
(AuditID,Field1)
VALUES(@i,'SomeThing')

SET @i += 1
END

最后,我运行以下事务将测试数据插入到这两个表中:

begin transaction
INSERT INTO dbo.Audit
SELECT TypeName
FROM @audit
ORDER BY ID

declare @lastIdentity int = @@identity
declare @offSet int = @lastIdentity - @rowCount

INSERT INTO dbo.AuditField
SELECT AuditID+@offSet AS AuditID, Field1
FROM @auditField 
ORDER BY AuditID
commit transaction

当这个事务并发运行时,发生死锁,一个进程失败,另一个进程出错:

消息 547,级别 16,状态 0,第 40 行 INSERT 语句冲突 具有 FOREIGN KEY 约束“FK_AuditFiled_Audit”。冲突 发生在数据库“MyDB”、表“dbo.Audit”、列“ID”中。

在 Audit 和 AuditField 表上没有触发器。 很抱歉代码的格式,我真的需要一个答案为什么会发生这种死锁,谢谢。

有一点要明确,AuditField 表的数据来自@auditField,正如@Bogdan 的回答我这样改写:

begin transaction
INSERT INTO dbo.Audit
OUTPUT inserted.ID INTO @temp
SELECT TypeName
FROM @audit

INSERT INTO @idMapping
SELECT ROW_NUMBER() OVER(ORDER BY ID) AS RowNumber, ID
FROM @temp

INSERT INTO dbo.AuditField
SELECT m.ID AS AuditID, Field1
FROM @auditField af
INNER JOIN @idMapping m ON af.AuditID = m.RowNumber

commit transaction

【问题讨论】:

  • 现在应该没问题了,因为INSERT INTO dbo.AuditField ... 只会在同一事务中插入INSERT INTO dbo.Audit ... 生成的AuditID 值。

标签: sql-server database foreign-keys deadlock


【解决方案1】:

这是和读写死锁:

如您所见,每个事务都成功获得了 [e]X[clusive] 锁,并请求 S[hared] 锁。问题是为什么一个事务试图读取被另一个事务锁定的行 X。

答案如下: 1)以下源代码

declare @lastIdentity int = @@identity
declare @offSet int = @lastIdentity - @rowCount

假设IDENTITY 值由每个生成

INSERT INTO dbo.Audit
SELECT TypeName
FROM ...

语句继续。这是完全错误的,如下图所示:

这意味着在某个时间点,事务可以成功地在插入的行上获得 X 锁,然后 1) 因为在 Audit 中插入的行不是连续的,并且 2)因为

declare @lastIdentity int = @@identity
declare @offSet int = @lastIdentity - @rowCount

INSERT INTO dbo.AuditField
SELECT AuditID+@offSet AS AuditID, Field1 ...

最后一个INSERT 尝试插入属于另一个事务的dbo.AuditFieldAuditID 值,这需要FK 验证,并且还意味着SQL Server 需要从dbo.Audit 读取行。为此,需要 S[hared] 锁。

要明确:这个死锁的根本原因不是 FK 约束。真正的问题是源代码。

解决方案:我会这样重写:

begin transaction
INSERT INTO dbo.Audit
OUTPUT inserted.ID, inserted.TypeName INTO @audit (ID, TypeName)
SELECT TypeName
FROM @audit
-- ORDER BY ID -- Isn't necessary 

... do something (ex. DELETE) with rows from @audit

INSERT INTO dbo.AuditField (AuditID, ...)
SELECT x.ID, ...
FROM @audit x
-- ORDER BY AuditID 

/* or 
    INSERT INTO dbo.AuditField (AuditID, Field1, ....)
    SELECT y.ID, y.ColumnName, ...
    FROM (
        SELECT x.ID, ...
        FROM @audit x
        UNPIVOT( ColumnValue FOR ColumnName IN ([TypeName], ...) )
    ) y
    WHERE y.....
*/
commit transaction -- Isn't necessary

【讨论】:

  • 感谢您的回答,我知道为什么会发生这种死锁。关于您的解决方案还有一个问题,使用 OUTPUT 我可以插入 ID,是否有一种示例方法可以将 ID 与 @auditField.AuditID 映射?
【解决方案2】:

您正试图在 dbo.AuditField 中插入无效的外键值:

SELECT AuditID+@offSet AS AuditID, Field1

为什么是@offset?您不一定在 dbo.Audit 表中有具有该值的 AuditId。

【讨论】:

  • 我想在 Audit 和 AuditField 表之间保持映射。
猜你喜欢
  • 1970-01-01
  • 2019-07-02
  • 1970-01-01
  • 1970-01-01
  • 2020-01-26
  • 1970-01-01
  • 1970-01-01
  • 2018-03-16
相关资源
最近更新 更多