显然,我的 CLR 汇编函数导致死锁?

Rus*_*ter 9 sql-server deadlock functions sql-clr

我们的应用程序需要与 Oracle 数据库或 Microsoft SQL Server 数据库同样良好地工作。为了促进这一点,我们创建了一些 UDF 来统一我们的查询语法。例如,SQL Server 有 GETDATE() 而 Oracle 有 SYSDATE。它们执行相同的功能,但它们是不同的词。我们为两个平台编写了一个名为 NOW() 的包装器 UDF,它将相关的平台特定语法包装在一个公共函数名称中。我们还有其他这样的功能,其中一些本质上什么都不做,只是为了同质化而存在。不幸的是,这对 SQL Server 来说是有代价的。内联标量 UDF 对性能造成严重破坏并完全禁用并行性。作为替代方案,我们编写了 CLR 汇编函数来实现相同的目标。当我们将其部署到客户端时,他们开始经常遇到死锁。这个特定的客户端正在使用复制和高可用性技术,我想知道这里是否存在某种交互。我只是不明白如何引入 CLR 函数会导致这样的问题。作为参考,我已经包含了原始标量 UDF 定义以及 C# 中的替换 CLR 定义及其 SQL 声明。如果有帮助,我也可以提供死锁 XML。

原始UDF

CREATE FUNCTION [fn].[APAD]
(
    @Value VARCHAR(4000)
    , @tablename VARCHAR(4000) = NULL
    , @columnname VARCHAR(4000) = NULL
)

RETURNS VARCHAR(4000)
WITH SCHEMABINDING
AS

BEGIN
    RETURN LTRIM(RTRIM(@Value))
END
GO
Run Code Online (Sandbox Code Playgroud)

CLR 汇编函数

[SqlFunction(IsDeterministic = true)]
public static string APAD(string value, string tableName, string columnName)
{
    return value?.Trim();
}
Run Code Online (Sandbox Code Playgroud)

CLR 函数的 SQL Server 声明

CREATE FUNCTION [fn].[APAD]
(
    @Value NVARCHAR(4000),
    @TableName NVARCHAR(4000),
    @ColumnName NVARCHAR(4000)
) RETURNS NVARCHAR(4000)
AS
EXTERNAL NAME ASI.fn.APAD
GO
Run Code Online (Sandbox Code Playgroud)

Sol*_*zky 7

您使用的是什么版本的 SQL Server?

我确实记得不久前看到 SQL Server 2017 中的行为发生了轻微变化。我将不得不回去看看我是否能找到我记录它的地方,但我认为这与访问 SQLCLR 对象时启动的模式锁有关。

当我在寻找那个时,我会就你的方法说以下几点:

  1. 请使用Sql*输入参数的类型,返回类型。您应该使用SqlString而不是string. SqlString非常类似于可为空的字符串(您的value?,但它具有其他内置的 SQL Server 特定功能。所有Sql*类型都有一个Value返回预期 .NET 类型的属性(例如,SqlString.Valuereturns stringSqlInt32returns intSqlDateTimereturnsDateTime等)。
  2. 无论死锁是否相关,我都建议不要从整个方法开始。我这样说是因为:

    1. 即使确定性 SQLCLR UDF 能够参与并行计划,您也很可能会因为模拟简单的内置函数而获得性能损失。
    2. SQLCLR API 不允许VARCHAR. 您是否可以将所有内容隐式转换为NVARCHAR然后再次转换VARCHAR为简单操作?
    3. SQLCLR API 不允许重载,因此您可能需要多个版本的函数来允许 T-SQL 和/或 PL/SQL 中的不同签名。
    4. 类似于不允许重载,NVARCHAR(4000)和之间有很大的区别NVARCHAR(MAX)MAX类型(在签名中甚至只有一个)使 SQLCLR 调用花费的时间是MAX签名中没有任何类型的两倍(我相信这成立对VARBINARY(MAX)vsVARBINARY(4000)也是如此)。因此,您需要在以下两者之间做出决定:
      • 仅使用NVARCHAR(MAX)简化的 API,但在使用 8000 字节或更少的字符串数据时会降低性能,或
      • 为所有/大多数/许多字符串函数创建两种变体:一种有MAX类型,另一种没有(当你保证永远不会输入或输出超过 8000 字节的字符串数据时)。这是我为SQL#库中的大多数函数选择的方法:有一个Trim()函数可能具有一种或多种MAX类型,而一个Trim4k()版本MAX在签名或结果集架构中的任何地方都没有类型。“4k”版本绝对更高效。
    5. 鉴于问题中的示例,您没有仔细模拟功能。LTRIM并且RTRIM只修剪空格,而 .NETString.Trim()修剪空白(至少是空格、制表符和换行符)。例如:

        PRINT LTRIM(RTRIM(N'      a       '));
      
      Run Code Online (Sandbox Code Playgroud)
    6. 另外,我刚刚注意到您的函数,无论是在 T-SQL 还是在 C# 中,都只使用 3 个输入参数中的 1 个。这只是概念证明还是编辑过的代码?