Hi,
In my database I have 2 tables - Batch & Device and a stored procedure - AddBatchDevice which accepts a XML input as follows –
And inserts one record in Batch Table and N number of records as specified in the Device table.
<BatchDevice>
<BatchGuid/>
<Size/>
<Devices>
<Device>
<HwId/>
</Device>
<Device>
<HwId/>
</Device>
</Devices>
</BatchDevice>
When I call the stored procedure from multiple instances - its getting into deadlock (KeyLock) on the INSERT Statement (for Device table).
If the foreign key constraint between Batch and Device table is deleted – the deadlock doesn’t happen but I cannot remove the foreign key constraint.
Please let me know if there are other ways to resolve this deadlock.
Steps for repro:
1) Create a test Database in the server
2) Run the code snippet Create.sql on the test database (it will create 2 tables and one stored procedure)
3) Open two query windows for the test Database and execute the command listed in code snippet deadlock.sql simultaneously.
Code Snippet - Create.sql
Code Snippet
CREATE TABLE [dbo].[Batch](
[Id] [int] IDENTITY(1,1) NOT NULL,
[BatchGuid] [uniqueidentifier] NOT NULL,
[Size] [int] NOT NULL
CONSTRAINT [PK_Batch] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY],
CONSTRAINT [ukBatchGuid_Batch] UNIQUE NONCLUSTERED
(
[BatchGuid] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Device](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[Hwid] [bigint] NOT NULL,
[BatchId] [int] NOT NULL,
CONSTRAINT [PK_Device] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [IX_Device_BatchID] ON [dbo].[Device]
(
[BatchId] ASC
)WITH (PAD_INDEX = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF) ON [PRIMARY]
ALTER TABLE [dbo].[Device] WITH CHECK ADD CONSTRAINT [FK_Device_Batch] FOREIGN KEY([BatchId])
REFERENCES [dbo].[Batch] ([Id])
GO
ALTER TABLE [dbo].[Device] CHECK CONSTRAINT [FK_Device_Batch]
GO
CREATE PROCEDURE [dbo].[AddBatchDevice]
@.batchDeviceXML XML
AS
BEGIN
DECLARE @.batchId INT
DECLARE @.batchSize INT
DECLARE @.err INT
DECLARE @.rowCount INT
DECLARE @.itc INT
SET @.rowCount = 0
SET @.err = 1
SET @.itc = @.@.TRANCOUNT
SET NOCOUNT ON;
BEGIN TRY
IF (@.itc = 0) BEGIN TRANSACTION;
-- Populate the Batch Table
INSERT INTO dbo.Batch(BatchGuid, Size)
SELECT
BatchDetails.value('BatchGuid[1]','UNIQUEIDENTIFIER'),
BatchDetails.value('Size[1]','INT')
FROM @.batchDeviceXML.nodes('/BatchDevice') as R(BatchDetails)
SELECT @.err = @.@.ERROR, @.rowCount = @.@.ROWCOUNT, @.batchId = SCOPE_IDENTITY()
-- Check if the Batch got added.
IF (@.rowCount = 0) OR (@.err <> 0)
BEGIN
SET @.err = 13
GOTO ROLLBACKTRAN;
END
-- Retrieve Batch Size for the current Batch
SELECT @.batchSize = Size FROM dbo.Batch WHERE Id = @.batchId;
-- Populate the Device table
INSERT INTO dbo.Device (HwId, BatchId)
SELECT x.value('./HwId[1]','BIGINT') AS HwId,
@.batchId
FROM @.batchDeviceXML.nodes('/BatchDevice/Devices/Device') as R(x)
SELECT @.err = @.@.ERROR, @.rowCount = @.@.ROWCOUNT
-- Check if the BatchSize is same as number of Device Added.
IF (@.batchSize <> @.rowCount)
BEGIN
SET @.err = 6
GOTO ROLLBACKTRAN;
END
COMMITTRAN:
IF (XACT_STATE() = 1) and (@.itc = 0) COMMIT TRAN;
GOTO RETURNPOINT;
ROLLBACKTRAN:
IF (@.itc = 0) AND (@.@.TRANCOUNT > 0) and (XACT_STATE() <> 0) ROLLBACK TRAN;
RETURNPOINT:
RETURN @.err;
END TRY
BEGIN CATCH
-- rollback if the transaction is active and start from the proc
-- @.itc is the the initial transaction count when it enters the proc,
-- XACT_STATE() is zero when the transaction inactive
IF (@.itc = 0) AND (@.@.TRANCOUNT > 0) AND (XACT_STATE() <> 0)
BEGIN
ROLLBACK TRANSACTION;
END
-- Display error Message
-- EXEC [dbo].[sp_RethrowError];
PRINT ERROR_MESSAGE()
END CATCH
RETURN @.err
END
GO
Code Snippet: Deadlock
Code Snippet
DECLARE @.p1 XML
DECLARE @.guid NVARCHAR(50)
SET @.guid = CONVERT(NVARCHAR(50),newid())
SET @.p1=convert(xml,N'<BatchDevice><BatchGuid>' + @.guid + N'</BatchGuid><Size>100</Size><Devices><Device><HwId>725186448764602445</HwId></Device><Device><HwId>725133527699733597</HwId></Device><Device><HwId>725163621214188850</HwId></Device><Device><HwId>725360424970446632</HwId></Device><Device><HwId>725155285022984412</HwId></Device><Device><HwId>725344818310067191</HwId></Device><Device><HwId>725088231823400156</HwId></Device><Device><HwId>725358312743308913</HwId></Device><Device><HwId>725188965066112420</HwId></Device><Device><HwId>725236470409711434</HwId></Device><Device><HwId>725214869542733619</HwId></Device><Device><HwId>725269236196070583</HwId></Device><Device><HwId>725245337788308830</HwId></Device><Device><HwId>725286270478665392</HwId></Device><Device><HwId>725122001939204270</HwId></Device><Device><HwId>725264967238497755</HwId></Device><Device><HwId>725278832846587913</HwId></Device><Device><HwId>725320615075719837</HwId></Device><Device><HwId>725304873858777678</HwId></Device>
<Device><HwId>725137863535379341</HwId></Device><Device><HwId>725249612385940875</HwId></Device><Device><HwId>725119300331609117</HwId></Device><Device><HwId>725226526392636646</HwId></Device><Device><HwId>725216389536026547</HwId></Device><Device><HwId>725269570366518415</HwId></Device><Device><HwId>725263311873665561</HwId></Device><Device><HwId>725340378202337092</HwId></Device><Device><HwId>725313855311969064</HwId></Device><Device><HwId>725116090589146170</HwId></Device><Device><HwId>725216246506949993</HwId></Device><Device><HwId>725315130396375765</HwId></Device><Device><HwId>725209536271189293</HwId></Device><Device><HwId>725182347479939757</HwId></Device><Device><HwId>725219305400071868</HwId></Device><Device><HwId>725174388214781749</HwId></Device><Device><HwId>725255813501735845</HwId></Device><Device><HwId>725149806189360593</HwId></Device><Device><HwId>725083241757904629</HwId></Device><Device><HwId>725238516856348792</HwId></Device>
<Device><HwId>725289375597612738</HwId></Device><Device><HwId>725268638923108985</HwId></Device><Device><HwId>725251187560381100</HwId></Device><Device><HwId>725121127358656212</HwId></Device><Device><HwId>725338523561846433</HwId></Device><Device><HwId>725163562362430808</HwId></Device><Device><HwId>725161331064600595</HwId></Device><Device><HwId>725154418534216304</HwId></Device><Device><HwId>725212177384074929</HwId></Device><Device><HwId>725091809814393355</HwId></Device><Device><HwId>725223569423617615</HwId></Device><Device><HwId>725218658642332889</HwId></Device><Device><HwId>725086919050359441</HwId></Device><Device><HwId>725167692336724911</HwId></Device><Device><HwId>725203132528167587</HwId></Device><Device><HwId>725176275524886059</HwId></Device><Device><HwId>725270403041508300</HwId></Device><Device><HwId>725260454099795083</HwId></Device><Device><HwId>725086539844081549</HwId></Device><Device><HwId>725105992902748925</HwId></Device>
<Device><HwId>725251163188132078</HwId></Device><Device><HwId>725151571461604189</HwId></Device><Device><HwId>725307710183909876</HwId></Device><Device><HwId>725162260369257115</HwId></Device><Device><HwId>725144167553782168</HwId></Device><Device><HwId>725306575103488759</HwId></Device><Device><HwId>725268023454736878</HwId></Device><Device><HwId>725191445308019528</HwId></Device><Device><HwId>725145699765219874</HwId></Device><Device><HwId>725331934019785035</HwId></Device><Device><HwId>725080177396169331</HwId></Device><Device><HwId>725141959395399544</HwId></Device><Device><HwId>725338929516312679</HwId></Device><Device><HwId>725155342379274566</HwId></Device><Device><HwId>725248907355390889</HwId></Device><Device><HwId>725284235773192184</HwId></Device><Device><HwId>725319940800576294</HwId></Device><Device><HwId>725240240269421134</HwId></Device><Device><HwId>725116844602172152</HwId></Device><Device><HwId>725242591005816826</HwId></Device>
<Device><HwId>725207269552993483</HwId></Device><Device><HwId>725309585361092409</HwId></Device><Device><HwId>725347869086295885</HwId></Device><Device><HwId>725320669266522591</HwId></Device><Device><HwId>725356839312994030</HwId></Device><Device><HwId>725123424605912001</HwId></Device><Device><HwId>725230939227577006</HwId></Device><Device><HwId>725127055700922395</HwId></Device><Device><HwId>725351488560331421</HwId></Device><Device><HwId>725358800566255337</HwId></Device><Device><HwId>725307540748164922</HwId></Device><Device><HwId>725157566714134657</HwId></Device><Device><HwId>725156382030214485</HwId></Device><Device><HwId>725257373630207105</HwId></Device><Device><HwId>725352188733356044</HwId></Device><Device><HwId>725110836101340998</HwId></Device><Device><HwId>725189887487119685</HwId></Device><Device><HwId>725095111375267930</HwId></Device><Device><HwId>725117401433091911</HwId></Device><Device><HwId>725177169568742307</HwId></Device>
<Device><HwId>725330167025361822</HwId></Device></Devices></BatchDevice>')
EXEC dbo.[AddBatchDevice] @.BatchDeviceXML=@.p1
Go 1000
From looking at the code the deadlock should be coming from the non-clustered index on the device table. What I have seen in the past is that sql server will take a more agressive lock, for instance a page lock instead of a row lock. If you run a sql profile trace and capture the locks being acquired you will likely see that a higher lock is being taken. My suggestions as a quick fix is to use the with clause on the insert statement providing the rowlock directive. The syntax is INSERT INTO TableA WITH (ROWLOCK) ...
HTH.
-Chris
|||I tried adding WITH (ROWLOCK) for the insert statement - stilll deadlocks are occuring.
Please let me know if you have any other suggestions.
Thanks,
Loonysan
You need to set the isolation level to SNAPSHOT. In order to do so you must first set the ALLOW_SNAPSHOT_ISOLATION database option to ON. Here is the command to do this:
Code Snippet
ALTER DATABASE MyDBName SET ALLOW_SNAPSHOT_ISOLATION ON
Next, add this statement at the beginning of your procedure:
Code Snippet
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
I hope this solves your problem.
Best regards,
Sami Samir
|||I looked at the execution plan of the insert statement into the device table and the foreign key lookup was doing a scan and a merge join. This was what was causing the issue. I have seen this before when the datatypes do not match, for example int to bigint, but that is not the case with your code. I believe it has something to do with the xml function that is causing the problems. I modified the code to parse the xml and put the results in a table variable and then do the insert and I did not receive any deadlocks. Let me know if you have the same results on your side. Below is the code snippet. Thanks.
-Chris
-- Populate the Device table
declare @.myTable TABLE (hwID bigint,BatchID int);
INSERT INTO @.myTable (HwId, BatchId)
SELECT x.value('./HwId[1]','BIGINT') AS HwId,
@.batchId
FROM @.batchDeviceXML.nodes('/BatchDevice/Devices/Device') as R(x)
INSERT INTO dbo.Device (HwId, BatchId)
SELECT hwID,BatchID
FROM @.myTable;
|||Hi Chris,
Your logic works
Thanks a lot.
- Loonysan
No comments:
Post a Comment