两个架构的故事:一个死锁,一个不死

时间:2014-08-26 19:34:34

标签: sql-server sql-server-2008-r2 ado.net database-deadlocks

背景

我有一个应用程序,它定期从外部源(恰好是XML文件)接收大量数据,并将该数据插入数据库。在此操作期间,没有其他任何内容正在访问数据库由于存在大量数据,因此我使用可配置数量的线程来执行插入。

该应用程序使用Entity Framework。但是,对于此特定操作,由于性能考虑,ADO.Net用于执行存储过程以将数据插入到头表(Answers)和详细信息表(ListAnswerSelections)中。

该项目早于EF Migrations。原始的DbContext实现了IDatabaseInitializer来创建视图,索引等。像魅力一样工作。我刚刚重新实现了我们的DbContext以正确使用EF迁移。这就是事情变得奇怪的地方。

问题

当使用单个线程针对使用重新实现的DbContext创建的新模式运行批量加载操作时,它可以正常工作。但是,当我使用2个或更多线程时,我在ListAnswerSelections(详细信息表)的主键索引上获得了很高的死锁率。死锁图如下所示:

Lock Diagram

如果我使用原始上下文并使用它创建一个单独的新模式,则批量加载操作将运行无死锁,并且有8个线程写入数据库。

在这两种情况下,都会为每个测试运行创建一个新模式,并导入相同的XML文件。测试已经针对每个模式运行了几次,每次都有相同的结果。

当我使用Visual Studio 2012中的Schema Compare工具查找原始模式与重新实现的DbContext创建的模式之间的差异时,我发现此操作中涉及的两个表中的任何一个都没有差别。

守则

调用存储过程的C#代码如下所示:

cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "dbo.uspInsertAnswer";
// Add appropriate parameters
int resultCount = cmd.ExecuteNonQuery();

存储过程本身如下:

CREATE PROCEDURE [dbo].[uspInsertAnswer] @defId INT, @partId INT, @weight FLOAT, @questionId INT, @listValues tvpInt32List READONLY AS
    DECLARE @type INT
    DECLARE @id INT

    BEGIN TRY   
        INSERT INTO dbo.Answers VALUES(@questionId, @partId, NULL, 0, @weight, GETDATE(), NULL, @partId, @defId, @type)

        INSERT INTO dbo.ListAnswerSelections SELECT n, '', GETDATE(), @questionId, @partId FROM @listValues
    END TRY
    BEGIN CATCH
        -- Error Handling
    END CATCH

所涉及的表看起来像(为简洁起见,删除了一些非索引列):

CREATE TABLE [dbo].[Answers](
    [RelatedQuestionId] [int] NOT NULL,
    [RelatedParticipantId] [int] NOT NULL,
    [Text] [nvarchar](max) NULL,
    [Response_ParticipantId] [int] NULL,
    [Response_DefinitionId] [int] NULL,
    [Type] [nvarchar](128) NOT NULL,
 CONSTRAINT [PK_dbo.Answers] PRIMARY KEY CLUSTERED 
(
    [RelatedQuestionId] ASC,
    [RelatedParticipantId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]


CREATE TABLE [dbo].[ListAnswerSelections](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [NumericValue] [int] NOT NULL,
    [ListAnswer_RelatedQuestionId] [int] NOT NULL,
    [ListAnswer_RelatedParticipantId] [int] NOT NULL,
 CONSTRAINT [PK_dbo.ListAnswerSelections] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

我的问题

两个模式之间可能有什么不同(并且在VS模式比较工具中没有显示)会导致其中一个模式经历2个(或更多)线程写入的频繁死锁,而另一个模式经历零死锁线程写作?

更新:死锁XML

<deadlock-list>
 <deadlock victim="process53ace08">
  <process-list>
   <process id="process53ace08" taskpriority="0" logused="1360" waitresource="KEY: 11:72057594041663488 (2c3c53413efb)" waittime="7106" ownerId="6158342" transactionname="user_transaction" lasttranstarted="2014-08-25T18:29:49.570" XDES="0xbf3cf950" lockMode="RangeS-S" schedulerid="7" kpid="2808" status="suspended" spid="59" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2014-08-25T18:30:01.240" lastbatchcompleted="2014-08-25T18:30:01.237" lastattention="2014-08-25T18:22:27.293" clientapp=".Net SqlClient Data Provider" hostname="CRUNCHBOX" hostpid="10164" loginname="Crunchbox\Eric" isolationlevel="read committed (2)" xactid="6158342" currentdb="11" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
    <executionStack>
     <frame procname="Survey_DEV_BAT.dbo.uspInsertAnswer" line="17" stmtstart="1088" stmtend="1332" sqlhandle="0x03000b004e241b1505a42b0192a300000100000000000000">
INSERT INTO dbo.Answers VALUES(@questionId, @partId, NULL, 0, @weight, GETDATE(), NULL, @partId, @defId, @type)     </frame>
    </executionStack>
    <inputbuf>
Proc [Database Id = 11 Object Id = 354100302]    </inputbuf>
   </process>
   <process id="process534ee08" taskpriority="0" logused="1360" waitresource="KEY: 11:72057594041663488 (354416591f4b)" waittime="4983" ownerId="6158339" transactionname="user_transaction" lasttranstarted="2014-08-25T18:29:49.570" XDES="0xc1d01950" lockMode="RangeS-S" schedulerid="4" kpid="13824" status="suspended" spid="54" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2014-08-25T18:30:01.237" lastbatchcompleted="2014-08-25T18:30:01.237" clientapp=".Net SqlClient Data Provider" hostname="CRUNCHBOX" hostpid="10164" loginname="Crunchbox\Eric" isolationlevel="read committed (2)" xactid="6158339" currentdb="11" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
    <executionStack>
     <frame procname="Survey_DEV_BAT.dbo.uspInsertAnswer" line="17" stmtstart="1088" stmtend="1332" sqlhandle="0x03000b004e241b1505a42b0192a300000100000000000000">
INSERT INTO dbo.Answers VALUES(@questionId, @partId, NULL, 0, @weight, GETDATE(), NULL, @partId, @defId, @type)     </frame>
    </executionStack>
    <inputbuf>
Proc [Database Id = 11 Object Id = 354100302]    </inputbuf>
   </process>
  </process-list>
  <resource-list>
   <keylock hobtid="72057594041663488" dbid="11" objectname="Survey_DEV_BAT.dbo.ListAnswerSelections" indexname="PK_dbo.ListAnswerSelections" id="lock8be36880" mode="RangeX-X" associatedObjectId="72057594041663488">
    <owner-list>
     <owner id="process534ee08" mode="RangeX-X"/>
    </owner-list>
    <waiter-list>
     <waiter id="process53ace08" mode="RangeS-S" requestType="wait"/>
    </waiter-list>
   </keylock>
   <keylock hobtid="72057594041663488" dbid="11" objectname="Survey_DEV_BAT.dbo.ListAnswerSelections" indexname="PK_dbo.ListAnswerSelections" id="lock8544da00" mode="X" associatedObjectId="72057594041663488">
    <owner-list>
     <owner id="process53ace08" mode="X"/>
    </owner-list>
    <waiter-list>
     <waiter id="process534ee08" mode="RangeS-S" requestType="wait"/>
    </waiter-list>
   </keylock>
  </resource-list>
 </deadlock>
</deadlock-list>

更新:外键约束

ALTER TABLE [dbo].[ListAnswerSelections]  WITH NOCHECK ADD  CONSTRAINT [FK_dbo.ListAnswerSelections_dbo.Answers_ListAnswer_RelatedQuestionId_ListAnswer_RelatedParticipantId] FOREIGN KEY([ListAnswer_RelatedQuestionId], [ListAnswer_RelatedParticipantId])
REFERENCES [dbo].[Answers] ([RelatedQuestionId], [RelatedParticipantId])
ON DELETE CASCADE

1 个答案:

答案 0 :(得分:2)

发布死锁图。不是它的图片,实际的死锁图XML。图片类似于XML中信息的1%。例如,XML会回答您是否有lock hash collision

范围锁。为什么?这意味着可序列化的隔离,并且您的描述中没有任何内使用read committed。

与死锁无关,但如果您真正关心性能,请使用批量插入。与 true 批量插入一样。使用SqlBulkCopy使用真正的bulk insert API,而不是普通的INSERT语句。只有批量插入API可以achieve minimal logging,并且只有批量插入API可以在插入时进行数据流传输。

某些操作在内部使用可序列化隔离级别。外键约束和索引视图维护是这样的两个示例,请参阅Conor vs. Isolation Level Upgrade on UPDATE/DELETE Cascading RI

  

同样的条件适用于索引视图维护

捕获这两个案件的执行计划会立即指出问题。