在并发调用中使用 CTE 和 OUTPUT 块进行更新

Rob*_*ini 5 sql-server deadlock stored-procedures concurrency locking

我遇到了死锁问题,所以我开始使用我在某处读到的这个 CTE 技巧。没有更多的僵局。

但是现在我添加的每个客户端都会减慢(阻止?)存储过程。例如,1 个客户端的 1-2 秒更新变为两个客户端的 2-4 秒。(这个简单的隐喻查询运行时间为 0.001 秒,但在少数客户之后达到 0.03 秒——所以这个问题与我的实际实现无关。)

是锁定问题吗?我需要将它包装在(某种)交易中吗?

WITH UpdateView AS (
    SELECT TOP 1 W.*
    FROM [WidgetSandbox].[dbo].[Widgets] W
    INNER JOIN [WidgetSandbox].[dbo].[Sizes] S ON W.SizeId = S.Id
    WHERE W.StatusId = @availableStatusId
    AND W.ColorCode = @colorCode
    ORDER BY S.DiameterInches
)

UPDATE UpdateView 
SET StatusId = @soldOutStatusId
OUTPUT INSERTED.Id INTO @outputIds;

SET @singleUpdatedId = (SELECT TOP 1 Id FROM @outputIds);

SELECT * FROM [WidgetSandbox].[dbo].[Widgets]
WHERE Id = @singleUpdatedId;
Run Code Online (Sandbox Code Playgroud)

我希望我能更好地了解具体问题是什么,但事实上我只是被卡住了......

DDL,如果有帮助:https : //gist.github.com/RobertBaldini/3740c7bb85eea47d7fe63cb8602ac2d6

回购:https : //github.com/RobertBaldini/WidgetSandbox

Pau*_*ite 8

您看到的许多问题都是由低效的执行计划引起的:

提供的计划

并不是说提供的计划和查询与问题相匹配,但即便如此,我仍在使用提供的内容。

无论如何,您应该实现我在上一个问题中提到的Name列数据类型更改(来自nvarchar(max))。更重要的是,你需要添加我推荐的索引,这样不需要排序就可以找到要更新的行:

CREATE NONCLUSTERED INDEX IX_dbo_Sizes__DiameterInches
ON dbo.Sizes (DiameterInches);

CREATE NONCLUSTERED INDEX IX_dbo_Widgets__SizeId_ColorCode_StatusId__Name
ON dbo.Widgets (SizeId, ColorCode, StatusId)
INCLUDE (Name);
Run Code Online (Sandbox Code Playgroud)

然后,您可以更新所选项目的状态并返回受影响的行:

DECLARE
    -- Constant values guessed, replace with the real ones
    @availableStatusId integer = 1,
    @soldOutStatusId integer= 9,
    @colorCode nvarchar(6) = N'Red';

WITH UpdateView AS 
(
    SELECT TOP (1)
        W.Id,
        W.Name,
        W.StatusId,
        W.ColorCode,
        W.SizeId
    FROM dbo.Widgets AS W WITH (UPDLOCK, ROWLOCK, READPAST)
    JOIN dbo.Sizes AS S 
        ON W.SizeId = S.Id
    WHERE 
        W.StatusId = @availableStatusId
        AND W.ColorCode = @colorCode
    ORDER BY 
        S.DiameterInches
)
UPDATE UpdateView 
SET StatusId = @soldOutStatusId
OUTPUT
    Inserted.Id,
    Inserted.Name,
    Inserted.StatusId,
    Inserted.ColorCode,
    Inserted.SizeId;
Run Code Online (Sandbox Code Playgroud)

注意UPDLOCK, ROWLOCK, READPAST那里的提示。这些是并发 FIFO 访问队列类型表所需的标准提示。有关更多信息,请参阅Remus Rusanu 的将表用作队列

你应该得到的执行计划形状(没有排序!)是:

更新计划

顺便说一句,除非你有很好的理由,否则你的数据库不应该使用SET AUTO_CLOSE ON.