首页 > 解决方案 > 隐式类型转换:JSON_VALUE() vs TRY_CAST(JSON_VALUE())

问题描述

我想创建一个通用链接表来链接我们拥有的记录,以及另一个数据库的记录。我可以为源数据库中的每个表创建一个链接表。但是,由于现在有支持,JSON我认为这可能是一个很好的用例来调查这种支持。

因此,我没有创建多个名为的表,而是Link{SourceTableName}创建了一个表,其中一个GUID代表我们这边的 Id,一个NVARCHAR(MAX)列代表JSON包含源表的主键。我保存主键是JSON因为我必须支持复合主键。如果我想要源表中的数据,我可以JOINJSON列与JSON_VALUE源表中的主键一起使用。

由于JSON_VALUE返回 an当源表的主键不是预期NVARCHAR(MAX)的时,我将获得隐式类型转换。NVARCHAR(MAX)但是,如果我将其包装JSON_VALUETRY_CAST不再需要隐式转换。我决定这样做,但我很好奇是否由于额外的函数调用而导致性能损失。这就是事情变得奇怪的地方......

下面是SELECT *源表上的 a simple 与 aSELECT * joined on TRY_CAST(JSON_VALUE())与 a的比较SELECT * joined on JSON_VALUE()

在此处输入图像描述 在此处输入图像描述 在此处输入图像描述

在此处输入图像描述

我创建了一个重现该问题的示例查询。

CREATE TABLE [#Link] (
    [Id] UNIQUEIDENTIFIER NOT NULL CONSTRAINT [DF_Link_Id] DEFAULT(NEWID()),
    [LinkId] NVARCHAR(MAX) NOT NULL,
 CONSTRAINT [PK_Link] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 100) ON [PRIMARY])
ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

CREATE TABLE [#Customer] (
    [Id] CHAR(7) NOT NULL,
    [Name] NVARCHAR(200) NULL,
 CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 100) ON [PRIMARY])
ON [PRIMARY]

DECLARE @i INT = 0
WHILE @i < 20
BEGIN
    WITH seed AS (
        SELECT (@i * 32767) + 1 AS n
        UNION ALL
        SELECT n+1 FROM seed WHERE n+1<=(@i * 32767) + 32767
    )
    INSERT INTO
        [#Customer]
    SELECT
        RIGHT('0000000'+CAST([n] AS VARCHAR(7)),7)
        ,RIGHT('0000000'+CAST([n] AS VARCHAR(7)),7)
    FROM
        seed OPTION(maxrecursion 32767)
    SET @i = @i + 1
END

INSERT INTO
    [#Link] ([LinkId])
SELECT
    [value] FROM OPENJSON((SELECT [Id] FROM [#Customer] FOR JSON PATH))


SELECT TOP 10 *
FROM [#Customer]

SELECT TOP 10 *
FROM [#Customer]
JOIN [#Link] ON [#Customer].[Id] = TRY_CAST(JSON_VALUE([#Link].[LinkId], '$.Id') AS char(7))

SELECT TOP 10 *
FROM [#Customer]
JOIN [#Link] ON [#Customer].[Id] = JSON_VALUE([#Link].[LinkId], '$.Id')

SELECT TOP 100 *
FROM [#Customer]

SELECT TOP 100 *
FROM [#Customer]
JOIN [#Link] ON [#Customer].[Id] = TRY_CAST(JSON_VALUE([#Link].[LinkId], '$.Id') AS char(7))

SELECT TOP 100 *
FROM [#Customer]
JOIN [#Link] ON [#Customer].[Id] = JSON_VALUE([#Link].[LinkId], '$.Id')

DROP TABLE [#Customer]
DROP TABLE [#Link]

在此处输入图像描述 在此处输入图像描述

如您所见,第一个JSON_VALUE()查询和第二个JSON_VALUE()查询的执行计划之间存在差异。我不是破译执行计划的英雄,但有趣的是存在差异这一事实。

存在一个警告,抱怨两个执行计划中都存在隐式类型转换。这一定是导致执行计划不同的原因,因为当将数据类型更改[#Customer].[Id]为 NVARCHAR(7) 时,TOP 10 和 TOP 100 的执行计划保持不变。

我知道 CONVERT_IMPLICIT 是性能杀手,但我没想到会看到 TOP 10 和 TOP 100 之间的差异。由于第二个查询比第一个查询快,显然第一个查询也有可能同样快。为什么查询优化器不总是选择第二个执行计划,或者我要求太多了?

标签: sql-servertsql

解决方案


推荐阅读