如何避免外键约束造成死锁?

Yis*_*hai 4 sql-server-2005 foreign-key deadlock

所以我有两个表会造成死锁。应用程序实际上什么都不做,只是在两个不同的事务中更新两个不同的表。这两个表之间存在外键约束(不是级联,只是为了强制完整性),否则就每个事务的发生方式而言,两者之间没有关系。这可能是僵局的全部根源吗?如果是这样,你如何设计外键约束来避免这个问题?

更新:我应该指出(对于那些遇到这个问题的人)根本问题最终与外键约束无关,但是根据一点,如果另一个外键约束可能导致死锁side 只有主键的聚集索引。

我还了解到死锁 XML 报告没有捕获到那时的整个事务(我错误的假设它确实导致了不正确的问题),这很烦人。

编辑:这两个表是一个名为 KID 的表和一个名为 Image 的表(还有 ZAT_KID 和 ZAT_Image 由触发器填充作为审计跟踪,它们不引用其他任何内容)。Image 的 KIDID 字段是 KIDID 的 KID 主键(聚集索引)的外键。我还应该补充一点,每个堆栈开头的初始选择 1 是数据库池,确保连接在执行其他任何操作之前仍然有效。

这是死锁 XML:

<deadlock-list>
 <deadlock victim="processedb6d8">
  <process-list>
   <process id="processec49b8" taskpriority="0" logused="2672" waitresource="PAGE: 7:1:295182" waittime="2609" ownerId="2771483341" transactionname="implicit_transaction" lasttranstarted="2013-02-05T15:32:48.150" XDES="0x804da410" lockMode="X" schedulerid="2" kpid="10180" status="suspended" spid="93" sbid="0" ecid="0" priority="0" transcount="2" lastbatchstarted="2013-02-05T15:32:56.980" lastbatchcompleted="2013-02-05T15:32:56.963" clientapp="Microsoft JDBC Driver for SQL Server" hostname="newappserver" hostpid="0" loginname="DatabaseUser" isolationlevel="read committed (2)" xactid="2771483341" currentdb="7" lockTimeout="4294967295" clientoption1="539099168" clientoption2="128058">
    <executionStack>
     <frame procname="Database.dbo.T_Audit_KID" line="67" stmtstart="3936" stmtend="5062" sqlhandle="0x030007005776f42cc93b87015aa100000000000000000000">
INSERT INTO ZAT_KID with (PAGLOCK) (KIDID, KID, KosherStatusID, restrictionText,expirationDate,issuedDate,name,brand,void,rabbiSigner,agencyID,vendorID,  ActivityDate, ActivityByID,Mode ,Extended,TempOwner,SecLevel,Source,Dataless,Overridden,UKDAgencyCode,UKDAgencyUniqueID)
      VALUES ( @KIDID, @KID, @KosherStatusID, @restrictionText,@expirationDate,@issuedDate,@name,@brand,@void,@rabbiSigner,@agencyID,@vendorID,  @ActivityDate, @ActivityByID,@Mode, @Extended,@TempOwner, @SecLevel,@Source,@Dataless,@Overridden,@UKDAgencyCode,@UKDAgencyUniqueID)     </frame>
     <frame procname="adhoc" line="1" stmtstart="60" sqlhandle="0x02000000285d0519fdc120b4e4bbeca4c48d20fa089a34b5">
UPDATE kid SET activityByID=@P0, activityDate=@P1 WHERE kidID=@P2     </frame>
     <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown     </frame>
    </executionStack>
    <inputbuf>
Select 1    </inputbuf>
   </process>
   <process id="processedb6d8" taskpriority="0" logused="1728" waitresource="PAGE: 7:1:295211" waittime="3781" ownerId="2771489653" transactionname="implicit_transaction" lasttranstarted="2013-02-05T15:32:55.683" XDES="0x2ce89f710" lockMode="X" schedulerid="3" kpid="9452" status="suspended" spid="64" sbid="0" ecid="0" priority="0" transcount="2" lastbatchstarted="2013-02-05T15:32:55.807" lastbatchcompleted="2013-02-05T15:32:55.713" clientapp="Microsoft JDBC Driver for SQL Server" hostname="newappserver" hostpid="0" loginname="DatabaseUser" isolationlevel="read committed (2)" xactid="2771489653" currentdb="7" lockTimeout="4294967295" clientoption1="539099168" clientoption2="128058">
    <executionStack>
     <frame procname="Database.dbo.T_Audit_Image" line="49" stmtstart="2178" stmtend="2612" sqlhandle="0x030007003c4d5a39f33b87015aa100000000000000000000">
INSERT INTO ZAT_Image with (PAGLOCK) (ImageID, KIDID, ExpirationDate, Exclude, ActivityByID, ActivityDate,Mode)
      VALUES ( @ImageID, @KIDID,@ExpirationDate, @Exclude, @ActivityByID, @ActivityDate,@Mode)     </frame>
     <frame procname="adhoc" line="1" stmtstart="174" sqlhandle="0x020000006641f2279ebb048f51cb23b67af52ff7c3599d05">
insert into Image (ImageStoreKey, ActivityByID, ActivityDate, exclude, ExpirationDate, KIDID, ImageID) values (@P0, @P1, @P2, @P3, @P4, @P5, @P6)     </frame>
     <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown     </frame>
    </executionStack>
    <inputbuf>
Select 1    </inputbuf>
   </process>
  </process-list>
  <resource-list>
   <pagelock fileid="1" pageid="295182" dbid="7" objectname="Database.dbo.ZAT_KID" id="lock16ec04700" mode="X" associatedObjectId="72057594481606656">
    <owner-list>
     <owner id="processedb6d8" mode="X"/>
    </owner-list>
    <waiter-list>
     <waiter id="processec49b8" mode="X" requestType="wait"/>
    </waiter-list>
   </pagelock>
   <pagelock fileid="1" pageid="295211" dbid="7" objectname="Database.dbo.ZAT_Image" id="lock6e16cea00" mode="X" associatedObjectId="72057594441498624">
    <owner-list>
     <owner id="processec49b8" mode="X"/>
    </owner-list>
    <waiter-list>
     <waiter id="processedb6d8" mode="X" requestType="wait"/>
    </waiter-list>
   </pagelock>
  </resource-list>
 </deadlock>
</deadlock-list>
Run Code Online (Sandbox Code Playgroud)

进一步编辑:以下是两个表架构:

CREATE TABLE [dbo].[Image](
    [ImageID] [int] NOT NULL,
    [KIDID] [int] NULL,
    [ExpirationDate] [datetime] NULL,
    [Exclude] [nchar](1) NULL,
    [ActivityDate] [datetime] NULL,
    [ActivityByID] [int] NULL,
    [ImageStoreKey] [nvarchar](255) NULL,
 CONSTRAINT [PK_Image2] PRIMARY KEY CLUSTERED 
(
    [ImageID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
) ON [PRIMARY]

ALTER TABLE [dbo].[Image]  WITH NOCHECK ADD  CONSTRAINT [FK_Image_KID] FOREIGN KEY([KIDID])
REFERENCES [dbo].[KID] ([KIDID])
GO

ALTER TABLE [dbo].[Image] CHECK CONSTRAINT [FK_Image_KID]
GO
Run Code Online (Sandbox Code Playgroud)

和 KID 表:

CREATE TABLE [dbo].[KID](
    [KIDID] [int] NOT NULL,
    [KID] [nchar](7) NULL,
    [KosherStatusID] [int] NULL,
    [RestrictionText] [nvarchar](255) NULL,
    [ExpirationDate] [datetime] NULL,
    [IssuedDate] [datetime] NULL,
    [Name] [nvarchar](255) NULL,
    [Brand] [nvarchar](255) NULL,
    [Void] [char](1) NULL,
    [RabbiSigner] [nvarchar](255) NULL,
    [Extended] [nchar](1) NULL,
    [AgencyID] [int] NULL,
    [VendorID] [int] NULL,
    [PublicVisible] [nchar](1) NULL,
    [TempOwner] [nvarchar](20) NULL,
    [Source] [nvarchar](20) NULL,
    [Dataless] [nvarchar](1) NULL,
    [Overridden] [nchar](1) NULL,
    [ActivityDate] [datetime] NULL,
    [ActivityByID] [int] NULL,
    [UKDAgencyCode] [nchar](12) NULL,
    [UKDAgencyUniqueID] [nvarchar](50) NULL,
    [SecLevel] [int] NOT NULL,
 CONSTRAINT [PK_KID] PRIMARY KEY CLUSTERED 
(
    [KIDID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
) ON [PRIMARY]
Run Code Online (Sandbox Code Playgroud)

ZAT 表:

CREATE TABLE [dbo].[ZAT_KID](
    [ZAT_KID_ID] [int] IDENTITY(1,1) NOT NULL,
    [KIDID] [int] NOT NULL,
    [KID] [nchar](7) NULL,
    [KosherStatusID] [int] NULL,
    [RestrictionText] [nvarchar](255) NULL,
    [ExpirationDate] [datetime] NULL,
    [IssuedDate] [datetime] NULL,
    [Name] [nvarchar](255) NULL,
    [Brand] [nvarchar](255) NULL,
    [Void] [char](1) NULL,
    [RabbiSigner] [nvarchar](255) NULL,
    [Extended] [nchar](1) NULL,
    [AgencyID] [int] NULL,
    [VendorID] [int] NULL,
    [PublicVisible] [nchar](1) NULL,
    [TempOwner] [nvarchar](20) NULL,
    [Source] [nvarchar](20) NULL,
    [Dataless] [nvarchar](1) NULL,
    [Overridden] [nchar](1) NULL,
    [ActivityDate] [datetime] NULL,
    [ActivityByID] [int] NULL,
    [Mode] [nvarchar](20) NULL,
    [UKDAgencyCode] [nchar](12) NULL,
    [UKDAgencyUniqueID] [nvarchar](50) NULL,
    [SecLevel] [int] NULL,
 CONSTRAINT [PK_ZAT_KID] PRIMARY KEY CLUSTERED 
(
    [ZAT_KID_ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
) ON [PRIMARY]






CREATE TABLE [dbo].[ZAT_Image](
    [ZAT_Image_ID] [int] IDENTITY(1,1) NOT NULL,
    [ImageID] [int] NOT NULL,
    [KIDID] [int] NULL,
    [ActivityDate] [datetime] NULL,
    [ActivityByID] [int] NULL,
    [Mode] [nvarchar](20) NULL,
    [ExpirationDate] [datetime] NULL,
    [Exclude] [nchar](1) NULL,
 CONSTRAINT [PK_ZAT_ImageID] PRIMARY KEY CLUSTERED 
(
    [ZAT_Image_ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
) ON [PRIMARY]
Run Code Online (Sandbox Code Playgroud)

Rem*_*anu 7

  • T1 SPID 93 processec49b8:UPDATE kid SET activityByID=@P0, activityDate=@P1 WHERE kidID=@P2触发器INSERT INTO ZAT_KID with (PAGLOCK)
  • T2 SPID 64 处理b6d8:insert into Image ...触发器INSERT INTO ZAT_Image with (PAGLOCK)

T1 想要 X 页 295182 (ZAT_KID) 并且有 X 页 295211 (ZAT_Image)。T2 需要 X 页 295211 (ZAT_Image) 并且在 295182 (ZAT_KID) 上有 X。这表明所涉及的两个 spid 的先前活动在这里起作用:T1 先前已锁定 ZAT_Image 上的一个页面,但当前堆栈与该表无关。类似地,T2 之前在 ZAT_Image 上锁定了一个页面,但当前堆栈与它无关。

场景非常清楚:虚假使用理解不足的提示。T1锁定了ZAT_Image的插入点,而T2锁定了ZAT_KID的插入点。僵局是不可避免的INSERT WITH (PAGLOCK)是罪魁祸首。你在玩火,你被烧伤了。停止使用您不理解的提示。去掉 PAGLOCK 提示,让引擎决定合适的策略。保留审计表中的插入点空闲以供进一步插入。用棍子击败将 PAGLOCK 添加到 INSERT 的人。