我有一个表的 UPDATE 和 INSERT 存储过程,该过程被执行多次以更新行(每 5 秒一次)。此过程在多个过程中使用,因此执行计数很高,即使表具有正确的索引并放置了表级提示,我也会在此过程中遇到死锁问题。
我已经根据博客尝试了很多调整,但在减少死锁数量方面没有运气,需要帮助来理解我做错了什么。逻辑是从表中获取 MAX(ID) 并更新记录或插入(如果不存在)。该表最多包含 5000 条记录。
这是带有索引和存储过程的表结构:
CREATE TABLE [dbo].[TBL_ABC]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[NotificationRequest] [nvarchar](250) NULL,
[ID_OrgLevelValue] [int] NULL,
[CreatedDate] [datetime] NULL,
[ID_Control_area] [int] NULL,
[NotificationMessage] [nvarchar](1000) NULL,
CONSTRAINT [PK_TBL_ABC]
PRIMARY KEY CLUSTERED ([ID] ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[TBL_ABC] WITH CHECK
ADD CONSTRAINT [FK_TBL_ABC_Mast_OrgLevelValue]
FOREIGN KEY([ID_OrgLevelValue])
REFERENCES [dbo].[Mast_OrgLevelValue] ([ID])
GO
ALTER TABLE [dbo].[TBL_ABC] WITH CHECK
ADD CONSTRAINT [FK_ID_Control_area_TBL_ABC]
FOREIGN KEY([ID_Control_area])
REFERENCES [dbo].[Mast_Control_Area] ([Id])
GO
CREATE NONCLUSTERED INDEX [IX_FK_Mast_OrgLevelValue]
ON [dbo].[TBL_ABC] ([ID_OrgLevelValue] ASC,
[NotificationRequest] ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON,
OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
GO
CREATE PROCEDURE [dbo].[usp_AddLineNotification]
@ID_ORGLEVELVALUE INT,
@ReuestType NVARCHAR(250),
@NotificationMessage NVARCHAR(2000)=NULL
AS
BEGIN
BEGIN TRANSACTION AddlineNotification ;
UPDATE TBL_ABC
SET CreatedDate = GETUTCDATE(),
NotificationMessage = @NotificationMessage
WHERE ID = (SELECT MAX(ID)
FROM TBL_ABC WITH (UPDLOCK)
WHERE NotificationRequest = @ReuestType
AND ID_OrgLevelValue = @ID_ORGLEVELVALUE)
IF @@ROWCOUNT = 0
BEGIN
INSERT INTO TBL_ABC (NotificationRequest, ID_OrgLevelValue, CreatedDate, NotificationMessage)
SELECT
@RequestType, @ID_ORGLEVELVALUE, GETUTCDATE(), @NotificationMessage
END
COMMIT TRAN;
END
下面是死锁 XML,用于进一步了解此问题。
<deadlock>
<victim-list>
<victimProcess id="process26f17535468" />
</victim-list>
<process-list>
<process id="process26f17535468" taskpriority="0" logused="260" waitresource="KEY: 5:72057595550236672 (f8a18c219a4d)" waittime="1238" ownerId="3563795785" transactionname="AddlineNotification" lasttranstarted="2024-06-18T18:24:43.583" XDES="0x26fe9750460" lockMode="RangeS-U" schedulerid="1" kpid="62652" status="suspended" spid="57" sbid="2" ecid="0" priority="0" trancount="2" lastbatchstarted="2024-06-18T18:24:41.377" lastbatchcompleted="2024-06-18T18:24:41.370" lastattention="1900-01-01T00:00:00.370" clientapp="EntityFramework" hostname="A98SW127PSMS1AP" hostpid="62948" loginname="gxoupuser" isolationlevel="read committed (2)" xactid="3563795785" currentdb="5" currentdbname="GXOUpgrade_LIVE" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="GXOUpgrade_LIVE.dbo.usp_AddLineNotification" line="13" stmtstart="776" stmtend="1300" sqlhandle="0x03000500e53503310e82830071b1000001000000000000000000000000000000000000000000000000000000">
UPDATE TBL_ABC
SET CreatedDate =GETUTCDATE (), NotificationMessage=@NotificationMessage
WHERE ID = ( SELECT MAX(ID) FROM TBL_ABC WITH (UPDLOCK) WHERE NotificationRequest = @ReuestType AND ID_OrgLevelValue=@ID_ORGLEVELVALUE </frame>
<frame procname="GXOUpgrade_LIVE.dbo.usp_Interface_WOUpdate" line="4451" stmtstart="273404" stmtend="273514" sqlhandle="0x03000500198ac41ee39aac0078b1000001000000000000000000000000000000000000000000000000000000">
EXEC usp_AddLineNotification @LineID
,'Losses </frame>
</executionStack>
<inputbuf>
Proc [Database Id = 5 Object Id = 516196889] </inputbuf>
</process>
<process id="process26feee55468" taskpriority="0" logused="107136" waitresource="KEY: 5:72057595550236672 (20b095273284)" waittime="1070" ownerId="3563785553" transactionname="user_transaction" lasttranstarted="2024-06-18T18:24:41.127" XDES="0x26e8a208460" lockMode="RangeS-U" schedulerid="1" kpid="36164" status="suspended" spid="81" sbid="2" ecid="0" priority="0" trancount="3" lastbatchstarted="2024-06-18T18:24:41.127" lastbatchcompleted="2024-06-18T18:24:41.127" lastattention="1900-01-01T00:00:00.127" clientapp="EntityFramework" hostname="A98SW127PSMS1AP" hostpid="28224" loginname="gxoupuser" isolationlevel="read committed (2)" xactid="3563785553" currentdb="5" currentdbname="GXOUpgrade_LIVE" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="GXOUpgrade_LIVE.dbo.usp_AddLineNotification" line="13" stmtstart="776" stmtend="1300" sqlhandle="0x03000500e53503310e82830071b1000001000000000000000000000000000000000000000000000000000000">
UPDATE TBL_ABC
SET CreatedDate =GETUTCDATE (), NotificationMessage=@NotificationMessage
WHERE ID = ( SELECT MAX(ID) FROM TBL_ABC WITH (UPDLOCK) WHERE NotificationRequest = @ReuestType AND ID_OrgLevelValue=@ID_ORGLEVELVALUE </frame>
<frame procname="GXOUpgrade_LIVE.dbo.usp_WorkOrder_Creation" line="940" stmtstart="92022" stmtend="92104" sqlhandle="0x03000500a01ea36c2a9cac0078b1000001000000000000000000000000000000000000000000000000000000">
EXEC usp_AddLineNotification @LineID,'WO </frame>
</executionStack>
<inputbuf>
Proc [Database Id = 5 Object Id = 1822629536] </inputbuf>
</process>
</process-list>
<resource-list>
<keylock hobtid="72057595550236672" dbid="5" objectname="GXOUpgrade_LIVE.sys.query_notification_2045927056" indexname="cidx" id="lock271878c4400" mode="RangeX-X" associatedObjectId="72057595550236672">
<owner-list>
<owner id="process26feee55468" mode="RangeX-X" />
</owner-list>
<waiter-list>
<waiter id="process26f17535468" mode="RangeS-U" requestType="wait" />
</waiter-list>
</keylock>
<keylock hobtid="72057595550236672" dbid="5" objectname="GXOUpgrade_LIVE.sys.query_notification_2045927056" indexname="cidx" id="lock2717ac11180" mode="RangeS-U" associatedObjectId="72057595550236672">
<owner-list>
<owner id="process26f17535468" mode="RangeS-U" />
</owner-list>
<waiter-list>
<waiter id="process26feee55468" mode="RangeS-U" requestType="wait" />
</waiter-list>
</keylock>
</resource-list>
</deadlock>
我尝试将代码放在桌子上的 TRANSACTION 和 HOLDLOCK 中。
我会尝试以下方法。
先尝试插入,我相信这是并发安全的
与更新分开获取您要更新的记录的Id。我认为这可能会强制按顺序进行锁定,从而避免死锁。
DECLARE @MaxId bigint;
INSERT INTO TBL_ABC (NotificationRequest, ID_OrgLevelValue, CreatedDate, NotificationMessage)
SELECT @RequestType, @ID_ORGLEVELVALUE, GETUTCDATE(), @NotificationMessage
WHERE NOT EXISTS (
SELECT 1
FROM TBL_ABC
WHERE NotificationRequest = @ReuestType
AND ID_OrgLevelValue = @ID_ORGLEVELVALUE
);
IF @@ROWCOUNT = 0 BEGIN
-- Find the row we want to update and lock it for update
SELECT TOP 1 @MaxId = ID
FROM TBL_ABC WITH (UPDLOCK)
WHERE NotificationRequest = @ReuestType
AND ID_OrgLevelValue = @ID_ORGLEVELVALUE
ORDER BY Id DESC;
UPDATE TBL_ABC SET
CreatedDate = GETUTCDATE()
, NotificationMessage = @NotificationMessage
WHERE ID = @MaxId;
END;