C# 卸载SQLCLR程序集时速度要快得多

C# 卸载SQLCLR程序集时速度要快得多,c#,sql-server,sqlclr,litjson,azure-managed-database,C#,Sql Server,Sqlclr,Litjson,Azure Managed Database,我有一个SQLCLR程序集,它在SQLAzure托管实例上使用LitJson包执行简单的JSON反序列化。这个CLR是从一个表值函数调用的,该函数只将JSON属性作为表返回(理论上比T-SQL中内置的JSON处理速度更快) 奇怪的是,程序集在卸载时(即当它没有显示在sys.dm_clr_loaded_assemblies中时)比加载时运行得快得多。对于某些颜色,卸载时我可以在约200ms内反序列化1000条记录,而加载程序集时同样的1000条记录需要约7秒 我有一个解决办法,就是在查询开始时,我

我有一个SQLCLR程序集,它在SQLAzure托管实例上使用LitJson包执行简单的JSON反序列化。这个CLR是从一个表值函数调用的,该函数只将JSON属性作为表返回(理论上比T-SQL中内置的JSON处理速度更快)

奇怪的是,程序集在卸载时(即当它没有显示在
sys.dm_clr_loaded_assemblies
中时)比加载时运行得快得多。对于某些颜色,卸载时我可以在约200ms内反序列化1000条记录,而加载程序集时同样的1000条记录需要约7秒

我有一个解决办法,就是在查询开始时,我将
权限设置
不安全
来回切换到
外部访问
,这会强制卸载程序集,但这感觉像是一次黑客攻击。程序集的加载速度应快于卸载速度

如果您有任何想法,我们将不胜感激。下面是代码的草图——根本没有什么奇怪的事情发生

[SqlFunction(FillRowMethodName = "FillRowMessageParser", IsDeterministic = true)]
    public static IEnumerable ParseRows(string MsgText)
    {
        DatabaseRow[] myRows;
        //LitJson doing its work here 
        myRows= JsonMapper.ToObject<DatabaseRow[]>(MsgText);

        return myRows;
    }

    public static FillRowMessageParser(object obj, out SqlChars Field1, out SqlChars Field2, [bunch more out fields here])
    {
         var myRow = (DatabaseRow)obj;

         //Set a bunch of fields to the out variables here
         Field1 = new SqlChars(myRow.Property1);
         //whole bunch more here

         //loop through some nested properties of the myRow class
         foreach (var x in myRow.Object1)
         {
              switch(x.Name)
              {
                 case "1": Field2 = new SqlChars(x.Value); break;
                 //whole bunch more here
              }
         }
    }

更新


问题似乎与LitJson包本身有关。最后,我们尝试将JsonFx作为另一个不需要任何不受支持的SQL Server.NET库的包(建议使用@SolomonRudzky),但无论出于何种原因,该包在反序列化中的性能(这就是我们练习的内容)不如本机的t-SQL JSON处理(至少对于我们的数据集而言)。因此,我们最终离开SQLCLR,回到T-SQL来完成这个过程。T-SQL中的性能仍然不如卸载的LitJson包好,但它足以满足我们的需要,并且避免了在每次调用CLR时卸载程序集的太多不可靠的解决方法

虽然由于没有时间全面检查代码,我目前无法提供一个明确的答案,但我确实对代码进行了简要的检查,并猜测这种奇怪的行为是在处理过程中使用静态类变量(主要是集合)缓存值的结果。从第一次执行到后续运行,除了:

  • 程序集在第一次运行时尚未加载到内存中
  • 静态类变量已初始化,但在执行期间可能不包含任何值(除非初始化加载的数据只是在所有执行时读取的数据)
  • 执行这些操作通常会提高性能,但在SQLCLR中执行这些操作时会有细微差别:SQLServer中的AppDomain在会话之间共享。这意味着共享资源不是线程安全的。这就是为什么通常不允许使用可写静态类变量的原因(即,在将程序集标记为“不安全的”)之外(您将得到一个错误,指出它们需要标记为“只读”)。然而,在这个特殊的案例中,我看到这条规则有两个缺陷:

  • 2015年所做的更改(并在整整2年后合并)似乎表明希望让LitJSON在标记为
    SAFE
    的程序集中工作,因此所有静态类变量都标记为
    readonly
    ,并进行了其他更改以适应这一点。您可以在此处看到这些更改: 不幸的是,即使进行了这些更改,即使它在
    安全程序集中“工作”,变量仍然是静态的,因此仍然是共享的。出于某些技术原因,允许从只读集合中添加/删除项,因此在实际应用中,它们不是真正的只读。这肯定会导致意外的“奇怪”行为
  • 如果上述更改的目的是允许程序集在标记为
    安全
    时工作,那么显然,自4.5年前基于SQLCLR的提交以来,发生了一些变化,因为现在不将其标记为
    不安全
    会导致以下错误(根据OP): 受保护的资源(仅在完全信任的情况下可用)是:所有需要的资源是:同步、外部线程 因此,当前代码需要标记为
    不安全
    ,在这种情况下,将静态类变量标记为
    只读
    的任何更改都是不必要的;-)

  • 无论如何,我认为这段代码不是线程安全的。事实上,通过执行多个执行,您可能会看到“奇怪”的行为,每个执行都使用不同的JSON文档,该文档的结构不同(至少元素数量不同)


    同样,这不是决定性的,但更有可能。在这种情况下,我猜第一次执行的出色性能是由于代码所做的事情在生产环境中实际不起作用。当然,您确实有一个硬编码的结构(输出行模式被编译到代码中),因此我认为这消除了传入不同结构的情况,但目前还不清楚如果两个会话在同一毫秒内使用不同的JSON文档执行此操作会产生什么影响。

    虽然由于没有时间全面查看代码,我目前无法提供明确的答案,我确实简单地看了一下,我猜想这种奇怪的行为是在处理过程中使用静态类变量(主要是集合)缓存值的结果。从第一次执行到后续运行,除了:

  • 程序集在第一次运行时尚未加载到内存中
  • 静态类变量已初始化,但在执行期间可能不包含任何值(除非初始化加载的数据只是在所有执行时读取的数据)
  • 执行这些操作通常会提高性能,但在SQLCLR中执行这些操作时会有细微差别:SQLServer中的AppDomain在会话之间共享。这意味着共享资源不是线程安全的。这就是为什么通常(即在将组件标记为
    不安全的范围之外
    DECLARE @JSON NVARCHAR(MAX) = 
    (
    SELECT 
        TOP 1000
            MessageID,
            JSON_QUERY(MessageText) AS MessageText
    FROM MyTable
    ORDER BY 1 ASC
    FOR JSON AUTO
    )
    
    DECLARE @Start DATETIME2
    DECLARE @End DATETIME2
    
    SET @Start = SYSDATETIME()
    
    SELECT *
    FROM MyCLRTableValuedFunction(@JSON)
    
    SET @End = SYSDATETIME()
    
    SELECT DATEDIFF(MILLISECOND,@Start, @End) --Time CLR takes to process