简体   繁体   English

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

[英]Processing multiple results from CLR stored procedure in T-SQL

I have some complex algorithm written in C# as CLR Stored procedure.我有一些用 C# 作为 CLR 存储过程编写的复杂算法。 Procedure is not deterministic (it depends on current time).程序不是确定性的(取决于当前时间)。 The result of procedure are two tables.过程的结果是两个表。 I didn't found any solution how to process multi-results from stored procedures in T-SQL.我没有找到任何解决方案如何处理 T-SQL 中存储过程的多结果。 The performance of this procedure is key (procedure is called every ~2 seconds).此过程的性能是关键(每约 2 秒调用一次该过程)。

I found the fastest way how to update tables is:我发现如何更新表的最快方法是:

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

It's much faster then update db-table from CLR procedure via ADO.NET.它比通过 ADO.NET 从 CLR 过程更新 db-table 要快得多。

I used static field to store results and query it after the execution of clr stored procedure.我使用 static 字段来存储结果并在执行 clr 存储过程后查询它。

The call stack is:调用栈是:

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

The problem is, sometimes CLR functions has null in static field result , but in CLR procedure is result not null.问题是,有时 CLR 函数在 static 字段result中有 null,但在 CLR 过程中result不是 null。 I found, sometimes the CLR functions are called in another AppDomain than CLR procedure.我发现,有时 CLR 函数在另一个 AppDomain 中调用,而不是 CLR 过程。 However CLR procedure is still running and can do next operations and no exception is thrown.但是 CLR 程序仍在运行,可以进行下一步操作,并且没有抛出异常。

Is there some way, how to force CLR functions to be called in same AppDomain as "parent" CLR procedure?有什么办法,如何强制在与“父”CLR 过程相同的 AppDomain 中调用 CLR 函数?

Or is there some another way, how to achieve my intention?还是有其他方法,如何实现我的意图?

PS: Originally the complex algorithm was written in T-SQL, but performance was poor (~100x slower than algorithm in C#). PS:最初复杂的算法是用 T-SQL 编写的,但性能很差(比 C# 中的算法慢约 100 倍)。

Thanks!谢谢!

Simplified code:简化代码:

// 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();
            }
        }
    }
}

There are two possibilities:有两种可能:

  • The more likely scenario has to do with App Domains getting unloaded due to memory pressure.更有可能的情况与由于 memory 压力而卸载应用程序域有关。 Generally speaking, there is only a single App Domain for a particular Assembly (and hence for the code in it) since App Domains are per-Database, per-Owner.一般来说,特定程序集(以及其中的代码)只有一个应用程序域,因为应用程序域是每个数据库、每个所有者的。 So your code is not being called in two App Domains, at least not conceptually .因此,您的代码没有在两个应用程序域中被调用,至少在概念上没有

    However, there is a particular sequence-of-events nuance to how SQL Server handles unloading App Domains that you are experiencing.但是,对于 SQL 服务器如何处理卸载您正在经历的应用程序域,存在特定的事件序列细微差别。 What is happening is that your system is experiencing memory pressure and is marking the App Domain to be unloaded.正在发生的事情是您的系统正在经历 memory 压力并且正在标记要卸载的应用程序域。 This can be seen in the SQL Server logs as it will tell you the exact name of the App Domain that is being unloaded.这可以在 SQL 服务器日志中看到,因为它会告诉您正在卸载的应用程序域的确切名称。

    AppDomain 61 ({database_name}.{owner_name}[runtime].60) is marked for unload due to memory pressure.由于 memory 压力,AppDomain 61 ({database_name}.{owner_name}[runtime].60) 被标记为卸载。

    When an App Domain is marked for unload, it is allowed to continue running until all currently running process complete.当应用程序域被标记为卸载时,它被允许继续运行,直到所有当前正在运行的进程完成。 At this point it has a "state" of E_APPDOMAIN_DOOMED instead of the normal E_APPDOMAIN_SHARED .此时它的“状态”为E_APPDOMAIN_DOOMED而不是正常的E_APPDOMAIN_SHARED If another process is started, even from within the doomed App Domain , a new App Domain is created.如果启动另一个进程,即使是在注定失败的 App Domain中,也会创建一个新的 App Domain。 And this is what causes the behavior you are experiencing.这就是导致您正在经历的行为的原因。 The sequence of events is as follows (and yes, I have reproduced this behavior):事件的顺序如下(是的,我已经重现了这种行为):

    1. Execute MyStoredProcedure : App Domain 1 is created if not already existing.执行MyStoredProcedure :如果 App Domain 1 尚不存在,则创建该应用程序域。 App Domain 1 "state" is E_APPDOMAIN_SHARED .应用程序域 1“状态”是E_APPDOMAIN_SHARED result is set to null . result设置为null
      1. result is populated as expected result按预期填充
      2. MyStoredProcedure executes GetFirstResultOfMyStoredProcedure : App Domain 1 "state" is still E_APPDOMAIN_SHARED . MyStoredProcedure执行GetFirstResultOfMyStoredProcedure :App Domain 1“状态”仍然是E_APPDOMAIN_SHARED result is retrieved as expected. result按预期检索。
      3. App Domain 1 is marked for unload: App Domain 1 "state" is changed to E_APPDOMAIN_DOOMED应用程序域 1 标记为卸载:应用程序域 1“状态”更改为E_APPDOMAIN_DOOMED
      4. MyStoredProcedure executes GetSecondResultOfMyStoredProcedure : App Domain 1 "state" is still E_APPDOMAIN_DOOMED and so cannot be used. MyStoredProcedure执行GetSecondResultOfMyStoredProcedure :App Domain 1“状态”仍为E_APPDOMAIN_DOOMED ,因此无法使用。 App Domain 2 is created.应用域 2 已创建。 App Domain 2 "state" is E_APPDOMAIN_SHARED .应用程序域 2“状态”是E_APPDOMAIN_SHARED result is set to null . result设置为null This is why you sometimes get nothing back: this process is in App Domain 2 (even though it was initiated from App Domain 1), with no access to App Domain 1.这就是为什么你有时什么都得不到的原因:这个过程在 App Domain 2 中(即使它是从 App Domain 1 启动的),无法访问 App Domain 1。
    2. MyStoredProcedure completes: App Domain 1 is unloaded. MyStoredProcedure完成:App Domain 1 已卸载。


    And there is another possibility of how this sequence of events could occur: App Domain 1 could be marked for unload prior to executing GetFirstResultOfMyStoredProcedure .还有另一种可能是如何发生这一系列事件:在执行GetFirstResultOfMyStoredProcedure之前,可以将 App Domain 1 标记为卸载。 In this case, App Domain 2 is created when GetFirstResultOfMyStoredProcedure is executed, and both it and GetSecondResultOfMyStoredProcedure run in App Domain 2 and return nothing.在这种情况下,执行GetFirstResultOfMyStoredProcedure时会创建 App Domain 2,并且它和GetSecondResultOfMyStoredProcedure都在 App Domain 2 中运行并且不返回任何内容。

    Hence, if you want / need an error to be thrown under these conditions, then your Get*ResultOfMyStoredProcedure methods need to check if result == null before attempting to retrieve, and if it is null, then throw an error.因此,如果您希望/需要在这些条件下抛出错误,那么您的Get*ResultOfMyStoredProcedure方法需要在尝试检索之前检查result == null ,如果是 null,则抛出错误。 OR, if it is possible to recalculate the value of what is being stored in the static variable, then if it is null simply repopulate it (eg call ComputeComplexAlgorithm again).或者,如果可以重新计算存储在 static 变量中的值,那么如果它是null只需重新填充它(例如再次调用ComputeComplexAlgorithm )。

  • A less likely possibility is that since the App Domain is shared by all sessions / callers to that code, it is possible , if you have not otherwise ensured that there is only ever 1 execution of this process at a time, that someone else or a SQL Agent job or something, executed MyStoredProcedure which would null out the static variable as it starts.不太可能的可能性是,由于应用程序域由该代码的所有会话/调用者共享,如果您没有以其他方式确保一次只有 1 次执行此过程,则其他人或SQL 代理作业或其他东西,执行MyStoredProcedure ,它将 null 在 static 变量启动时取出。

    Since you have already accepted using an UNSAFE Assembly in order to get the updateable static variable, you might as well add a locking mechanism to ensure that MyStoredProcedure is single-threaded.由于您已经接受使用UNSAFE程序集来获取可更新的 static 变量,您不妨添加一个锁定机制来确保MyStoredProcedure是单线程的。


Aside from those areas to look at, this process could most likely be done even faster and in a less convoluted manner.除了这些领域之外,这个过程很可能会以一种不那么复杂的方式更快地完成。 You can use Table-Valued Parameters (TVPs) to stream data back to SQL Server, just like you would from app code.您可以使用表值参数 (TVP) 将 stream 数据返回到 SQL 服务器,就像使用应用程序代码一样。 Just create one or two User-Defined Table Types (UDTTs) that match the structures of the two result sets being returned by the TVFs ( GetFirstResultOfMyStoredProcedure and GetSecondResultOfMyStoredProcedure ).只需创建一个或两个用户定义的表类型 (UDTT),它们与 TVF 返回的两个结果集的结构( GetFirstResultOfMyStoredProcedureGetSecondResultOfMyStoredProcedure )相匹配。 Please see my answer here regarding how to properly stream in the results.在此处查看我的回答,了解如何在结果中正确设置 stream。 By using this model, you can:通过使用此 model,您可以:

  • do both updates inline in the MyStoredProcedure CLR procMyStoredProcedure CLR proc 中进行内联更新
  • get rid of the static variable摆脱 static 变量
  • Possibly no more need for UNSAFE (if it was only being used for the static variable).可能不再需要UNSAFE (如果它仅用于 static 变量)。 You might still need EXTERNAL_ACCESS if you can't pass back the results over the Context Connection, in which case you would use a regular connection (ie connection string uses either "Server=(local)" or doesn't specify "Server").如果您无法通过上下文连接传回结果,您可能仍需要EXTERNAL_ACCESS ,在这种情况下,您将使用常规连接(即连接字符串使用“Server=(local)”或未指定“Server”) .
  • get rid of UpdateDataFromMyStoredProcedure method摆脱UpdateDataFromMyStoredProcedure方法
  • get rid of UpdateDataFromMyStoredProcedure T-SQL proc摆脱UpdateDataFromMyStoredProcedure T-SQL proc
  • get rid of GetFirstResultOfMyStoredProcedure CLR function摆脱GetFirstResultOfMyStoredProcedure CLR function
  • get rid of GetSecondResultOfMyStoredProcedure CLR function摆脱GetSecondResultOfMyStoredProcedure CLR function
  • free up all of the memory that is currently being used by that static variable to hold two result sets!!释放 static 变量当前使用的所有 memory 以保存两个结果集!

Not only is this approach easier to maintain and most likely faster, it also does not allow for the new App Domain with uninitialized static variable issue that you are running into here:-).这种方法不仅更易于维护并且很可能更快,而且它还不允许您在此处遇到的具有未初始化 static 变量问题的新应用程序域:-)。

According to Bob Beauchemin "SQLCLR creates one appdomain per assembly owner, not one appdomain per database" Do both your SQLCLR Assemblies have the same owner?根据Bob Beauchemin的说法,“SQLCLR 为每个程序集所有者创建一个 appdomain,而不是每个数据库一个 appdomain” 您的两个 SQLCLR 程序集是否具有相同的所有者?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM