Multiple updates to same table via stored procedure - sql-server

I am having recurring deadlocks on my application database and trying to understand what is causing them. Using extended events to track them I have identified two common causes, one via an T-SQL statement in the application and the other using a stored procedure. I'll limit this question just to the stored procedure.
I have a stored procedure called writeNominalTrans, I pass some variables from the software and it will either insert or update a record depending on whether a match is found. The XML Deadlock reports commonly refer to the update statements causing the problems, I'll post both stored procedure and an example XML report below.
I have done some reading to understand Deadlocks but most sources just talk about locking between two tables where as my issues resolve around updating records in a single table and not sure exactly how to resolve this.
I think this issue is that it locks the table by pages, possibly multiple at a time as it searches, looking for the correct row to update. If multiple users call the same procedure at the same time, it causes different pages to be locked, ending up with both waiting on each other to finish and thus a Deadlock. Please correct me if this isnt the case.
Within the stored procedure, there are begin/end statements with multiple update statements and not sure if this is one of the potentially issues. Should I put each update statement within it's own begin/end so it is only trying to do one statement at a time? This is assuming it is firing multiple statements per user and some times there are a larger number of processes than in the example below. Aside from this I'm not sure what else is causing the problem, please if you take a look and lend me your expertise.
Regards
David
Stored Procedure
[dbo].[spWriteNominalTrans]
#Recordkey Int Output,
#NominalDataKey int,
#NominalCode int,
#NominalType int,
#TransDate datetime,
#Memo nvarchar(500),
#debit money,
#Credit money,
#VatValueIN money,
#VatValueOut money,
#VatNLAccount int,
#VatRelationKey int,
#Reference nvarchar(50),
#Period int,
#FiscalYear int,
#UserID int,
#DateStamp datetime,
#Transtype nvarchar(150),
#vatcode int,
#vatrate decimal(18,2),
#GoodsValue money,
#AllowMerge bit,
#VatReconcileKey Integer,
#VatOut bit,
#LocationKey int
AS
IF exists ( select * from TblNominalTrans where TransDate= #TransDate and NominalCode = #NominalCode and Period = #Period and FiscalYear = #FiscalYear and Reference = #Reference and vatcode = #vatcode and transtype = #Transtype and #AllowMerge=1 and Memo=#Memo and #Transtype=Transtype and LocationKey =#LocationKey)
-- do an update
BEGIN
UPDATE TblNominalTrans SET
Memo=#Memo,
Debit = Debit + #Debit ,
Credit = Credit +#Credit,
GoodsValue = GoodsValue + #GoodsValue,
VatValueIN = VatValueIN + #VatValueIN,
VatValueOut = VatValueOut + #VatValueOut,
VatOut = #Vatout,
VatNLAccount=#VatNLAccount,
VatRelationKey=#VatRelationKey,
UserId=#UserId,
DateStamp=#DateStamp,
VatReconcileKey = #VatReconcileKey
WHERE TransDate= #TransDate and NominalCode = #NominalCode and Period = #Period and FiscalYear = #FiscalYear and Reference = #Reference and vatcode = #vatcode and Memo=#Memo and #Transtype=Transtype and LocationKey = #LocationKey
UPDATE TblNominalTrans SET
Credit = Credit - Debit, debit = 0
WHERE TransDate= #TransDate and NominalCode = #NominalCode and Period = #Period and FiscalYear = #FiscalYear and Reference = #Reference and vatcode = #vatcode and Memo=#Memo and #Transtype=Transtype and LocationKey = #LocationKey and credit >= debit and debit > 0
UPDATE TblNominalTrans SET
Debit = Debit - Credit, credit = 0
WHERE TransDate= #TransDate and NominalCode = #NominalCode and Period = #Period and FiscalYear = #FiscalYear and Reference = #Reference and vatcode = #vatcode and Memo=#Memo and #Transtype=Transtype and LocationKey = #LocationKey and debit >= credit and credit > 0
END
ELSE
-- do an insert
BEGIN
INSERT TblNominalTrans (
NominalDataKey,
NominalCode,
NominalType ,
TransDate ,
Memo ,
debit ,
Credit ,
VatValueIN,
VatValueOut ,
VatNLAccount ,
VatRelationKey ,
Reference ,
Period ,
FiscalYear ,
VatOut ,
TransType ,
UserID ,
DateStamp,
vatcode,
vatrate,
Goodsvalue,
VatReconcileKey,
LocationKey)
SELECT
#NominalDataKey ,
#NominalCode ,
#NominalType ,
#TransDate ,
#Memo ,
#debit ,
#Credit ,
#VatValueIN,
#VatValueOut ,
#VatNLAccount ,
#VatRelationKey ,
#Reference ,
#Period,
#FiscalYear,
#VatOut,
#transtype,
#UserID,
#DateStamp,
#vatcode,
#vatrate,
#goodsvalue,
#VatReconcileKey,
#LocationKey
END
Deadlock XML Report
<deadlock>
<victim-list>
<victimProcess id="process2c457dd0ca8" />
</victim-list>
<process-list>
<process id="process2c457dd0ca8" taskpriority="0" logused="0" waitresource="PAGE: 7:1:23277 " waittime="1684" ownerId="1782665" transactionname="UPDATE" lasttranstarted="2022-12-13T09:46:09" XDES="0x2c314a19900" lockMode="U" schedulerid="3" kpid="3212" status="suspended" spid="66" sbid="0" ecid="2" priority="0" trancount="0" lastbatchstarted="2022-12-13T09:46:08.930" lastbatchcompleted="2022-12-13T09:46:08.923" lastattention="1900-01-01T00:00:00.923" clientapp="Liquidity" hostname="TERM-SERVER" hostpid="5228" isolationlevel="read committed (2)" xactid="1782665" currentdb="7" lockTimeout="4294967295" clientoption1="536870944" clientoption2="128024">
<executionStack>
<frame procname="MDT_DB.dbo.spWriteNominalTrans" line="43" stmtstart="2092" stmtend="3384" sqlhandle="0x030007002b291964daa21d01caac000001000000000000000000000000000000000000000000000000000000">
UPDATE TblNominalTrans
SET
Memo=#Memo,
Debit = Debit + #Debit ,
Credit = Credit +#Credit,
GoodsValue = GoodsValue + #GoodsValue,
VatValueIN = VatValueIN + #VatValueIN,
VatValueOut = VatValueOut + #VatValueOut,
VatOut = #Vatout,
VatNLAccount=#VatNLAccount,
VatRelationKey=#VatRelationKey,
UserId=#UserId,
DateStamp=#DateStamp,
VatReconcileKey = #VatReconcileKey
WHERE TransDate= #TransDate and NominalCode = #NominalCode and Period = #Period and FiscalYear = #FiscalYear and Reference = #Reference and vatcode = #vatcode and Memo=#Memo and #Transtype=Transtype and LocationKey = #LocationKe </frame>
</executionStack>
<inputbuf>
Proc [Database Id = 7 Object Id = 1679370539] </inputbuf>
</process>
<process id="process2c457d96108" taskpriority="0" logused="0" waitresource="PAGE: 7:1:2425 " waittime="1686" ownerId="1782668" transactionname="UPDATE" lasttranstarted="2022-12-13T09:46:09.003" XDES="0x2c45d69d900" lockMode="U" schedulerid="2" kpid="3336" status="suspended" spid="176" sbid="0" ecid="1" priority="0" trancount="0" lastbatchstarted="2022-12-13T09:46:08.817" lastbatchcompleted="2022-12-13T09:46:08.813" lastattention="1900-01-01T00:00:00.813" clientapp="Liquidity" hostname="DESKTOP-O2" hostpid="8728" isolationlevel="read committed (2)" xactid="1782668" currentdb="7" lockTimeout="4294967295" clientoption1="536870944" clientoption2="128024">
<executionStack>
<frame procname="MDT_DB.dbo.spWriteNominalTrans" line="64" stmtstart="4210" stmtend="4886" sqlhandle="0x030007002b291964daa21d01caac000001000000000000000000000000000000000000000000000000000000">
UPDATE TblNominalTrans
SET
Debit = Debit - Credit, credit = 0
WHERE TransDate= #TransDate and NominalCode = #NominalCode and Period = #Period and FiscalYear = #FiscalYear and Reference = #Reference and vatcode = #vatcode and Memo=#Memo and #Transtype=Transtype and LocationKey = #LocationKey and debit >= credit and credit > </frame>
</executionStack>
<inputbuf>
Proc [Database Id = 7 Object Id = 1679370539] </inputbuf>
</process>
<process id="process2c457d97468" taskpriority="0" logused="0" waitresource="PAGE: 7:1:23277 " waittime="1686" ownerId="1782665" transactionname="UPDATE" lasttranstarted="2022-12-13T09:46:09" XDES="0x2c3ab371cb0" lockMode="U" schedulerid="2" kpid="5808" status="suspended" spid="66" sbid="0" ecid="1" priority="0" trancount="0" lastbatchstarted="2022-12-13T09:46:08.930" lastbatchcompleted="2022-12-13T09:46:08.923" lastattention="1900-01-01T00:00:00.923" clientapp="Liquidity" hostname="TERM-SERVER" hostpid="5228" isolationlevel="read committed (2)" xactid="1782665" currentdb="7" lockTimeout="4294967295" clientoption1="536870944" clientoption2="128024">
<executionStack>
<frame procname="MDT_DB.dbo.spWriteNominalTrans" line="43" stmtstart="2092" stmtend="3384" sqlhandle="0x030007002b291964daa21d01caac000001000000000000000000000000000000000000000000000000000000">
UPDATE TblNominalTrans
SET
Memo=#Memo,
Debit = Debit + #Debit ,
Credit = Credit +#Credit,
GoodsValue = GoodsValue + #GoodsValue,
VatValueIN = VatValueIN + #VatValueIN,
VatValueOut = VatValueOut + #VatValueOut,
VatOut = #Vatout,
VatNLAccount=#VatNLAccount,
VatRelationKey=#VatRelationKey,
UserId=#UserId,
DateStamp=#DateStamp,
VatReconcileKey = #VatReconcileKey
WHERE TransDate= #TransDate and NominalCode = #NominalCode and Period = #Period and FiscalYear = #FiscalYear and Reference = #Reference and vatcode = #vatcode and Memo=#Memo and #Transtype=Transtype and LocationKey = #LocationKe </frame>
</executionStack>
<inputbuf>
Proc [Database Id = 7 Object Id = 1679370539] </inputbuf>
</process>
<process id="process2c45d6984e8" taskpriority="0" logused="10000" waittime="1624" schedulerid="4" kpid="5024" status="suspended" spid="176" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2022-12-13T09:46:08.817" lastbatchcompleted="2022-12-13T09:46:08.813" lastattention="1900-01-01T00:00:00.813" clientapp="Liquidity" hostname="DESKTOP-O2" hostpid="8728" loginname="LOCAL\debbie.weavers" isolationlevel="read committed (2)" xactid="1782668" currentdb="7" lockTimeout="4294967295" clientoption1="536870944" clientoption2="128024">
<executionStack>
<frame procname="MDT_DB.dbo.spWriteNominalTrans" line="64" stmtstart="4210" stmtend="4886" sqlhandle="0x030007002b291964daa21d01caac000001000000000000000000000000000000000000000000000000000000">
UPDATE TblNominalTrans
SET
Debit = Debit - Credit, credit = 0
WHERE TransDate= #TransDate and NominalCode = #NominalCode and Period = #Period and FiscalYear = #FiscalYear and Reference = #Reference and vatcode = #vatcode and Memo=#Memo and #Transtype=Transtype and LocationKey = #LocationKey and debit >= credit and credit > </frame>
</executionStack>
<inputbuf>
Proc [Database Id = 7 Object Id = 1679370539] </inputbuf>
</process>
</process-list>
<resource-list>
<pagelock fileid="1" pageid="23277" dbid="7" subresource="FULL" objectname="MDT_DB.dbo.tblNominalTrans" id="lock2c3c82bb300" mode="U" associatedObjectId="287811838279680">
<owner-list>
<owner id="process2c457d97468" mode="U" requestType="wait" />
</owner-list>
<waiter-list>
<waiter id="process2c457dd0ca8" mode="U" requestType="wait" />
</waiter-list>
</pagelock>
<pagelock fileid="1" pageid="2425" dbid="7" subresource="FULL" objectname="MDT_DB.dbo.tblNominalTrans" id="lock2c431d37300" mode="U" associatedObjectId="287811838279680">
<owner-list>
<owner id="process2c457dd0ca8" mode="U" />
</owner-list>
<waiter-list>
<waiter id="process2c457d96108" mode="U" requestType="wait" />
</waiter-list>
</pagelock>
<pagelock fileid="1" pageid="23277" dbid="7" subresource="FULL" objectname="MDT_DB.dbo.tblNominalTrans" id="lock2c3c82bb300" mode="U" associatedObjectId="287811838279680">
<owner-list>
<owner id="process2c45d6984e8" mode="U" />
</owner-list>
<waiter-list>
<waiter id="process2c457d97468" mode="U" requestType="wait" />
</waiter-list>
</pagelock>
<exchangeEvent id="Pipe2c3488ae780" WaitType="e_waitPipeGetRow" nodeId="4">
<owner-list>
<owner id="process2c457d96108" />
</owner-list>
<waiter-list>
<waiter id="process2c45d6984e8" />
</waiter-list>
</exchangeEvent>
</resource-list>
</deadlock>

Related

SELECT TABLOCKX then MERGE vs MERGE with TABLOCKX

I would have thought that the following query's would have the same result:
MERGE [myTable] AS T WITH (TABLOCKX)
...
SELECT TOP 1 1 FROM [myTable] WITH (TABLOCKX);
MERGE [myTable] AS T
...
However, when running my MERGE-statement parellel from multiple processes, the first one will result in deadlocks while the second one runs just fine. Am I missing something here?
I should note that it runs within a transaction.
Edit
I have created a sample DDL and testdata to recreate the issue:
DROP TABLE IF EXISTS [dbo].[myReference]
GO
DROP TABLE IF EXISTS [dbo].[myTable]
GO
CREATE TABLE [dbo].[myTable](
[Primary key] [int] IDENTITY(1,1) NOT NULL,
[Dataset key] [int] NOT NULL,
[Key] [int] NOT NULL,
CONSTRAINT [PK myTable] PRIMARY KEY CLUSTERED ([Primary key] ASC)
)
GO
CREATE TABLE [dbo].[myReference](
[Foreign key] INT NOT NULL,
CONSTRAINT [FK myReference myTable] FOREIGN KEY ([Foreign key]) REFERENCES [dbo].[myTable] ([Primary key]) ON DELETE CASCADE
)
GO
DROP PROCEDURE IF EXISTS [dbo].[usp]
GO
CREATE PROCEDURE [dbo].[usp] #DatasetKey INT AS
WITH Val AS (
SELECT *
FROM ( VALUES
(1),(2),(3),(4),(5),(6),(7),(8),(9),(10)
) s ([Value])
)
SELECT t1.[Value]
+ (t2.[Value] - 1) * 10
+ (t3.[Value] - 1) * 100
+ (t4.[Value] - 1) * 1000
+ (t5.[Value] - 1) * 10000
+ (t6.[Value] - 1) * 100000
AS [Key]
INTO #t
FROM Val t1
cross apply Val t2
cross apply Val t3
cross apply Val t4
cross apply Val t5
cross apply Val t6
;
--SELECT TOP 1 1 FROM [dbo].[myTable] WITH (TABLOCKX);
MERGE [dbo].[myTable] WITH (TABLOCKX) AS T
USING #t AS S
ON T.[Dataset key] = #DatasetKey
AND T.[Key] = S.[Key]
WHEN NOT MATCHED BY TARGET THEN
INSERT ([Dataset key], [Key]) VALUES (#DatasetKey, S.[Key])
WHEN NOT MATCHED BY SOURCE AND T.[Dataset key] = #DatasetKey THEN
DELETE
;
GO
EXEC [dbo].[usp] 1
GO
EXEC [dbo].[usp] 2
GO
INSERT INTO [dbo].[myReference]
SELECT [Primary key]
FROM [dbo].[myTable]
GO
When running the following two transactions simultaneously, the result will be a deadlock every single time.
TRAN 1
BEGIN TRY;
BEGIN TRANSACTION;
exec [dbo].[usp] 1;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
DECLARE #XactState INT = XACT_STATE();
IF #XactState <> 0
ROLLBACK TRANSACTION;
THROW;
END CATCH;
TRAN 2
BEGIN TRY;
BEGIN TRANSACTION;
exec [dbo].[usp] 2;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
DECLARE #XactState INT = XACT_STATE();
IF #XactState <> 0
ROLLBACK TRANSACTION;
THROW;
END CATCH;
Deadlock report
<deadlock>
<victim-list>
<victimProcess id="process273cd96d468" />
</victim-list>
<process-list>
<process id="process273cd96d468" taskpriority="0" logused="0" waitresource="OBJECT: 42:1282103608:0 " waittime="2356" ownerId="2255393274" transactionname="user_transaction" lasttranstarted="2021-11-26T16:11:20.080" XDES="0x296a4fb64d0" lockMode="X" schedulerid="4" kpid="19904" status="suspended" spid="67" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2021-11-26T16:11:20.080" lastbatchcompleted="2021-11-26T16:11:20.077" lastattention="1900-01-01T00:00:00.077" clientapp="Microsoft SQL Server Management Studio - Query" hostname="SV00415" hostpid="21276" loginname="VIECURI\mhoogeveen" isolationlevel="read committed (2)" xactid="2255393274" currentdb="42" currentdbname="Test20211126KanDaarnaWeg" lockTimeout="4294967295" clientoption1="671090784" clientoption2="390200">
<executionStack>
<frame procname="Test20211126KanDaarnaWeg.dbo.usp" line="29" stmtstart="1050" stmtend="1626" sqlhandle="0x03002a00aaa1534ecba70a01ecad000001000000000000000000000000000000000000000000000000000000">
MERGE [dbo].[myTable] WITH (TABLOCKX) AS T
USING #t AS S
ON T.[Dataset key] = #DatasetKey
AND T.[Key] = S.[Key]
WHEN NOT MATCHED BY TARGET THEN
INSERT ([Dataset key], [Key]) VALUES (#DatasetKey, S.[Key])
WHEN NOT MATCHED BY SOURCE AND T.[Dataset key] = #DatasetKey THEN
DELETE </frame>
<frame procname="adhoc" line="4" stmtstart="72" stmtend="106" sqlhandle="0x020000009f228824b51645ad1d06b456eabe7b2b24f2e8fe0000000000000000000000000000000000000000">
unknown </frame>
</executionStack>
<inputbuf>
BEGIN TRY;
BEGIN TRANSACTION;
exec [dbo].[usp] 2;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
DECLARE #XactState INT = XACT_STATE();
IF #XactState <> 0
ROLLBACK TRANSACTION;
THROW;
END CATCH; </inputbuf>
</process>
<process id="process273c6c4b088" taskpriority="0" logused="0" waitresource="OBJECT: 42:1250103494:0 " waittime="2994" ownerId="2255393256" transactionname="user_transaction" lasttranstarted="2021-11-26T16:11:19.633" XDES="0x27385bfd080" lockMode="X" schedulerid="1" kpid="8752" status="suspended" spid="53" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2021-11-26T16:11:19.633" lastbatchcompleted="2021-11-26T16:11:19.630" lastattention="1900-01-01T00:00:00.630" clientapp="Microsoft SQL Server Management Studio - Query" hostname="SV00415" hostpid="21276" loginname="VIECURI\mhoogeveen" isolationlevel="read committed (2)" xactid="2255393256" currentdb="42" currentdbname="Test20211126KanDaarnaWeg" lockTimeout="4294967295" clientoption1="671090784" clientoption2="390200">
<executionStack>
<frame procname="Test20211126KanDaarnaWeg.dbo.usp" line="29" stmtstart="1050" stmtend="1626" sqlhandle="0x03002a00aaa1534ecba70a01ecad000001000000000000000000000000000000000000000000000000000000">
MERGE [dbo].[myTable] WITH (TABLOCKX) AS T
USING #t AS S
ON T.[Dataset key] = #DatasetKey
AND T.[Key] = S.[Key]
WHEN NOT MATCHED BY TARGET THEN
INSERT ([Dataset key], [Key]) VALUES (#DatasetKey, S.[Key])
WHEN NOT MATCHED BY SOURCE AND T.[Dataset key] = #DatasetKey THEN
DELETE </frame>
<frame procname="adhoc" line="4" stmtstart="72" stmtend="106" sqlhandle="0x020000002457720d4bb1099d3682fee9760829cab4bbc2be0000000000000000000000000000000000000000">
unknown </frame>
</executionStack>
<inputbuf>
BEGIN TRY;
BEGIN TRANSACTION;
exec [dbo].[usp] 1;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
DECLARE #XactState INT = XACT_STATE();
IF #XactState <> 0
ROLLBACK TRANSACTION;
THROW;
END CATCH; </inputbuf>
</process>
</process-list>
<resource-list>
<objectlock lockPartition="0" objid="1282103608" subresource="FULL" dbid="42" objectname="Test20211126KanDaarnaWeg.dbo.myReference" id="lock29a7a3f4780" mode="IX" associatedObjectId="1282103608">
<owner-list>
<owner id="process273c6c4b088" mode="IX" />
</owner-list>
<waiter-list>
<waiter id="process273cd96d468" mode="X" requestType="convert" />
</waiter-list>
</objectlock>
<objectlock lockPartition="0" objid="1250103494" subresource="FULL" dbid="42" objectname="Test20211126KanDaarnaWeg.dbo.myTable" id="lock28a11c39800" mode="X" associatedObjectId="1250103494">
<owner-list>
<owner id="process273cd96d468" mode="X" />
<owner id="process273cd96d468" mode="X" />
<owner id="process273cd96d468" mode="X" />
<owner id="process273cd96d468" mode="X" />
<owner id="process273cd96d468" mode="X" />
<owner id="process273cd96d468" mode="X" />
</owner-list>
<waiter-list>
<waiter id="process273c6c4b088" mode="X" requestType="wait" />
</waiter-list>
</objectlock>
</resource-list>
</deadlock>
It looks like the difference is that MERGE with TABLOCKX initially does take an IX lock, whereas SELECT ... WITH TABLOCKX does not.
I verified this profiling the lock:acquired event on SQL Server 2019 CU 14, and with smaller tables was able to repro the deadlock. It's an extremely short window where this can happen, and the larger tables didn't allow enough concurrency on my system.
This creates a small window where two sessions could acquire IX table locks and neither will be able to escalate to an X lock.
If you want to serialize a block of code, sp_getapplock is the simplest way.

SQL Server: getting deadlock victim error on a stored procedure without transaction

I have a table called Storage with this design:
CREATE TABLE [dbo].[Storage]
(
[Id] [INT] IDENTITY(1,1) NOT NULL,
[GameId] [INT] NOT NULL,
[UserId] [INT] NOT NULL,
[Status] [TINYINT] NOT NULL,
[CreatedAt] [DATETIME] NOT NULL,
[UpdatedAt] [DATETIME] NULL,
[Data] [NVARCHAR](MAX) NOT NULL,
CONSTRAINT [PK_Storage]
PRIMARY KEY CLUSTERED ([Id] ASC)
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
and I get a "deadlock victim error" in stored procedure MMO_Storage_Set:
CREATE PROCEDURE [dbo].[MMO_Storage_Set]
#Data NVARCHAR(MAX),
#GameKey INT,
#UserId INT,
#ErrorCode INT OUT
AS
BEGIN
SET NOCOUNT ON;
-- user must have 1 active session at minimum
IF NOT EXISTS(SELECT Id FROM dbo.[Session] WITH(NOLOCK)
WHERE UserId = #UserId AND ([Status] = 1))
BEGIN
SET #ErrorCode = -3
RETURN
END
DECLARE #GameId INT = NULL
DECLARE #GameStatus TINYINT = NULL
SELECT #GameStatus = [Status], #GameId = Id
FROM dbo.[Game] WITH(NOLOCK)
WHERE ([Key] = #GameKey)
-- Game not found
IF #GameStatus IS NULL
BEGIN
SET #ErrorCode = -5
RETURN
END
-- Game is not valid
IF #GameStatus != 1
BEGIN
SET #ErrorCode = -6
RETURN
END
SET #ErrorCode = 0
IF (NOT EXISTS (SELECT ID FROM [Storage] WITH (NOLOCK)
WHERE [UserID] = #UserId AND [GameId] = #GameId))
BEGIN
INSERT INTO dbo.Storage (GameId, UserId,[Status], CreatedAt, UpdatedAt, Data)
VALUES (#GameId, #UserId, 1, GETDATE(), NULL, #Data)
END
ELSE
BEGIN
UPDATE dbo.Storage
SET Data = #Data, UpdatedAt = GETDATE()
WHERE (GameID = #GameId) AND (UserId = #UserId) AND ([Status] = 1)
END
SET #ErrorCode = 1
END
My error is:
Transaction (Process ID 55) was deadlocked on lock | communication buffer resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
I use SQL Server Profiler to trace deadlock, as you can see in deadlock report generated by SQL Server Profiler:
<deadlock>
<victim-list>
<victimProcess id="process10754aca8" />
</victim-list>
<process-list>
<process id="process10754aca8" taskpriority="0" logused="0" waitresource="PAGE: 9:1:1167 " waittime="1302" ownerId="501754107" transactionname="UPDATE" lasttranstarted="2018-02-18T02:21:16.990" XDES="0x270741590" lockMode="U" schedulerid="3" kpid="6700" status="suspended" spid="63" sbid="0" ecid="1" priority="0" trancount="0" lastbatchstarted="2018-02-18T02:21:16.810" lastbatchcompleted="2018-02-18T02:21:16.817" lastattention="1900-01-01T00:00:00.817" clientapp=".Net SqlClient Data Provider" hostname="APP-SOCCER-VAS" hostpid="11332" isolationlevel="read committed (2)" xactid="501754107" currentdb="9" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056">
<executionStack>
<frame procname="SoccerChampion.dbo.MMO_Storage_Set" line="60" stmtstart="2870" stmtend="3132" sqlhandle="0x030009008b0bd114d1f2a4004da8000001000000000000000000000000000000000000000000000000000000">
Update dbo.Storage
Set Data = #Data, UpdatedAt = GETDATE()
WHERE (GameID = #GameId) AND (UserId = #UserId) AND ([Status] = 1 </frame>
</executionStack>
<inputbuf>
Proc [Database Id = 9 Object Id = 349244299] </inputbuf>
</process>
<process id="process136d8d088" taskpriority="0" logused="5384" waitresource="PAGE: 9:1:1167 " waittime="1056" ownerId="501753989" transactionname="UPDATE" lasttranstarted="2018-02-18T02:21:16.773" XDES="0x1767f9ce0" lockMode="U" schedulerid="1" kpid="9920" status="suspended" spid="52" sbid="0" ecid="3" priority="0" trancount="0" lastbatchstarted="2018-02-18T02:21:16.590" lastbatchcompleted="2018-02-18T02:21:16.597" lastattention="1900-01-01T00:00:00.597" clientapp=".Net SqlClient Data Provider" hostname="APP-SOCCER-VAS" hostpid="11332" isolationlevel="read committed (2)" xactid="501753989" currentdb="9" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056">
<executionStack>
<frame procname="SoccerChampion.dbo.MMO_Storage_Set" line="60" stmtstart="2870" stmtend="3132" sqlhandle="0x030009008b0bd114d1f2a4004da8000001000000000000000000000000000000000000000000000000000000">
Update dbo.Storage
Set Data = #Data, UpdatedAt = GETDATE()
WHERE (GameID = #GameId) AND (UserId = #UserId) AND ([Status] = 1 </frame>
</executionStack>
<inputbuf>
Proc [Database Id = 9 Object Id = 349244299] </inputbuf>
</process>
<process id="process1001b7848" taskpriority="0" logused="10000" waittime="654" schedulerid="4" kpid="10028" status="suspended" spid="52" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2018-02-18T02:21:16.590" lastbatchcompleted="2018-02-18T02:21:16.597" lastattention="1900-01-01T00:00:00.597" clientapp=".Net SqlClient Data Provider" hostname="APP-SOCCER-VAS" hostpid="11332" loginname="SC_Core" isolationlevel="read committed (2)" xactid="501753989" currentdb="9" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056">
<executionStack>
<frame procname="SoccerChampion.dbo.MMO_Storage_Set" line="60" stmtstart="2870" stmtend="3132" sqlhandle="0x030009008b0bd114d1f2a4004da8000001000000000000000000000000000000000000000000000000000000">
Update dbo.Storage
Set Data = #Data, UpdatedAt = GETDATE()
WHERE (GameID = #GameId) AND (UserId = #UserId) AND ([Status] = 1 </frame>
</executionStack>
<inputbuf>
Proc [Database Id = 9 Object Id = 349244299] </inputbuf>
</process>
</process-list>
<resource-list>
<pagelock fileid="1" pageid="1167" dbid="9" subresource="FULL" objectname="SoccerChampion.dbo.Storage" id="lock205c8bb00" mode="U" associatedObjectId="72057594084524032">
<owner-list>
<owner id="process1001b7848" mode="U" />
</owner-list>
<waiter-list>
<waiter id="process10754aca8" mode="U" requestType="wait" />
</waiter-list>
</pagelock>
<pagelock fileid="1" pageid="1167" dbid="9" subresource="FULL" objectname="SoccerChampion.dbo.Storage" id="lock205c8bb00" mode="U" associatedObjectId="72057594084524032">
<owner-list>
<owner id="process10754aca8" mode="U" requestType="wait" />
</owner-list>
<waiter-list>
<waiter id="process136d8d088" mode="U" requestType="wait" />
</waiter-list>
</pagelock>
<exchangeEvent id="Pipeb1ff6380" WaitType="e_waitPipeGetRow" nodeId="2">
<owner-list>
<owner id="process136d8d088" />
</owner-list>
<waiter-list>
<waiter id="process1001b7848" />
</waiter-list>
</exchangeEvent>
</resource-list>
</deadlock>
I'm wondering why I get deadlock in an update query without transaction and a read query with nolock!!
Can someone tell me why I'm getting this error and how can I fix it?
Not an answer to locks but I see a problem here
IF (NOT EXISTS (SELECT ID FROM [Storage] WITH (NOLOCK)
WHERE [UserID] = #UserId AND [GameId] = #GameId))
But if it does exists you update
AND ([Status] = 1)
You have not checked for status = 1 so that record may not be there
Search on upsert which uses merge. You can eliminate that IF (NOT EXISTS. This is not a clean up the code type suggestion. It may fix your lock problem. For sure it cannot hurt.
Maybe take an explicit rowlock on the update but table hint is should be a last ditch effort.

Incorrect syntax near the keyword 'WHEN'

Trying to create a stored procedure to insert or update records depending on a field.
CREATE PROCEDURE [dbo].[usp_InsertorUpdateDB]
#dp_id char(32),
#dv_id char(32),
#em_number char(12),
#email varchar(50),
#emergency_relation char(32),
#option1 char(16),
#status char(20),
#em_id char(35),
#em_title varchar(64),
#date_hired datetime
AS
MERGE [dbo].[em] AS [Target]
USING (SELECT #dp_id, #dv_id , #em_number, #email, #emergency_relation, #option1, #status, #em_id, #em_title, #date_hired)
AS [Source] ([dp_id], [dv_id], [em_number], [email], [emergency_relation], [option1], [status], [em_id], [em_title], [date_hired])
ON [Target].[em_id] = [Source].[em_id]
WHEN MATCHED THEN
UPDATE SET [dp_id] = [Source].[dp_id],
[dv_id] = [Source].[dv_id],
[em_number] = [Source].[em_number],
[email] = [Source].[email],
[emergency_relation] = [Source].[email],
[option1] = [Source].[option1],
[status] = [Source].[status],
[em_id] = [Source].[em_id],
[em_title] = [Source].[em_title],
[date_hired] = [Source].[date_hired],
WHEN NOT MATCHED THEN
INSERT ([dp_id], [dv_id], [em_number], [email], [emergency_relation], [option1], [status], [em_id], [em_title],[date_hired])
VALUES ([Source].[dp_id], [Source].[dv_id], [Source].[em_number], [Source].[email], [Source].[emergency_relation], [Source].[option1], [Source].[status], [Source].[em_id], [Source].[em_title], [Source].[date_hired]);
GO
I am getting error on the line WHEN NOT MATCHED THEN
Incorrect syntax near the keyword 'WHEN'.
I am new to stored procedure and tried to figure it out but couldn't.
You don't want this comma -
UPDATE SET [dp_id] = [Source].[dp_id],
[dv_id] = [Source].[dv_id],
[em_number] = [Source].[em_number],
[email] = [Source].[email],
[emergency_relation] = [Source].[email],
[option1] = [Source].[option1],
[status] = [Source].[status],
[em_id] = [Source].[em_id],
[em_title] = [Source].[em_title],
[date_hired] = [Source].[date_hired]**,**
after [date_hired],

Stored procedure to update or insert

I am new to stored procedure and managed to google, to create a stored procedure for inserting or updating the database. The set of records are selected from the Oracle Database and depending on the em_id it hase to be iserted or updated in to the Sql Database table using BizTalk
Trying to create a stored procedure to insert or update records depending on a field.
CREATE PROCEDURE [dbo].[usp_InsertorUpdateDB]
#dp_id char(32),
#dv_id char(32),
#em_number char(12),
#email varchar(50),
#emergency_relation char(32),
#option1 char(16),
#status char(20),
#em_id char(35),
#em_title varchar(64),
#date_hired datetime
AS
MERGE [dbo].[em] AS [Target]
USING (SELECT #dp_id, #dv_id , #em_number, #email, #emergency_relation, #option1, #status, #em_id, #em_title, #date_hired)
AS [Source] ([dp_id], [dv_id], [em_number], [email], [emergency_relation], [option1], [status], [em_id], [em_title], [date_hired])
ON [Target].[em_id] = [Source].[em_id]
WHEN MATCHED THEN
UPDATE SET [dp_id] = [Source].[dp_id],
[dv_id] = [Source].[dv_id],
[em_number] = [Source].[em_number],
[email] = [Source].[email],
[emergency_relation] = [Source].[emergency_relation],
[option1] = [Source].[option1],
[status] = [Source].[status],
[em_id] = [Source].[em_id],
[em_title] = [Source].[em_title],
[date_hired] = [Source].[date_hired]
WHEN NOT MATCHED THEN
INSERT ([dp_id], [dv_id], [em_number], [email], [emergency_relation], [option1], [status], [em_id], [em_title],[date_hired])
VALUES ([Source].[dp_id], [Source].[dv_id], [Source].[em_number], [Source].[email], [Source].[emergency_relation], [Source].[option1], [Source].[status], [Source].[em_id], [Source].[em_title], [Source].[date_hired]);
GO
I asked a question two days before because it was showing
Incorrect syntax near the keyword 'WHEN'.
There was a comment showing the code is prone deadlock. Since I am new to Stored Procedures I dont know how to create a stored procedure for insert or update without deadlocks.I am really stuck.
Merge is the reason for deadlock, you can simply try that
CREATE PROCEDURE [dbo].[usp_InsertorUpdateDB]
#dp_id char(32),
#dv_id char(32),
#em_number char(12),
#email varchar(50),
#emergency_relation char(32),
#option1 char(16),
#status char(20),
#em_id char(35),
#em_title varchar(64),
#date_hired datetime
AS
IF ( (SELECT COUNT(em_id) FROM [dbo].[em] WHERE em_id = #em_id) > 0)
UPDATE [dbo].[em]
SET
[dp_id] = #dp_id,
[dv_id] = #dv_id,
[em_number] = #em_number,
[email] = #email,
[emergency_relation] = #emergency_relation,
[option1] = #option1,
[status] = #status,
[em_id] = #em_id,
[em_title] = #em_title,
[date_hired] = #date_hired
WHERE em_id = #em_id
ELSE
INSERT INTO [dbo].[em] (dp_id, dv_id, em_number, email, emergency_relation, option1, [status], em_id, em_title,date_hired)
VALUES (#dp_id, #dv_id, #em_number, #email, #emergency_relation, #option1, #status, #em_id, #em_title, #date_hired);
GO

TSQL Query Inserting data from xPath

I am using SQL Server 2012 and trying trying to insert data into multiple tables from an XML string containing the data. The issue and confusion stems from the XML containing multiple nodes so its not just a single record at a time.
Due to this, I am using the output method to insert the data along with the Identity so I know the result of each of the records it inserts.
My problem is due to the structure of the XML string, it is not inserting all of the data it needs to.
Here is the block of code I am working with along with a SQL Fiddle:
Fiddle: http://sqlfiddle.com/#!6/d41d8/24236
DECLARE #xml xml = '<root>
<trainingEventID>572</trainingEventID>
<segment>
<segmentDate>03/03/2015</segmentDate>
<hours>4</hours>
<details>
<locale>653</locale>
<teammates>3</teammates>
<leaders>4</leaders>
</details>
<details>
<locale>655</locale>
<teammates>44</teammates>
<leaders>55</leaders>
</details>
<details>
<locale>657</locale>
<teammates>55</teammates>
<leaders>66</leaders>
</details>
<trainers>
<trainer>
<empID>User12341</empID>
</trainer>
</trainers>
</segment>
<segment>
<segmentDate>03/04/2015</segmentDate>
<hours>4</hours>
<details>
<locale>653</locale>
<teammates>3</teammates>
<leaders>4</leaders>
</details>
<details>
<locale>655</locale>
<teammates>44</teammates>
<leaders>55</leaders>
</details>
<details>
<locale>657</locale>
<teammates>55</teammates>
<leaders>66</leaders>
</details>
<trainers>
<trainer>
<empID>User1234</empID>
</trainer>
</trainers>
</segment>
<segment>
<segmentDate>03/13/2015</segmentDate>
<hours>4</hours>
<details>
<locale>653</locale>
<teammates>3</teammates>
<leaders>4</leaders>
</details>
<details>
<locale>655</locale>
<teammates>44</teammates>
<leaders>55</leaders>
</details>
<details>
<locale>657</locale>
<teammates>55</teammates>
<leaders>66</leaders>
</details>
<trainers>
<trainer>
<empID>User1234</empID>
</trainer>
</trainers>
</segment>
</root>'
-- Declare our temp tables
DECLARE #tmpSeg TABLE (teSegmentID INT, trainingEventID INT, segmentDate DATE, nonProdHrs int);
DECLARE #tmpEvents TABLE (teSegmentID INT IDENTITY(1,1), trainingEventID INT, segmentDate DATE, nonProdHrs INT);
-- First, Insert the main segments
INSERT INTO #tmpEvents(trainingEventID, segmentDate, nonProdHrs)
OUTPUT Inserted.teSegmentID, Inserted.trainingEventID, Inserted.segmentDate, Inserted.nonProdHrs INTO #tmpSeg
SELECT ParamValues.x1.value('../trainingEventID[1]', 'INT'),
ParamValues.x1.value('(segmentDate/text())[1]', 'DATE'),
ParamValues.x1.value('(hours/text())[1]', 'INT')
FROM #xml.nodes('/root/segment') AS ParamValues(x1);
SELECT * FROM #tmpSeg
-- Now, we join on our temp table and insert the Segment Details
SELECT s.teSegmentID,
ParamValues.x1.value('(details/locale/text())[1]', 'INT') AS localeID,
ParamValues.x1.value('(details/teammates/text())[1]', 'INT') AS teammates,
ParamValues.x1.value('(details/leaders/text())[1]', 'INT') AS leaders,
ParamValues.x1.value('(../trainingEventID/text())[1]', 'INT') AS eventID,
ParamValues.x1.value('(segmentDate/text())[1]', 'DATE') AS date,
ParamValues.x1.value('(hours/text())[1]', 'INT') AS hours
FROM #tmpSeg AS s
INNER JOIN #xml.nodes('/root/segment') AS ParamValues(x1)
ON s.trainingEventID = ParamValues.x1.value('(../trainingEventID/text())[1]', 'INT')
AND s.segmentDate = ParamValues.x1.value('(segmentDate/text())[1]', 'DATE')
AND s.nonProdHrs = ParamValues.x1.value('(hours/text())[1]', 'INT')
As you can see from the XML structure, it is broken down into parts. There is a segment and then within the segment there can be multiple Details Nodes.
The first step in the query is to create all of the segments which appears to be working fine. Each segment gets created and the Identity is stored in a temp table from the output.
Next, I need to create records for each details node using the Identity of its parent segment. I do this by joining the temp table from the output some some of its data to get the details needed.
The issue with this is due to multiple details nodes, it is only accessing the first one and storing its data.
The output in the last statement using this example should contain 9 records. There are 3 details nodes for each segment and there are 3 segments total.
Not sure how to accomplish this but its driving me crazy.
Thanks for any help.
You need another level for details, first off, but there's also the question of trainers..I took a bit of liberty with the solution here, so feel free to modify as needed.
DECLARE #xml xml = '<root>
<trainingEventID>572</trainingEventID>
<segment>
<segmentDate>03/03/2015</segmentDate>
<hours>4</hours>
<details>
<locale>653</locale>
<teammates>3</teammates>
<leaders>4</leaders>
</details>
<details>
<locale>655</locale>
<teammates>44</teammates>
<leaders>55</leaders>
</details>
<details>
<locale>657</locale>
<teammates>55</teammates>
<leaders>66</leaders>
</details>
<trainers>
<trainer>
<empID>User12341</empID>
</trainer>
</trainers>
</segment>
<segment>
<segmentDate>03/04/2015</segmentDate>
<hours>4</hours>
<details>
<locale>653</locale>
<teammates>3</teammates>
<leaders>4</leaders>
</details>
<details>
<locale>655</locale>
<teammates>44</teammates>
<leaders>55</leaders>
</details>
<details>
<locale>657</locale>
<teammates>55</teammates>
<leaders>66</leaders>
</details>
<trainers>
<trainer>
<empID>User1234</empID>
</trainer>
</trainers>
</segment>
<segment>
<segmentDate>03/13/2015</segmentDate>
<hours>4</hours>
<details>
<locale>653</locale>
<teammates>3</teammates>
<leaders>4</leaders>
</details>
<details>
<locale>655</locale>
<teammates>44</teammates>
<leaders>55</leaders>
</details>
<details>
<locale>657</locale>
<teammates>55</teammates>
<leaders>66</leaders>
</details>
<trainers>
<trainer>
<empID>User1234</empID>
</trainer>
</trainers>
</segment>
</root>'
-- Declare temp tables
DECLARE #tmpSeg TABLE (teSegmentID INT IDENTITY(1,1), trainingEventID INT, segmentDate DATE, nonProdHrs INT, trainer varchar(30));
DECLARE #tmpLocales TABLE (teSegmentID INT, trainingEventID INT/*, segmentDate DATE, nonProdHrs int*/, locale int, teammates int, leaders int);
DECLARE #tmpTrainers TABLE (teSegmentID INT, trainingEventID INT, empID VARCHAR(30));
-- Get Segment info
INSERT INTO #tmpSeg(trainingEventID, segmentDate, nonProdHrs, trainer)
SELECT
ParamValues.x1.value('../trainingEventID[1]', 'INT')
, ParamValues.x1.value('segmentDate[1]', 'DATE')
, ParamValues.x1.value('hours[1]', 'INT')
, ParamValues.x1.value('trainers[1]/trainer[1]/empID[1]', 'VARCHAR(30)')
FROM #xml.nodes('/root/segment') AS ParamValues(x1);
SELECT * FROM #tmpSeg
-- Get Segment-dependent trainer info
INSERT INTO #tmpTrainers(teSegmentID, trainingEventID, empID)
SELECT
S.teSegmentID
, D.trainingEventID
, D.empID
FROM (
SELECT
ParamValues.x1.value('empID[1]', 'VARCHAR(30)') AS empID
, ParamValues.x1.value('../../../trainingEventID[1]', 'INT') AS trainingEventID
, ParamValues.x1.value('../../segmentDate[1]', 'DATE') AS segmentDate
, ParamValues.x1.value('../../hours[1]', 'INT') AS nonProdHours
FROM #xml.nodes('/root/segment/trainers/trainer') AS ParamValues(x1)
) D
INNER JOIN #tmpSeg S ON D.trainingEventID = S.trainingEventID
AND D.segmentDate = S.segmentDate
AND D.nonProdHours = S.nonProdHrs
SELECT * FROM #tmpTrainers
-- Get segment-dependent locale info
INSERT INTO #tmpLocales
SELECT
S.teSegmentID
, D.trainingEventID
, D.locale
, D.teammates
, D.leaders
FROM (
SELECT
ParamValues.x1.value('locale[1]', 'INT') AS locale
, ParamValues.x1.value('teammates[1]', 'INT') AS teammates
, ParamValues.x1.value('leaders[1]', 'INT') AS leaders
, ParamValues.x1.value('../../trainingEventID[1]', 'INT') AS trainingEventID
, ParamValues.x1.value('../segmentDate[1]', 'DATE') AS segmentDate
, ParamValues.x1.value('../hours[1]', 'INT') AS nonProdHours
FROM #xml.nodes('/root/segment/details') AS ParamValues(x1)
) D
INNER JOIN #tmpSeg S ON D.trainingEventID = S.trainingEventID
AND D.segmentDate = S.segmentDate
AND D.nonProdHours = S.nonProdHrs
SELECT *
FROM #tmpLocales
Your last SELECT is still iterating through the <segment> nodes, of which there are only 3. You need to shed it to the <details> level by using another CROSS APPLY:
-- Now, we join on our temp table and insert the Segment Details
SELECT s.teSegmentID,
D.Detail.value('locale[1]', 'INT') AS localeID,
D.Detail.value('teammates[1]', 'INT') AS teammates,
D.Detail.value('leaders[1]', 'INT') AS leaders,
ParamValues.x1.value('(../trainingEventID/text())[1]', 'INT') AS eventID,
ParamValues.x1.value('(segmentDate/text())[1]', 'DATE') AS date,
ParamValues.x1.value('(hours/text())[1]', 'INT') AS hours
FROM #tmpSeg AS s
INNER JOIN #xml.nodes('/root/segment') AS ParamValues(x1)
CROSS APPLY ParamValues.x1.nodes('details') AS D(Detail)
ON s.trainingEventID = ParamValues.x1.value('(../trainingEventID/text())[1]', 'INT')
AND s.segmentDate = ParamValues.x1.value('(segmentDate/text())[1]', 'DATE')
AND s.nonProdHrs = ParamValues.x1.value('(hours/text())[1]', 'INT')

Resources