对于不重要的where子句,增加死锁

时间:2019-04-11 20:13:53

标签: sql-server database-deadlocks

我正在尝试编写一些在相当复杂的条件下进行upsert的SQL:

BEGIN TRAN;

  UPDATE LocationLog WITH(SERIALIZABLE)
  SET StartTime = CASE
      WHEN StartTime > @StartTime THEN @StartTime
      ELSE StartTime
    END,
    EndTime = CASE
      WHEN EndTime < @EndTime THEN @EndTime
      ELSE EndTime
    END,
    Updated = GETUTCDATE()
  WHERE Who = @Who
    AND (
        StartTime BETWEEN @RangeStart and @RangeEnd
        or
        EndTime BETWEEN @RangeStart and @RangeEnd
    )
    AND cast(Latitude as decimal(8,5)) = cast(@Latitude as decimal(8, 5))
    AND cast(Longitude as decimal(8,5)) = cast(@Longitude as decimal(8, 5))
    AND (Accuracy = @Accuracy or COALESCE(Accuracy, @Accuracy) is NULL)
    AND (Altitude = @Altitude or COALESCE(Altitude, @Altitude) is NULL)
    AND (AltitudeAccuracy = @AltitudeAccuracy or COALESCE(AltitudeAccuracy, @AltitudeAccuracy) is NULL)
    AND (Heading = @Heading or COALESCE(Heading, @Heading) is NULL)
    AND (Speed = @Speed or COALESCE(Speed, @Speed) is NULL);

  IF @@ROWCOUNT = 0
  BEGIN
    INSERT Position(UUID, Who, StartTime, EndTime, Latitude, Longitude, Accuracy, Altitude, AltitudeAccuracy, Heading, Speed, CreatedTime, Updated)
    VALUES (NEWID(), @Who, @StartTime, @EndTime, @Latitude, @Longitude, @Accuracy, @Altitude, @AltitudeAccuracy, @Heading, @Speed, GETUTCDATE(), GETUTCDATE())
  END

COMMIT TRAN

我正在使用带有事务且可序列化的标准“更新,如果@@ rowcount = 0插入”,它与Sam Saffron's "Insert or Update pattern for Sql Server" Blog post相同(几乎可以告诉我),除了不使用单个列ID,由于无法以编程方式生成单个候选键,因此我使用了大量候选列。

并发调用时我陷入僵局,我不知道为什么。只是为了帮助您完整了解图片,这是表格定义:

CREATE TABLE LocationLog (
    [UUID] [uniqueidentifier] NOT NULL CONSTRAINT [PK_Position] PRIMARY KEY NONCLUSTERED ,
    [Who] [uniqueidentifier] NOT NULL INDEX [IX_Who],
    [StartTime] [datetime] NOT NULL,
    [EndTime] [datetime] NULL,
    [Latitude] [decimal](9, 6) NOT NULL,
    [Longitude] [decimal](9, 6) NOT NULL,
    [Accuracy] [float] NULL,
    [Altitude] [float] NULL,
    [AltitudeAccuracy] [float] NULL,
    [Heading] [float] NULL,
    [Speed] [float] NULL,
    [CreatedUtc] [datetime] NOT NULL,
    [UpdatedUtc] [datetime] NOT NULL
)

这是一个脚本,它将使用上述sql导致 lot 死锁:https://dotnetfiddle.net/xkze6l

我希望答案中有两件事:

  1. 为什么会出现这种僵局的解释。 (我已经遍历了代码,但我不明白自己做错了什么。)
  2. 针对该代码的修复程序,让我可以做到这一点。

2 个答案:

答案 0 :(得分:0)

此谓词:

AND (
    StartTime BETWEEN @RangeStart and @RangeEnd
    or
    EndTime BETWEEN @RangeStart and @RangeEnd
)
AND cast(Latitude as decimal(8,5)) = cast(@Latitude as decimal(8, 5))
AND cast(Longitude as decimal(8,5)) = cast(@Longitude as decimal(8, 5))

将需要进行表扫描。因此,如果您每次都要扫描表,则不妨使用TABLOCKX提示而不是SERIALIZABLE。

答案 1 :(得分:0)

我建议阅读Dan Guzman的Conditional INSERT/UPDATE Race Condition

您很有可能需要在您的HOLDLOCK语句中添加UPDATE提示。需要HOLDLOCK以确保在交易结束前一直保持锁定。

据我了解,您的WITH(SERIALIZABLE)提示仅适用于UPDATE语句,不会影响下一个INSERT语句。

这说明了为什么会出现死锁-该锁仅在UPDATE语句的持续时间内保持,然后被释放,从而允许另一个会话在INSERT之前进入。< / p>

也许您还需要UPDLOCK,因为即使它是UPDATE语句,它也必须先找到要更新的行,并且应该在此SELECT阶段之前放置锁。 UPDATE阶段。