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 2012 SSDT 解决方案中引用 CLR 项目