Ok, I'm totally lost on deadlock issue. I just don't know how to solve this.
I have these three tables (I have removed not important columns):
CREATE TABLE [dbo].[ManageServicesRequest]
(
[ReferenceTransactionId] INT NOT NULL,
[OrderDate] DATETIMEOFFSET(7) NOT NULL,
[QueuePriority] INT NOT NULL,
[Queued] DATETIMEOFFSET(7) NULL,
CONSTRAINT [PK_ManageServicesRequest] PRIMARY KEY CLUSTERED ([ReferenceTransactionId]),
)
CREATE TABLE [dbo].[ServiceChange]
(
[ReferenceTransactionId] INT NOT NULL,
[ServiceId] VARCHAR(50) NOT NULL,
[ServiceStatus] CHAR(1) NOT NULL,
[ValidFrom] DATETIMEOFFSET(7) NOT NULL,
CONSTRAINT [PK_ServiceChange] PRIMARY KEY CLUSTERED ([ReferenceTransactionId],[ServiceId]),
CONSTRAINT [FK_ServiceChange_ManageServiceRequest] FOREIGN KEY ([ReferenceTransactionId]) REFERENCES [ManageServicesRequest]([ReferenceTransactionId]) ON DELETE CASCADE,
INDEX [IDX_ServiceChange_ManageServiceRequestId] ([ReferenceTransactionId]),
INDEX [IDX_ServiceChange_ServiceId] ([ServiceId])
)
CREATE TABLE [dbo].[ServiceChangeParameter]
(
[ReferenceTransactionId] INT NOT NULL,
[ServiceId] VARCHAR(50) NOT NULL,
[ParamCode] VARCHAR(50) NOT NULL,
[ParamValue] VARCHAR(50) NOT NULL,
[ParamValidFrom] DATETIMEOFFSET(7) NOT NULL,
CONSTRAINT [PK_ServiceChangeParameter] PRIMARY KEY CLUSTERED ([ReferenceTransactionId],[ServiceId],[ParamCode]),
CONSTRAINT [FK_ServiceChangeParameter_ServiceChange] FOREIGN KEY ([ReferenceTransactionId],[ServiceId]) REFERENCES [ServiceChange] ([ReferenceTransactionId],[ServiceId]) ON DELETE CASCADE,
INDEX [IDX_ServiceChangeParameter_ManageServiceRequestId] ([ReferenceTransactionId]),
INDEX [IDX_ServiceChangeParameter_ServiceId] ([ServiceId]),
INDEX [IDX_ServiceChangeParameter_ParamCode] ([ParamCode])
)
And these two procedures:
CREATE PROCEDURE [dbo].[spCreateManageServicesRequest]
@ReferenceTransactionId INT,
@OrderDate DATETIMEOFFSET,
@QueuePriority INT,
@Services ServiceChangeUdt READONLY,
@Parameters ServiceChangeParameterUdt READONLY
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
/* VYTVOŘ NOVÝ REQUEST NA ZMĚNU SLUŽEB */
/* INSERT REQUEST */
INSERT INTO [dbo].[ManageServicesRequest]
([ReferenceTransactionId]
,[OrderDate]
,[QueuePriority]
,[Queued])
VALUES
(@ReferenceTransactionId
,@OrderDate
,@QueuePriority
,NULL)
/* INSERT SERVICES */
INSERT INTO [dbo].[ServiceChange]
([ReferenceTransactionId]
,[ServiceId]
,[ServiceStatus]
,[ValidFrom])
SELECT
@ReferenceTransactionId AS [ReferenceTransactionId]
,[ServiceId]
,[ServiceStatus]
,[ValidFrom]
FROM @Services AS [S]
/* INSERT PARAMS */
INSERT INTO [dbo].[ServiceChangeParameter]
([ReferenceTransactionId]
,[ServiceId]
,[ParamCode]
,[ParamValue]
,[ParamValidFrom])
SELECT
@ReferenceTransactionId AS [ReferenceTransactionId]
,[ServiceId]
,[ParamCode]
,[ParamValue]
,[ParamValidFrom]
FROM @Parameters AS [P]
END TRY
BEGIN CATCH
THROW
END CATCH
END
CREATE PROCEDURE [dbo].[spGetManageServicesRequest]
@ReferenceTransactionId INT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
/* VRAŤ MANAGE SERVICES REQUEST PODLE ID */
SELECT
[MR].[ReferenceTransactionId],
[MR].[OrderDate],
[MR].[QueuePriority],
[MR].[Queued],
[SC].[ReferenceTransactionId],
[SC].[ServiceId],
[SC].[ServiceStatus],
[SC].[ValidFrom],
[SP].[ReferenceTransactionId],
[SP].[ServiceId],
[SP].[ParamCode],
[SP].[ParamValue],
[SP].[ParamValidFrom]
FROM [dbo].[ManageServicesRequest] AS [MR]
LEFT JOIN [dbo].[ServiceChange] AS [SC] ON [SC].[ReferenceTransactionId] = [MR].[ReferenceTransactionId]
LEFT JOIN [dbo].[ServiceChangeParameter] AS [SP] ON [SP].[ReferenceTransactionId] = [SC].[ReferenceTransactionId] AND [SP].[ServiceId] = [SC].[ServiceId]
WHERE [MR].[ReferenceTransactionId] = @ReferenceTransactionId
END TRY
BEGIN CATCH
THROW
END CATCH
END
Now these are used this way (it's a simplified C# method that creates a record and then posts record to a micro service queue):
public async Task Consume(ConsumeContext<CreateCommand> context)
{
using (var sql = sqlFactory.Cip)
{
/*SAVE REQUEST TO DATABASE*/
sql.StartTransaction(System.Data.IsolationLevel.Serializable); <----- First transaction starts
/* Create id */
var transactionId = await GetNewId(context.Message.CorrelationId);
/* Create manage services request */
await sql.OrderingGateway.ManageServices.Create(transactionId, context.Message.ApiRequest.OrderDate, context.Message.ApiRequest.Priority, services);
sql.Commit(); <----- First transaction ends
/// .... Some other stuff ...
/* Fetch the same object you created in the first transaction */
Try
{
sql.StartTransaction(System.Data.IsolationLevel.Serializable);
var request = await sql.OrderingGateway.ManageServices.Get(transactionId); <----- HERE BE THE DEADLOCK,
request.Queued = DateTimeOffset.Now;
await sql.OrderingGateway.ManageServices.Update(request);
... Here is a posting to a microservice queue ...
sql.Commit();
}
catch (Exception)
{
sql.RollBack();
}
/// .... Some other stuff ....
}
Now my problem is. Why are these two procedures getting deadlocked? The first and the second transaction are never run in parallel for the same record.
Here is the deadlock detail:
<deadlock>
<victim-list>
<victimProcess id="process1dbfa86c4e8" />
</victim-list>
<process-list>
<process id="process1dbfa86c4e8" taskpriority="0" logused="0" waitresource="KEY: 18:72057594046775296 (b42d8e559092)" waittime="2503" ownerId="33411557480" transactionname="user_transaction" lasttranstarted="2021-12-01T01:06:15.303" XDES="0x1ddd2df4420" lockMode="RangeS-S" schedulerid="20" kpid="23000" status="suspended" spid="55" sbid="2" ecid="0" priority="0" trancount="1" lastbatchstarted="2021-12-01T01:06:15.310" lastbatchcompleted="2021-12-01T01:06:15.300" lastattention="1900-01-01T00:00:00.300" clientapp="Core Microsoft SqlClient Data Provider" hostpid="11020" isolationlevel="serializable (4)" xactid="33411557480" currentdb="18" currentdbname="xxx" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056">
<executionStack>
<frame procname="xxx.dbo.spGetManageServicesRequest" line="10" stmtstart="356" stmtend="4256" sqlhandle="0x030012001374fc02f91433019aad000001000000000000000000000000000000000000000000000000000000"></frame>
</executionStack>
</process>
<process id="process1dbfa1c1c28" taskpriority="0" logused="1232" waitresource="KEY: 18:72057594046971904 (ffffffffffff)" waittime="6275" ownerId="33411563398" transactionname="user_transaction" lasttranstarted="2021-12-01T01:06:16.450" XDES="0x3d4e842c420" lockMode="RangeI-N" schedulerid="31" kpid="36432" status="suspended" spid="419" sbid="2" ecid="0" priority="0" trancount="2" lastbatchstarted="2021-12-01T01:06:16.480" lastbatchcompleted="2021-12-01T01:06:16.463" lastattention="1900-01-01T00:00:00.463" clientapp="Core Microsoft SqlClient Data Provider" hostpid="11020" isolationlevel="serializable (4)" xactid="33411563398" currentdb="18" currentdbname="xxx" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056">
<executionStack>
<frame procname="xxx.dbo.spCreateManageServicesRequest" line="40" stmtstart="2592" stmtend="3226" sqlhandle="0x03001200f01ab84aeb1433019aad000001000000000000000000000000000000000000000000000000000000"></frame>
</executionStack>
</process>
</process-list>
<resource-list>
<keylock hobtid="72057594046775296" dbid="18" objectname="xxx.dbo.ServiceChange" indexname="PK_ServiceChange" id="lock202ecfd0380" mode="X" associatedObjectId="72057594046775296">
<owner-list>
<owner id="process1dbfa1c1c28" mode="X" />
</owner-list>
<waiter-list>
<waiter id="process1dbfa86c4e8" mode="RangeS-S" requestType="wait" />
</waiter-list>
</keylock>
<keylock hobtid="72057594046971904" dbid="18" objectname="xxx.dbo.ServiceChangeParameter" indexname="PK_ServiceChangeParameter" id="lock27d3d371880" mode="RangeS-S" associatedObjectId="72057594046971904">
<owner-list>
<owner id="process1dbfa86c4e8" mode="RangeS-S" />
</owner-list>
<waiter-list>
<waiter id="process1dbfa1c1c28" mode="RangeI-N" requestType="wait" />
</waiter-list>
</keylock>
</resource-list>
</deadlock>
Why is this deadlock happening? How do I avoid it in the future?
Edit: Here is a plan for Get procedure: https://www.brentozar.com/pastetheplan/?id=B1UMMhaqF
Another Edit: After GSerg comment, I changed the line number in the deadlock graph from 65 to 40, due to removed columns that are not important to the question.


