web-dev-qa-db-ja.com

$ Partition関数を使用してクエリのパフォーマンスを向上させる

INT列に基づいて分割されたテーブルがあります。

実際のフィールドデータを比較する代わりに、$Partition関数を使用してパーティション番号を比較しているクエリがいくつかあります。

たとえば、代わりに:

select *
from T1 
    inner join T2 on T2.SnapshotKey = T1.SnapshotKey

それらは以下のように書かれています:

select *
from T1 
    inner join T2 on $Partition.PF_Name(T2.SnapshotKey) = $Partition.PF_Name(T1.SnapshotKey)

ここで、PF_Nameはパーティション関数の名前です。

これらのクエリについて、パフォーマンスを向上させるために実行されたコメントが表示されます。両方のクエリを実行すると、実行時間と実行プランが異なります。これら2つのクエリがどのように異なるのかはわかりません。

これは実際のクエリです:

-- this takes about 9 seconds 
select sum(PrinBal)
from fpc
    inner join gl on gl.SnapshotKey = fpc.SnapshotKey
where fpc.SnapshotKey = 201703

-- this takes about 5 seconds
select sum(PrinBal)
from fpc
    inner join gl on $Partition.SnapshotKeyPF(gl.SnapshotKey) = $Partition.SnapshotKeyPF(fpc.SnapshotKey)
where fpc.SnapshotKey = 201703

そして、実際のクエリの実行計画は次のとおりです。

You can see the execution plan here

アップロードはネットワークによって監視されており、ポリシー違反の可能性があるため、サニタイズされた実行プランでもアップロードできません。

質問:なぜ実行プランが異なり、2番目のクエリの方が速いのか。

誰かがこれについてのアイデアを共有できれば幸いです。それらが異なる理由を知りたいだけです。速いほど良いです。

これはSQL Server 2014で発生しています。SQLServer 2012でも同じように実行すると、結果が異なり、最初のクエリの実行が速くなります

7
user71787

実行計画が異なる理由

最初のクエリ

select sum(PrinBal)
from fpc
    inner join gl on gl.SnapshotKey = fpc.SnapshotKey
where fpc.SnapshotKey = 201703

オプティマイザは知っています:

  • gl.SnapshotKey = fpc.SnapshotKey;そして
  • fpc.SnapshotKey = 201703

だからそれは推論することができます:

  • gl.SnapshotKey = 201703

あなたが書いたかのように:

select sum(PrinBal)
from fpc
    inner join gl on gl.SnapshotKey = fpc.SnapshotKey
where fpc.SnapshotKey = 201703
and gl.SnapshotKey = 201703

リテラル値201703は、オプティマイザがパーティションIDを決定するためにも使用できます。両方のSnapshotKey述語(1つが与えられ、1つが推定)を使用すると、オプティマイザは両方のテーブルのパーティションIDを知っています。

さらに進んで、SnapshotKeyのリテラル値(201703)を両方のテーブルで使用できるようになり、結合述語は次のようになります。

  • gl.SnapshotKey = fpc.SnapshotKey

次のように簡素化します。

  • 201703 = 201703;または単に
  • true

結合述語がまったくないことを意味します。結果は、論理的なクロス結合です。利用可能な最も近いT-SQL構文を使用して最終的な実行プランを表現すると、次のようになります。

SELECT
    CASE
        WHEN SUM(Q1.c) = 0 THEN NULL
        ELSE SUM(Q1.s)
    END
FROM 
(
    SELECT c = COUNT_BIG(*), s = SUM(GL.PrinBal)
    FROM dbo.gl AS GL
    WHERE GL.SnapshotKey = 201703
    AND $PARTITION.PF(GL.SnapshotKey) = $PARTITION.PF(201703)
) AS Q1
CROSS JOIN
(
    SELECT Dummy = 1
    FROM dbo.fpc AS FPC
    WHERE FPC.SnapshotKey = 201703
    AND $PARTITION.PF(FPC.SnapshotKey) = $PARTITION.PF(201703)
) AS Q2;

2番目のクエリ

select sum(PrinBal)
from fpc
    inner join gl on $Partition.PF(gl.SnapshotKey) = $Partition.PF(fpc.SnapshotKey)
where fpc.SnapshotKey = 201703

オプティマイザはgl.SnapshotKeyについて何も推論できなくなったため、最初のクエリに対して簡略化および変換を行うことはできなくなりました。

実際、各パーティションが単一のSnapshotKeyのみを保持していることが真実でない限り、書き換えによって同じ結果が得られるとは限りません。

繰り返しになりますが、利用可能な最も近いT-SQL構文を使用して生成された実行プランを表現します。

SELECT
    CASE
        WHEN SUM(Q2.c) = 0 THEN NULL
        ELSE SUM(Q2.s)
    END
FROM 
(
    SELECT
        Q1.PtnID,
        c = COUNT_BIG(*),
        s = SUM(Q1.PrinBal)
    FROM 
    (
        SELECT GL.PrinBal, PtnID = $PARTITION.PF(GL.SnapshotKey)
        FROM dbo.gl AS GL
    ) AS Q1
    GROUP BY
        Q1.PtnID
) AS Q2
CROSS APPLY
(
    SELECT Dummy = 1
    FROM dbo.fpc AS FPC
    WHERE
        $PARTITION.PF(FPC.SnapshotKey) = Q2.PtnID
        AND FPC.SnapshotKey = 201703
) AS Q3;

今回は論理的なクロス結合はありません。代わりに、パーティションIDに相関結合(適用)があります。


2番目のクエリの方が速い理由。

与えられた情報からこれを評価することは困難です。提供されたクエリと計画イメージに基づいてモックデータとテーブルを使用したところ、最初のクエリが2番目のクエリよりも優れていることがわかりました。

オプティマイザが別のポイントから開始し、適切な実行プランを見つける前にオプションを別の順序で探索しただけで、異なる構文を使用して表現された同じクエリが異なる実行プランを生成することがよくあります。計画の検索は網羅的ではなく、考えられるすべての論理変換が利用できるわけではないため、最終結果は異なる可能性があります。上記のように、2つのクエリは必ずしも(少なくともオプティマイザが利用できる情報が与えられていれば)必ずしも同じ要件を表すとは限りません。

別の注意点として、SQL Server 2012(および多少少ないが2014)の初期の列ストア実装には、特に最適化の面で多くの制限があることに注意してください。最新のリリース(理想的には最新)にアップグレードすると、より良い、より一貫した結果が得られる可能性があります。これは、パーティショニングを使用する場合に特に当てはまります。

$PARTITIONを使用して結合を書き換える習慣を身に付けることはお勧めしません。ただし、最後の手段として、自分が何をしているかを深く理解している場合を除きます。

スキーマや計画の詳細を見ることができなくても、私が言えることはこれで全部です。

9
Paul White 9