SQL CLR 在 SQL Server 中随机失败

Posted

技术标签:

【中文标题】SQL CLR 在 SQL Server 中随机失败【英文标题】:SQL CLR randomly fails in SQL Server 【发布时间】:2021-03-02 23:21:57 【问题描述】:

我创建了一个 .Net 程序集并将其导入 SQL Server 并在标量值函数 (SVF) 中使用它。

当我执行 SVF 时,它工作正常。第二次也是。但是每三次 SVF 失败,潜在的错误是

System.NullReferenceException:对象引用未设置为对象的实例

我已经向 CLR 添加了 try/catch 语句和调试数据,但我仍然不知道为什么这个 CLR 会失败。

你能帮帮我吗?

这是 C# 代码:

[Microsoft.SqlServer.Server.SqlFunction(DataAccess = DataAccessKind.Read)]
public static SqlString HEAD(SqlString Uri)

    string statusCode = "";
    // Debug info
    string debugData = "1,";

    try
    
        debugData += "2,";
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
        debugData += "3,";
        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(Convert.ToString(Uri));

        req.Method = "HEAD";
        req.AllowAutoRedirect = true;
        debugData += "4,";
        HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
        debugData += "5,";

        if (resp.StatusCode == HttpStatusCode.MethodNotAllowed)
        
            // Use GET instead of HEAD
            debugData += "6,";
            req = (HttpWebRequest)WebRequest.Create(Convert.ToString(Uri));
            req.Method = "GET";
            resp = (HttpWebResponse)req.GetResponse();
        

        debugData += "7,";
        statusCode = ((int)resp.StatusCode).ToString();
        debugData += "8,";
    
    catch (WebException ex)
    
        debugData += "9,";
        HttpWebResponse webResponse = (HttpWebResponse)ex.Response;
        debugData += "10,";
        statusCode = ((int)webResponse.StatusCode).ToString();
        debugData += "11,";
    
    catch (Exception ex)
    
        debugData += "12,";
        statusCode = debugData + ex.Message;
        debugData += "13,";
    
    debugData += "14,";
    //return (statusCode);
    return debugData;

该方法接受一个字符串作为输入(它是一个 URL),并通过使用 HEAD 方法执行 HttpWebrequest 来检查该 URL 是否实际存在。

它返回状态码或错误消息(因此输出字符串)。

程序集已成功导入我的数据库:

ALTER DATABASE MyDatabase SET TRUSTWORTHY ON;
GO

CREATE ASSEMBLY SQLHttpRequest
FROM N'C:\somefolder\SQLHttpRequest.dll'
WITH PERMISSION_SET=UNSAFE;
GO

这是 SVF:

CREATE FUNCTION dbo.uf_head_webrequest
    (@Uri nvarchar(max))
RETURNS nvarchar(max)
AS
EXTERNAL NAME SQLHttpRequest.[SQLHttpRequest.HTTPFunctions].HEAD;

这是我测试这个功能的方式:

declare @uri nvarchar(1000) = 'https://logos-download.com/wp-content/uploads/2016/02/Microsoft_logo_SQL-700x566.png'
SELECT dbo.uf_head_webrequest(@uri)

输出是1,2,3,4,5,7,8,14,

但是,如果我执行几次,我会得到以下响应:

消息 6522,第 16 级,状态 1,第 2 行 在执行用户定义的例程或聚合“uf_head_webrequest”期间发生 .NET Framework 错误: System.NullReferenceException:对象引用未设置为对象的实例。 System.NullReferenceException: 在 SQLHttpRequest.HTTPFunctions.HEAD(SqlString Uri) .

我了解对象实例化的概念,但不明白为什么我没有得到任何调试数据。

我已经在互联网上搜索并提出了这个问题:

SQLCLR .NET Error: Object reference not set to an instance of an object

这描述了 NULL 值的问题。但在我看来,情况并非如此。尤其是因为我一次又一次地使用相同的 Uri。

谢谢!

【问题讨论】:

NullReferenceException 没有任何随机性。这是总是,因为有人试图在null 变量上使用方法或属性。这与 SQLCLR 无关。在数据库中使用这样的阻塞操作也是导致大量阻塞的好方法。从客户端代码执行 HTTP 调用更容易、更高效 如果您坚持从数据库发起 HTTP 调用,更好的选择是使用 SQL Server 2017 的 Python 支持,或者在 2016 年使用 R。您发布的代码在 SQL 中分配了大量临时字符串服务器自己的内存。使用 Python,您可以直接在命令中指定脚本,脚本将在 SQL Server 进程之外执行 让您的数据库服务器执行 HTTP 调用是多么可怕的想法,我怎么强调都不过分。管理/监控资源使用、防火墙、推出新版本和故障排除比在客户端代码中执行这些操作要复杂得多(正如您所经历的那样)。在任何情况下,“表现”都不是一个好的借口;有很多方法可以优化从客户端到服务器的数据吞吐量(批量复制、内存表等) 服务器可能会限制来自同一客户端的请求数。我会在请求周围放置一个 using 块,以便在每个请求结束时处理它。 ex.Response 可以是null,因为如果没有响应(例如连接问题),WebException 将不会有响应。我同意这个线程中的其他人的观点,来自 SQL 的 Web 请求听起来是个坏主意。 【参考方案1】:

可能是这一行(在 catch (WebException ex) 中):

HttpWebResponse webResponse = (HttpWebResponse)ex.Response;

您假设ex.Response 存在。首先尝试检查它是否是null。这可以解释为什么您没有返回 debugData 值(由于错误处理中未处理的异常)。

但是,即使这样修复了错误,这段代码仍然存在几个问题,最大的问题是:

    不清理一次性资源(即没有using()) 使用TRUSTWORTHY ON(请参阅:PLEASE, Please, please Stop Using Impersonation, TRUSTWORTHY, and Cross-DB Ownership Chaining) 不必要地将程序集设置为UNSAFE 而不是EXTERNAL_ACCESS

小问题:

    设置DataAccessKind = Read(您没有进行数据访问,这会影响性能;可能不会增加此类操作的性能,但仍然如此) 使用Convert.ToString(Uri) 而不仅仅是Uri.Value

最后,您为什么要通过编写自己的 HTTP 请求来重新发明***?这个确切的功能有几个现有的选项,它们功能更强大,并且已经过一些测试。我在 GitHub 上看到过一两个不错的免费选项(不要只从一些博客中获取源代码,因为那里有几个相当糟糕的示例),并且通过我的 SQL# (SQLsharp) 项目有一个非免费选项(虽然有一个免费版本,INET_GetWebPages 功能仅在付费版本中),我认为这些免费选项中没有一些功能(至少截至我上次检查时没有)。

【讨论】:

嗨 Solomon 和其他人,感谢您的时间和回复。事实上,由于超时,ex.response 不存在。但是,如果我测试是否为 null,我仍然会得到异常而不是调试数据。显然,SQL Server 中的超时提前启动并产生了异常。当我减少请求的超时值时,我确实得到了响应。我将研究您的其他建议(发现的问题)并研究 SQL 之外的解决方案! @Roeland 在catch (WebException ex) 中,您在执行任何其他操作之前测试了if (ex.Response != null),它会引发异常吗?你确定代码甚至会进入那个特定的 catch 块吗?您可以在每个catch 块的顶部放置一个throw new Exception("catch 1"?);,以查看正在使用哪个块。郑重声明,我不属于“SQL Server 中的 HTTP 调用纯属邪恶”阵营。他们可以做到,但要非常小心。它们适用于不常见的呼叫,例如维护。您还可以为其使用单独的 Express 实例,通过 Linked Server 与主实例通信。

以上是关于SQL CLR 在 SQL Server 中随机失败的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server 中的 CLR 程序集 C#

CLR SQL Server UDF 问题

基于SQL Server版本的SQL CLR条件编译

在 SQL Server 2012 SSDT 解决方案中引用 CLR 项目

如何在 SQL Server 的 clr 存储过程中执行动态 .net 代码

在 SQL Server CLR 程序集中使用 Microsoft Solver Foundation