【问题标题】:How to troubleshoot SqlException deadlocked on lock | communication buffer resources如何解决锁上死锁的 SqlException通信缓冲资源
【发布时间】:2021-06-15 11:44:23
【问题描述】:

stackoverflow 上已经有这个问题的不同版本,但没有一个版本能帮助我找到问题的根源。所以,我在这里再次详细说明我的问题。

我们随机收到 Transaction (Process ID xx) was deadlocked on lock | communication buffer resources with another process and has been chosen as the deadlock victim. Rerun the transaction. 。让我明确一点,这不是行或表级锁定。我已经尝试了足够多的猜测/随机事物;我需要关于如何解决通信缓冲区死锁的详细分步指南。

如果您对具体细节感兴趣,请继续阅读。

场景的具体细节:我们有一个非常简单的基于 Dapper ORM 的 C# .net 核心 Web API,它接收请求并对托管在此 Microsoft Sql 服务器上的数据库执行 CRUD 操作。为此,连接管理器(注册为范围服务)在请求范围内打开一个新的IDbConnection 连接;此连接用于执行删除、插入、更新或获取。对于插入/更新/删除 C# 行看起来像这样 await connection.ExecuteAsync("<Create update or delete statement>", entity); 对于 GET 请求,我们只需运行 await connection.QueryFirstOrDefaultAsync<TEntity>("<select statement>", entity); ;有 5 种类型的实体(都呈现简单的非关系表)。他们都按 ID CRUD。

到目前为止已经尝试过什么

  1. MAXDOP=1 SQL 语句的查询提示
  2. 确保一种实体在给定时间点只有 1 个实体 CRUD。
  3. 正在重新启动 SQL 服务器/应用程序实例
  4. 确保端口/RAM/CPU/网络带宽没有耗尽
  5. 改变 DATABASE XXXXXX SET READ_COMMITTED_SNAPSHOT ON/OFF
  6. 尽可能减少事务
  7. 持久重试策略作为一种解决方法(以处理问题的随机瞬态性质)
  8. 每个实体类型一个线程

服务器规格: 我们在具有 64 个内核和 400GB RAM 的虚拟机中托管了 Microsoft Sql Server 2016 On Azure。此服务器上的通常工作负载是 10% CPU 和 30% RAM,偶尔会上升到 80% CPU 和 350GB RAM。在此问题发生的所有时间,CPU 使用率都低于 20%(大多数情况下在 10% 左右,只有一次是 20%,RAM 在所有情况下都低于 30%)。

根据@Dan Guzman 的请求的死锁 XML 事件

这个帖子的文件太大,所以创建了这个谷歌驱动器文件。请点击以下链接,然后在右上角点击下载。它是一个 zip 文件。

https://drive.google.com/file/d/1oZ4dT8Yrd2uW2oBqBy9XK_laq7ftGzFJ/view?usp=sharing

【问题讨论】:

  • 将死锁 xml 添加到您的问题中。有关从 system_health xe 跟踪中提取事件的示例,请参阅 this answer
  • @DanGuzman 由于文件大小限制,我不得不将文件放在谷歌驱动器上。我在上面的帖子中添加了文件链接。请点击右上角的下载按钮。谢谢。
  • 我看到所有参数都声明为nvarchar(4000)。当引用的列类型不同时,这可能会阻止索引的有效使用,从而导致在 UPDATE 查询期间进行完全扫描,从而导致死锁。尝试使用与引用列类型相同的强类型参数,并检查执行计划的效率。
  • @DanGuzman,非常感谢。杰出的。它似乎是固定的。即使处理了 200 万个请求,也不例外。 Dapper 默认参数大小 4000 不适合我的情况。

标签: c# sql-server deadlock sqlexception database-deadlocks


【解决方案1】:

死锁通常是需要查询和索引调整的症状。下面是来自死锁跟踪的示例查询,它表明了死锁的根本原因:

<inputbuf>
@SomeStatus1 nvarchar(4000),@ProductName nvarchar(4000),@ProductNameSide nvarchar(4000),@BayNo nvarchar(4000),@CreatedDateTime datetime,@EffectiveDate datetime,@ForSaleFrom datetime,@ForSaleTo datetime,@SetupInfoNode nvarchar(4000),@LocationNumber nvarchar(4000),@AverageProductPrice decimal(3,2),@NetAverageCost decimal(3,1),@FocustProductType nvarchar(4000),@IsProduceCode nvarchar(4000),@ActivationIndicator nvarchar(4000),@ResourceType nvarchar(4000),@ProductIdentifierNumber nvarchar(4000),@SellingStatus nvarchar(4000),@SectionId nvarchar(4000),@SectionName nvarchar(4000),@SellPriceGroup nvarchar(4000),@ShelfCapacity decimal(1,0),@SellingPriceTaxExclu decimal(2,0),@SellingPriceTaxInclu decimal(2,0),@UnitToSell nvarchar(4000),@VendorNumber nvarchar(4000),@PastDate datetime,@PastPrice decimal(29,0))
UPDATE dbo.ProductPricingTable 
SET SellingPriceTaxExclu = @SellingPriceTaxExclu, SellingPriceTaxInclu = @SellingPriceTaxInclu, 
SellPriceGroup = @SellPriceGroup, 
ActivationIndicator = @ActivationIndicator, 
IsProduceCode = @IsProduceCode, 
EffectiveDate = @EffectiveDate, 
NetCos
</inputbuf>

虽然 SQL 语句文本被截断,但它确实表明所有参数声明都是nvarchar(4000)(ORM 的常见问题)。当 join/where 子句中引用的列类型不同时,这可能会阻止索引的有效使用,从而导致在并发查询期间导致死锁的全扫描。

更改参数类型以匹配引用列的类型并检查执行计划的效率。

【讨论】:

    【解决方案2】:

    @DanGuzman 提供了帮助,因此我不得不投票/选择他的答案作为已接受的答案。但是,我想总结一下这里发生了什么,我学到了什么,以及如何解决通信缓冲区死锁(或任何死锁)的分步方法。

    步骤 - 1
    拉死锁报告。我使用了以下查询,但您也可以使用@DanGuzman 建议的查询(在此问题的评论部分)。

    SELECT
       xed.value('@timestamp', 'datetime2(3)') as CreationDate,
       xed.query('.') AS XEvent
    FROM
    (
       SELECT CAST([target_data] AS XML) AS TargetData
       FROM sys.dm_xe_session_targets AS st
          INNER JOIN sys.dm_xe_sessions AS s
             ON s.address = st.event_session_address
          WHERE s.name = N'system_health'
             AND st.target_name = N'ring_buffer'
    ) AS Data
    CROSS APPLY TargetData.nodes('RingBufferTarget/event[@name="xml_deadlock_report"]') AS XEventData (xed)
    ORDER BY CreationDate DESC
    

    步骤 - 2
    找到与您的 sql 异常时间/数据相对应的死锁事件。然后结合Detecting and Ending Deadlocks 指南阅读此报告,以了解死锁问题的根本原因。就我而言,我在通信缓冲区上遇到了死锁,因此根据本指南,内存(检测和结束死锁指南的Memory 部分)一定是导致问题的原因。正如 Dan 指出的那样,在我的情况下,死锁报告中出现了以下查询,该报告使用了太多缓冲区(由于查询效率低下)。那么什么是通信缓冲区死锁?好吧,如果这个查询需要太多的缓冲区来完成它的执行,那么两个这样的查询可以同时开始执行并开始申请他们需要的缓冲区,但在某些时候可用的缓冲区可能不够,他们将不得不等待完成其他查询的执行释放了更多缓冲区。因此,两个查询都等待对方完成,以期释放更多缓冲区。这可能会导致缓冲区死锁(根据指南的内存部分)

    <inputbuf>
    @SomeStatus1 nvarchar(4000),@ProductName nvarchar(4000),@ProductNameSide nvarchar(4000),@BayNo nvarchar(4000),@CreatedDateTime datetime,@EffectiveDate datetime,@ForSaleFrom datetime,@ForSaleTo datetime,@SetupInfoNode nvarchar(4000),@LocationNumber nvarchar(4000),@AverageProductPrice decimal(3,2),@NetAverageCost decimal(3,1),@FocustProductType nvarchar(4000),@IsProduceCode nvarchar(4000),@ActivationIndicator nvarchar(4000),@ResourceType nvarchar(4000),@ProductIdentifierNumber nvarchar(4000),@SellingStatus nvarchar(4000),@SectionId nvarchar(4000),@SectionName nvarchar(4000),@SellPriceGroup nvarchar(4000),@ShelfCapacity decimal(1,0),@SellingPriceTaxExclu decimal(2,0),@SellingPriceTaxInclu decimal(2,0),@UnitToSell nvarchar(4000),@VendorNumber nvarchar(4000),@PastDate datetime,@PastPrice decimal(29,0))
    UPDATE dbo.ProductPricingTable 
    SET SellingPriceTaxExclu = @SellingPriceTaxExclu, SellingPriceTaxInclu = @SellingPriceTaxInclu, 
    SellPriceGroup = @SellPriceGroup, 
    ActivationIndicator = @ActivationIndicator, 
    IsProduceCode = @IsProduceCode, 
    EffectiveDate = @EffectiveDate, 
    NetCos
    </inputbuf>
    

    第 3 步(修复)
    等待 !!!!但我使用了 Dapper。那它怎么能把我的查询变成这么致命的查询呢?好吧,Dapper 对于大多数具有开箱即用默认值的情况来说都很棒,但是,很明显,在我的情况下,这个默认的 4000 nvarchar 杀死了它(请阅读 Dan 的回答以了解此类查询如何导致问题)。正如 Dan 建议的那样,我从 await connection.ExecuteAsync("&lt;Create update or delete statement&gt;", entity); 这样的输入实体自动构建参数,其中 entity 是 C# 模型类的一个实例。我将其更改为自定义参数,如下所示。 (为简单起见,我只添加了一个参数,但您可以使用所有必需的参数)

                var parameters = new DynamicParameters();
                parameters.Add("Reference", entity.Reference, DbType.AnsiString, size: 18 );
    await connection.ExecuteAsync("<Create update or delete statement>", parameters );
    

    我可以在分析器中看到请求现在具有完全匹配的类型列参数类型。就是这样,此修复程序使问题消失了。谢谢丹。

    结论
    我可以得出结论,在我的情况下,通信缓冲区发生死锁是因为一个错误的查询占用了太多的缓冲区来执行。之所以如此,是因为我盲目地使用了默认的 Dapper 参数构建器。使用 Dapper 的自定义参数生成器解决了这个问题。

    【讨论】:

      猜你喜欢
      • 2011-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多