在T-SQL中处理来自CLR存储过程的多个结果

我有一些用C#编写的复杂算法作为CLR存储过程。 程序不是确定性的(取决于当前时间)。 程序的结果是两个表。 我没有找到任何解决方案如何处理T-SQL中存储过程的多结果。 这个过程的性能是关键(程序每隔约2秒调用一次)。

我发现更新表的最快方法是:

UPDATE [db-table] SET ... SELECT * FROM [clr-func] 

它比通过ADO.NET从CLR过程更新db-table快得多。

我使用静态字段来存储结果,并在执行clr存储过程后查询它。

调用堆栈是:

 T-SQL proc -> CLR proc (MyStoredProcedure) -> T-SQL proc (UpdateDataFromMyStoredProcedure) -> CLR func (GetFirstResultOfMyStoredProcedure) -> CLR func (GetSecondResultOfMyStoredProcedure) 

问题是,有时CLR函数在静态字段result空,但在CLR过程中result不为null。 我发现,有时CLR函数在另一个AppDomain中被调用而不是CLR过程。 但是,CLR过程仍在运行,可以执行下一个操作,不会抛出任何exception。

有没有办法,如何强制CLR函数在同一个AppDomain中作为“父”CLR程序调用?

或者是否有其他方式,如何实现我的意图?

PS:最初复杂的算法是用T-SQL编写的,但性能很差(比C#中的算法慢约100倍)。

谢谢!

简化代码:

 // T-SQL CREATE PROC [dbo].[UpdateDataFromMyStoredProcedure] AS BEGIN UPDATE [dbo].[tblObject] SET ... SELECT * FROM [dbo].[GetFirstResultOfMyStoredProcedure]() UPDATE [dbo].[tblObjectAction] SET ... SELECT * FROM [dbo].[GetSecondResultOfMyStoredProcedure]() END // ... somewhere else EXEC [dbo].[MyStoredProcedure] 

 // C# public class StoredProcedures { // store for result of "MyStoredProcedure ()" private static MyStoredProcedureResult result; [SqlProcedure] public static int MyStoredProcedure() { result = null; result = ComputeComplexAlgorithm(); UpdateDataFromMyStoredProcedure(); result = null; } [SqlFunction(...)] public static IEnumerable GetFirstResultOfMyStoredProcedure() { return result.First; } [SqlFunction(...)] public static IEnumerable GetSecondResultOfMyStoredProcedure() { return result.Second; } private static void UpdateDataFromMyStoredProcedure() { using(var cnn = new SqlConnection("context connection=true")) { using(var cmd = cnn.CreateCommand()) { cmd.CommandType = System.Data.CommandType.StoredProcedure; cmd.CommandText = "[dbo].[UpdateDataFromMyStoredProcedure]"; cmd.ExecuteNonQuery(); } } } } 

根据Bob Beauchemin的说法“SQLCLR为每个程序集所有者创建一个appdomain,而不是每个数据库创建一个appdomain”你的SQLCLR程序集是否都拥有相同的所有者?

有两种可能性:

  • 更可能的情况与App Domains由于内存压力而被卸载有关。 一般来说,对于特定的程序集(因此其中的代码)只有一个应用程序域,因为应用程序域是每个数据库,每个所有者。 因此,您的代码不会在两个应用程序域中调用, 至少在概念上是这样

    但是,SQL Server如何处理卸载您遇到的App域时,会有一个特定的事件序列细微差别。 发生的事情是您的系统遇到内存压力并且正在标记要卸载的App域。 这可以在SQL Server日志中看到,因为它会告诉您正在卸载的App Domain的确切名称。

    AppDomain 61({database_name}。{owner_name} [runtime] .60)由于内存压力而被标记为卸载。

    当应用程序域标记为卸载时,允许它继续运行,直到所有当前正在运行的进程完成。 此时它具有E_APPDOMAIN_DOOMED的“状态”而不是正常的E_APPDOMAIN_SHARED 。 如果启动了另一个进程, 即使是在注定的App域内 ,也会创建一个新的App Domain。 这就是导致您遇到的行为的原因。 事件的顺序如下(是的,我已经重现了这种行为):

    1. 执行MyStoredProcedure :如果尚未存在,则创建App Domain 1。 App Domain 1“state”是E_APPDOMAIN_SHAREDresult设置为null
      1. result按预期填充
      2. MyStoredProcedure执行GetFirstResultOfMyStoredProcedure :App Domain 1“state”仍然是E_APPDOMAIN_SHAREDresult按预期检索。
      3. App Domain 1标记为卸载:App Domain 1“state”更改为E_APPDOMAIN_DOOMED
      4. MyStoredProcedure执行GetSecondResultOfMyStoredProcedure :App Domain 1“state”仍为E_APPDOMAIN_DOOMED ,因此无法使用。 App Domain 2已创建。 App Domain 2“state”是E_APPDOMAIN_SHAREDresult设置为null 。 这就是为什么你有时什么也得不回来的:这个过程在App Domain 2中(尽管它是从App Domain 1发起的),没有访问App Domain 1。
    2. MyStoredProcedure完成:卸载App Domain 1。

    并且还有另一种可能发生这种事件序列的可能性:在执行GetFirstResultOfMyStoredProcedure之前,可以将App Domain 1标记为卸载。 在这种情况下,在执行GetFirstResultOfMyStoredProcedure时创建App Domain 2,并且它和GetSecondResultOfMyStoredProcedure都在App Domain 2中运行并且不返回任何内容。

    因此,如果您希望/需要在这些条件下抛出错误,那么您的Get*ResultOfMyStoredProcedure方法需要在尝试检索之前检查result == null ,如果它为null,则抛出错误。 或者,如果可以重新计算静态变量中存储的值的值,那么如果它为null只需重新填充它(例如再次调用ComputeComplexAlgorithm )。

  • 一种不太可能的可能性是,由于App Domain由该代码的所有会话/呼叫者共享,如果您没有另外确保一次只执行此过程,则可能是其他人或者SQL Agent作业或其他东西,执行MyStoredProcedure ,它会在静态变量启动时将其MyStoredProcedure

    由于您已经接受使用UNSAFE程序集以获取可更新的静态变量,因此您可以添加一个锁定机制以确保MyStoredProcedure是单线程的。


除了要查看的那些方面之外,这个过程很可能会以更快的方式完成,而且不那么复杂。 您可以使用表值参数(TVP)将数据流式传输回SQL Server,就像使用应用程序代码一样。 只需创建一个或两个用户定义的表类型(UDTT),它们与TVF返回的两个结果集的结构相匹配( GetFirstResultOfMyStoredProcedureGetSecondResultOfMyStoredProcedure )。 请在此处查看我的答案,了解如何正确地汇总结果。 通过使用此模型,您可以:

  • MyStoredProcedure CLR过程中进行内联更新
  • 摆脱静态变量
  • 可能不再需要UNSAFE (如果它只用于静态变量)。 如果无法通过Context Connection传回结果,则可能仍需要EXTERNAL_ACCESS ,在这种情况下,您将使用常规连接(即连接字符串使用“Server =(local)”或未指定“Server”) 。
  • 摆脱UpdateDataFromMyStoredProcedure方法
  • 摆脱UpdateDataFromMyStoredProcedure T-SQL proc
  • 摆脱GetFirstResultOfMyStoredProcedure CLRfunction
  • 摆脱GetSecondResultOfMyStoredProcedure CLRfunction
  • 释放该静态变量当前正用于保存两个结果集的所有内存!

这种方法不仅更容易维护而且更有可能更快,它也不允许新的App Domain带有未初始化的静态变量问题,你在这里遇到:-)。