Фон
У меня есть приложение, которое периодически получает большие объемы данных из внешнего источника (например, XML-файла) и вставляет эти данные в базу данных. Во время этой операции ничто другое не обращается к базе данных. Поскольку имеется большой объем данных, я использую настраиваемое количество потоков для выполнения вставки.
Приложение использует Entity Framework. Однако для этой конкретной операции из-за соображений производительности ADO.Net используется для выполнения хранимой процедуры для вставки данных в таблицу заголовков (ответы) и таблицу сведений (ListAnswerSelections).
Этот проект предшествует EF Migrations. Оригинальный DbContext реализует IDatabaseInitializer для создания представлений, индексов и т. д. Работает как шарм. Я только что повторно реализовал наш DbContext, чтобы правильно использовать миграцию EF. Вот где все становится странным.
Проблема
Когда операция массовой загрузки выполняется с одним потоком для новой схемы, созданной с помощью повторно реализованного DbContext, она работает нормально. Однако когда я использую 2 или более потоков, я получаю высокий уровень взаимоблокировок в индексе первичного ключа ListAnswerSelections (подробная таблица). Диаграмма взаимоблокировки выглядит так:
Если я использую исходный контекст и создаю с ним отдельную новую схему, операция массовой загрузки выполняется без взаимоблокировок с 8 потоками, записывающими в базу данных.
В обоих случаях для каждого запуска теста создается новая схема и импортируются идентичные XML-файлы. Тесты проводились несколько раз для каждой схемы, каждый раз с одним и тем же результатом.
Когда я использую инструменты сравнения схем в Visual Studio 2012, чтобы найти различия между схемами, созданными исходным и повторно реализованным 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 (или более) потоков, в то время как другая испытывает нулевые взаимоблокировки с записью 8 потоков?
Обновление: взаимоблокировка 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