异常的列比较和查询性能

Bob*_*bst 6 performance sql-server sql-server-2016 except query-performance

我们有一些顾问致力于扩展内部数据仓库。我正在做代码审查并在所有加载过程中遇到这种模式:

    MERGE [EDHub].[Customer].[Class] AS TARGET
    USING (
        SELECT <columns>
        FROM [dbo].[vw_CustomerClass]
            WHERE JHAPostingDate = @PostingDate   
        ) AS SOURCE
        ON  TARGET.BankId = SOURCE.BankId       -- This join is on the business keys
            AND TARGET.Code = SOURCE.Code
    WHEN NOT MATCHED BY TARGET  
        THEN
            <INSERT Statement>
    WHEN MATCHED
        AND TARGET.IsLatest = 1
        AND EXISTS (
            SELECT SOURCE.[HASH]   
            EXCEPT          
            SELECT TARGET.[Hash]
            )
        THEN 
            <UPDATE Statement>
Run Code Online (Sandbox Code Playgroud)

要点是,如果我们有一个新的业务键,则插入,但如果业务键存在并且属性的散列与我们的当前行不匹配,则更新旧行并插入一个新行(稍后在代码中)。一切正常,但是当我看到这段代码时我暂停了

AND EXISTS (
            SELECT SOURCE.[HASH]   
            EXCEPT          
            SELECT TARGET.[Hash]
            )
Run Code Online (Sandbox Code Playgroud)

与 SOURCE.[HASH] <> TARGET.[Hash] 相比,它似乎过于复杂。EXCEPT 将进行准确的 NULL 比较,但在我们的情况下,哈希值永远不会为 NULL(或者我们有更大的问题)。我希望我们的代码易于阅读,这样当有人必须维护它时,它不会混淆。我向我们的顾问询问了它,他们推测它可能会因为集合操作而更快,但我决定编写一个简单的测试(下面的测试代码)。

我注意到的第一件事是 EXISTS/EXCEPT 有一个更复杂的查询计划,但这并不总是很糟糕

我运行了每个选择的客户端统计信息,<> 连接产生了 12,000 与 EXISTS/EXCEPT 的 25,000 的总执行时间。我想将此提交给我们的顾问,并要求重构该语句,但希望在此处获得反馈:

  1. 这是一个很好的测试吗?- 我错过了什么吗?
  2. 是否存在 EXISTS/EXCEPT 是更好比较的情况?

测试脚本:

CREATE TABLE r (hash VARBINARY(8000))
CREATE TABLE l (hash VARBINARY(8000))

SET NOCOUNT ON
DECLARE @x INT = 10000
WHILE @x <> 0 BEGIN

   INSERT INTO dbo.r ( hash ) SELECT HASHBYTES('SHA2_256',CAST(NEWID() AS VARCHAR(200)))
   INSERT INTO dbo.l ( hash ) SELECT HASHBYTES('SHA2_256',CAST(NEWID() AS VARCHAR(200)))    

   SET @x = @x-1
END

INSERT INTO dbo.r ( hash ) VALUES ( NULL  )
INSERT INTO dbo.l ( hash ) VALUES ( NULL  )

SELECT COUNT(1) 
FROM dbo.l
CROSS JOIN dbo.r 
WHERE ISNULL(r.hash,0) <> ISNULL(l.hash,0)

SELECT COUNT(1) 
FROM dbo.l
CROSS JOIN dbo.r 
WHERE EXISTS(SELECT r.hash except select l.HASH)
Run Code Online (Sandbox Code Playgroud)

Mar*_*ith 9

我不喜欢ISNULL哨兵值,它需要选择现在或以后永远不会合法出现在数据中的值,我个人发现包含这些的表达式更难以推理。

对于您的测试设备,我尝试了四种不同的方式来表达查询并得到了所述的结果。

SELECT COUNT(1)
FROM   dbo.l
       CROSS JOIN dbo.r
WHERE  r.hash <> l.hash
        OR ( r.hash IS NULL
             AND l.hash IS NOT NULL )
        OR ( l.hash IS NULL
             AND r.hash IS NOT NULL )
Run Code Online (Sandbox Code Playgroud)

SQL Server 执行时间:CPU 时间 = 30968 毫秒,已用时间 = 8230 毫秒。

SELECT COUNT(1)
FROM   dbo.l
       CROSS JOIN dbo.r
WHERE  ISNULL(r.hash, 0) <> ISNULL(l.hash, 0)
Run Code Online (Sandbox Code Playgroud)

SQL Server 执行时间:CPU 时间 = 31594 毫秒,已用时间 = 9230 毫秒。

SELECT COUNT(1)
FROM   dbo.l
       CROSS JOIN dbo.r
WHERE  EXISTS(SELECT r.hash
              EXCEPT
              SELECT l.HASH)
Run Code Online (Sandbox Code Playgroud)

SQL Server 执行时间:CPU 时间 = 46531 毫秒,已用时间 = 13191 毫秒。

SELECT COUNT(1)
FROM   dbo.l
       CROSS JOIN dbo.r
WHERE  NOT EXISTS(SELECT r.hash
                  INTERSECT
                  SELECT l.HASH) 
Run Code Online (Sandbox Code Playgroud)

SQL Server 执行时间:CPU 时间 = 23812 毫秒,已用时间 = 6760 毫秒。

因此,在此基础上,最后一个将是明显的赢家 - 以及带有指向未记录查询计划的链接的代码注释对于不熟悉该模式的任何人的平等比较

但是您也应该测试这种模式是否可以在您的实际MERGE查询中重现。