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
そして、実際のクエリの実行計画は次のとおりです。
アップロードはネットワークによって監視されており、ポリシー違反の可能性があるため、サニタイズされた実行プランでもアップロードできません。
質問:なぜ実行プランが異なり、2番目のクエリの方が速いのか。
誰かがこれについてのアイデアを共有できれば幸いです。それらが異なる理由を知りたいだけです。速いほど良いです。
これはSQL Server 2014で発生しています。SQLServer 2012でも同じように実行すると、結果が異なり、最初のクエリの実行が速くなります
実行計画が異なる理由
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;
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
を使用して結合を書き換える習慣を身に付けることはお勧めしません。ただし、最後の手段として、自分が何をしているかを深く理解している場合を除きます。
スキーマや計画の詳細を見ることができなくても、私が言えることはこれで全部です。