显然,我的CLR汇编函数引起了死锁?


9

我们的应用程序需要与Oracle数据库或Microsoft SQL Server数据库同样良好地工作。为方便起见,我们创建了一些UDF以使查询语法同质。例如,SQL Server具有GETDATE(),而Oracle具有SYSDATE。它们执行相同的功能,但它们是不同的词。我们为两个平台编写了一个名为NOW()的包装UDF,该包装将相关的平台特定语法包装在一个通用函数名称中。我们还有其他这样的功能,其中一些功能实际上什么也不做,只是为了同质化而存在。不幸的是,这对于SQL Server是有成本的。内联标量UDF严重破坏性能,并完全禁用并行性。作为替代方案,我们编写了CLR汇编函数以实现相同的目标。当我们将其部署到客户端时,他们开始遇到频繁的死锁。这个特定的客户端正在使用复制和高可用性技术,我想知道这里是否存在某种交互。我只是不了解引入CLR函数将如何导致这样的问题。作为参考,我在C#中包含了原始的标量UDF定义以及替换的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

CLR组装功能

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

用于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

9
确定性标量CLR函数不应导致死锁。当然,可以读取数据库的CLR函数。您应该在问题中包括死锁XML。
David Browne-微软

Answers:


7

您正在使用哪个版本的SQL Server?

我确实记得不久前看到SQL Server 2017中的行为略有变化。我将不得不回头看看是否能找到我做过记录的地方,但是我认为这与在访问SQLCLR对象时启动模式锁有关。

在寻找所需内容的同时,我会针对您的方法说以下几点:

  1. 请使用Sql*输入参数的类型,返回类型。您应该使用SqlString而不是stringSqlString非常相似,可为空字符串(你的value?,但它有内置的其他功能是SQL Server的特异性。所有Sql*类型都有一个Value属性,它返回预期的.NET类型(例如SqlString.Value回报stringSqlInt32回报intSqlDateTime回报率DateTime等)。
  2. 无论僵局是否相关,我都建议从整个方法入手。我之所以这样说是因为:

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

        PRINT LTRIM(RTRIM(N'      a       '));
    6. 另外,我只是注意到您的函数(在T-SQL和C#中)仅使用3个输入参数中的1个。这仅仅是概念证明还是编写的代码?

1.感谢您提供有关使用Sql类型的技巧。我现在进行更改。2.这里有外力在起作用,因此必须使用外力。我对此并不感到兴奋,但请相信我,它比其他选择更好。我最初的问题包含一些解释,说明为什么存在并使用了似似的asinine函数。
Russ Suter

@RussSuter理解了:外力。我只是指出做出该决定时可能未知的一些陷阱。无论哪种方式,我都无法找到我的笔记,也无法从我记得的一些细节中重现该场景。我只是记得2017年在交易和从程序集调用代码方面肯定发生了一些变化,并且对此感到非常恼火,因为这似乎是不必要的变化,我不得不为我正在测试的方法而努力在以前的版本中很好。因此,请在问题中发布指向死锁XML的链接。
所罗门·鲁兹基

感谢您提供其他信息。这是指向XML的链接:dropbox.com/s/n9w8nsdojqdypqm/deadlock17.xml?dl=0
Russ Suter

@RussSuter您是否尝试过内联T-SQL?查看死锁XML(这并不容易,因为它是一行,因为所有换行都以某种方式被删除了),它似乎在会话60和会话78之间存在一系列PAGE锁定。两个会话之间有8个页面被锁定:一个会话为3页SPID,其他为5。每个进程都有不同的进程ID,因此这是并行性问题。如果这与SQLCLR有关,则可能具有讽刺意味的事实是,SQLCLR并未阻止并行性。这就是为什么我问您是否尝试过将简单函数内联,因为这也可能显示死锁。
所罗门·鲁兹基
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.