web-dev-qa-db-ja.com

異常な列の比較とクエリのパフォーマンス

社内データウェアハウスの拡張に取り組んでいるコンサルタントがいます。私はコードレビューを行っていて、すべてのロードプロシージャでこのパターンに遭遇しました。

    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>

Gistは、新しいビジネスキーがある場合に挿入しますが、ビジネスキーが存在し、属性のハッシュが現在の行と一致しない場合は、古い行を更新して新しい行を挿入します(コードの後半)。すべて正常に動作しますが、このコードに到達したときに一時停止しました

AND EXISTS (
            SELECT SOURCE.[HASH]   
            EXCEPT          
            SELECT TARGET.[Hash]
            )

SOURCE。[HASH] <> TARGET。[Hash]に比べて複雑すぎるようです。 EXCEPTは正確なNULL比較を行いますが、私たちの場合、ハッシュがNULLになることはありません(またはより大きな問題があります)。コードを読みやすくして、誰かがコードを保守しなければならないときに混乱しないようにしたいと思います。私はそれについて私たちのコンサルタントに尋ねました、そして彼らは集合演算のためにそれはより速いかもしれないと推測しました、しかし私は簡単なテスト(以下のテストコード)を書くことにしました。

最初に気付いたのは、EXISTS/EXCEPTのクエリプランがより複雑だったということですが、それは必ずしも悪いことではありません

私は選択した各クライアント統計を実行し、<>結合はEXISTS/EXCEPTを使用した場合、合計実行時間を12,000対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)
6
Bob Probst

センチネル値を使用するISNULLは好きではありません。現在またはその後ずっとデータに正当に表示されない値を選択する必要があり、個人的にこれらを含む式を推論するのがより困難であることがわかります。

テストリグについて、クエリを表現する4つの異なる方法を試して、指定された結果を得ました。

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 )

SQL Server実行時間:CPU時間= 30968ミリ秒、経過時間= 8230ミリ秒。

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

SQL Server実行時間:CPU時間= 31594ミリ秒、経過時間= 9230ミリ秒。

SELECT COUNT(1)
FROM   dbo.l
       CROSS JOIN dbo.r
WHERE  EXISTS(SELECT r.hash
              EXCEPT
              SELECT l.HASH)

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) 

SQL Server実行時間:CPU時間= 23812ミリ秒、経過時間= 6760ミリ秒。

したがって、それを基にして、最後のパターンは、パターンに不慣れな人のための ドキュメント化されていないクエリプラン:等価比較 へのリンクを含むコードコメントとともに、明らかに勝者になります。

ただし、このパターンが実際のMERGEクエリでも再現可能かどうかをテストする必要があります。

9
Martin Smith

私はあなたの調査結果に同意します。コンサルタントが作成したEXCEPTパターンはNULL値を処理し、非常に多数の比較列を管理するための非常に優れた方法でもあります。

しかし、列をNULLにすることができず、パフォーマンスが低下する場合は、それを保持する意味がないと思います。

あなたのテストcould以下の点で不正確です:

  • オリジナルはビューをクエリしますが、テストはヒープをクエリします。
  • 元のクエリのデータ量はかなり異なる可能性がありますが、100 000行を試行しています。プランは、結果として得られる推定行数に応じて、異なる方法で生成されます。
  • 元の列はreally varbinary(8000)ですか?ハッシュ値の場合、これは途方もなく広いように思われ、列の幅はメモリの許可、そして最終的にはパフォーマンスに影響します。

このパターンは、BIMLパッケージまたは同様のETLツールによって生成される列比較の一般的なソリューションである可能性があります。

4