背景
我有一个应用程序,它定期从外部源(恰好是XML文件)接收大量数据,并将该数据插入数据库。在此操作期间,没有其他任何内容正在访问数据库由于存在大量数据,因此我使用可配置数量的线程来执行插入。
该应用程序使用Entity Framework。但是,对于此特定操作,由于性能考虑,ADO.Net用于执行存储过程以将数据插入到头表(Answers)和详细信息表(ListAnswerSelections)中。
该项目早于EF Migrations。原始的DbContext实现了IDatabaseInitializer来创建视图,索引等。像魅力一样工作。我刚刚重新实现了我们的DbContext以正确使用EF迁移。这就是事情变得奇怪的地方。
问题
当使用单个线程针对使用重新实现的DbContext创建的新模式运行批量加载操作时,它可以正常工作。但是,当我使用2个或更多线程时,我在ListAnswerSelections(详细信息表)的主键索引上获得了很高的死锁率。死锁图如下所示:
如果我使用原始上下文并使用它创建一个单独的新模式,则批量加载操作将运行无死锁,并且有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
答案 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:
同样的条件适用于索引视图维护
捕获这两个案件的执行计划会立即指出问题。