Sql server 非平凡where子句的Upsert死锁

Sql server 非平凡where子句的Upsert死锁,sql-server,database-deadlocks,Sql Server,Database Deadlocks,我正在尝试编写一些SQL,它可以在相当复杂的条件上进行升级: begintran; 使用(可序列化)更新LocationLog 设置开始时间=大小写 当StartTime>@StartTime然后@StartTime 其他开始时间 完,, 结束时间=案例 当EndTime

我正在尝试编写一些SQL,它可以在相当复杂的条件上进行升级:

begintran;
使用(可序列化)更新LocationLog
设置开始时间=大小写
当StartTime>@StartTime然后@StartTime
其他开始时间
完,,
结束时间=案例
当EndTime<@EndTime然后@EndTime
其他结束时间
完,,
Updated=GETUTCDATE()
Who=@Who
及(
@RangeStart和@RangeEnd之间的开始时间
或
@RangeStart和@RangeEnd之间的结束时间
)
AND cast(纬度为十进制(8,5))=cast(@纬度为十进制(8,5))
AND cast(经度为十进制(8,5))=cast(@经度为十进制(8,5))
和(精度=@精度或合并(精度,@精度)为空)
和(高度=@高度或合并(高度,@高度)为空)
和(AltitudeAccuracy=@AltitudeAccuracy或COALESCE(AltitudeAccuracy,@AltitudeAccuracy)为空)
和(Heading=@Heading或COALESCE(Heading,@Heading)为空)
和(速度=@Speed或联合(速度,@Speed)为空);
如果@@ROWCOUNT=0
开始
插入位置(UUID、Who、开始时间、结束时间、纬度、经度、精度、高度、高度精度、航向、速度、创建时间、更新)
值(NEWID()、@Who、@StartTime、@EndTime、@纬度、@经度、@精度、@高度、@AltitudeAccuracy、@航向、@Speed、GETUTCDATE()、GETUTCDATE())
结束
提交传输
我使用标准的“update,if@@@rowcount=0 insert”处理事务并可序列化,这与(据我所知)相同,只是没有使用单个列ID,而是使用大量候选列,因为无法通过编程方式生成单个候选键

当这个被并发调用时,我会出现死锁,我不知道为什么。为了帮助完成这张图,以下是表格定义:

创建表位置日志(
[UUID][uniqueidentifier]非空约束[PK_位置]主键非聚集,
[Who][uniqueidentifier]非空索引[IX_Who],
[StartTime][datetime]不为空,
[EndTime][datetime]空,
[纬度][十进制](9,6)不为空,
[经度][十进制](9,6)不为空,
[精度][浮点]空,
[高度][浮动]空,
[AltitudeAccuracy][float]空,
[标题][浮动]空,
[速度][float]为空,
[CreatedUtc][datetime]不为空,
[UpdatedUtc][datetime]不为空
)
下面是一个脚本,使用上述sql将导致大量死锁:

我希望答案中有两件事:

  • 这就是为什么会出现僵局的原因。(我已经浏览了代码,我不明白我错了什么)
  • 对代码的修复将允许我这样做
  • 这个谓词:

    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

    这个谓词:

    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

    我推荐丹·古兹曼的作品

    您很可能需要在
    UPDATE
    语句中添加
    HOLDLOCK
    提示<需要code>HOLDLOCK,以确保锁一直保持到事务结束

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

    这就解释了为什么会出现死锁-锁只在
    UPDATE
    语句期间保持,然后释放,这允许在
    INSERT
    之前挤入另一个会话

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

    我建议Dan Guzman阅读

    您很可能需要在
    UPDATE
    语句中添加
    HOLDLOCK
    提示<需要code>HOLDLOCK,以确保锁一直保持到事务结束

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

    这就解释了为什么会出现死锁-锁只在
    UPDATE
    语句期间保持,然后释放,这允许在
    INSERT
    之前挤入另一个会话


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

    有没有可能更改为集群identity bigint主键?在WHERE子句中消除Cases(s)的任何方法,并提供适当的索引以方便WHERE子句?张贴死锁图-它告诉您是什么引起的。并且考虑在它们专门研究数据库问题时发布。@ PbbuStin当然计划对这个索引进行仔细的索引。这可能是a)包括强制转换,或者b)将强制转换替换为索引的
    持久化的
    计算列…我知道这里的性能问题,但我试图找出的是死锁(可能与此相关?)所有的话,我很肯定主键根本不是问题的一部分,特别是因为我明确地标记了非集群,以避免碎片问题,因为这是一个独立的表,没有其他表引用它。@ DaleBurl感谢!我将同时做这两个(死锁图,并考虑DBA。.是否有可能更改为群集Identity bigint主键?是否有办法消除WHERE子句中的强制转换()并提供适当的索引t