卸载时 SQLCLR 程序集快得多

SQLCLR Assembly Much Faster When Unloaded

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

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

我有一个解决方法,即在查询开始时,我将 PERMISSION_SETUNSAFE 来回切换到 EXTERNAL_ACCESS,这会强制卸载程序集,但这感觉像是黑客攻击。程序集应该加载比卸载快。

如有任何想法,我们将不胜感激。代码在下面勾画出来——那里没有什么特别的。

[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
              }
         }
    }

SQL 组件看起来像这样:


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



更新

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

虽然由于没有时间完整查看 LitJSON 代码,我目前无法提供明确的答案,但我确实简单地查看了一下,并猜测这种奇怪的行为是使用 static 的结果class 变量(主要是集合)在处理过程中缓存值。我想不出与第一次执行到后续 运行s 之外的任何其他内容:

  1. 第一次 运行
  2. 程序集尚未加载到内存中
  3. 静态 class 变量已初始化,但在执行期间可能不包含任何值(除非初始化加载的数据在所有执行中都被简单地读取)

做这样的事情通常会提高性能,但是在 SQLCLR 中做这样的事情时会有细微差别:SQL 服务器中的 AppDomains 在会话之间共享。这意味着共享资源 不是 线程安全的。这就是为什么通常(即在将程序集标记为 UNSAFE 之外)您不允许使用可写静态 class 变量(您会收到一条错误消息,指出它们需要被标记为 readonly ).但是,在这种特殊情况下,我看到这条规则有两个细分:

  1. 2015 年所做的更改(并在整整 2 年后合并)似乎表明希望让 LitJSON 在标记为 SAFE 的程序集中工作,因此所有静态 class 变量被标记为 readonly,并进行了额外的更改以适应这一点。你可以在这里看到这些变化: https://github.com/LitJSON/litjson/commit/1a120dff7e9b677633bc568322fa065c9dfe4bb8 不幸的是,即使进行了这些更改,即使它在 SAFE 程序集中做了 "work",变量仍然是静态的,因此仍然是共享的。由于某些技术原因,允许 add/remove 只读集合中的项目,因此在实际层面上,它们并非 真正 只读。这肯定会导致意外的 "odd" 行为。
  2. 如果上述更改的目的是允许程序集在被标记为 SAFE 时工作,那么显然自 4.5 年前基于 SQLCLR 的提交以来已经发生了一些变化不将其标记为 UNSAFE 现在会导致以下错误(根据 OP):

    The protected resources (only available with full trust) were: All The demanded resources were: Synchronization, ExternalThreading So, currently the code requires being marked as UNSAFE, in which case, none of the changes made to mark the static class variables as readonly were necessary ;-).

无论如何,我不认为这段代码是线程安全的。事实上,您 可能 能够通过执行多次执行来查看 "odd" 行为,每次执行都有不同的 JSON 结构不同的文档(至少元素)。

同样,这不是确定的,但很有可能。在这种情况下,我猜测第一次执行的出色性能是由于代码执行的操作实际上在生产环境中不起作用。当然,你确实有一个硬编码结构(输出行模式被编译到代码中)所以我想这消除了传递不同结构的情况,但仍然不清楚如果两个会话执行这个会产生什么影响在完全相同的毫秒内处理不同的 JSON 文档。