1

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

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

我有一个解决方法,即在我的查询开始时,我PERMISSION_SET来回UNSAFE切换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 时卸载程序集的太多不稳定的解决方法。

4

1 回答 1

0

虽然由于没有时间全面审查LitJSON代码,我目前无法提供明确的答案,但我确实简要地查看了它,并猜测这种奇怪的行为是使用静态类变量(主要是集合)缓存值的结果在加工过程中。我想不出任何其他与第一次执行到后续运行不同的地方:

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

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

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

    受保护的资源(仅在完全信任的情况下可用)是:所有所需的资源是:同步、外部线程因此,当前代码需要标记为UNSAFE,在这种情况下,不需要进行任何更改以将静态类变量标记为readonly必要的; -)。

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

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

于 2020-06-04T19:19:39.900 回答