SQL Serverがこのような単純なケースで誤った推定を行うのはなぜでしょうか。シナリオがあります。
CREATE PARTITION FUNCTION PF_Test (int) AS RANGE RIGHT
FOR VALUES (20140801, 20140802, 20140803)
CREATE PARTITION SCHEME PS_Test AS PARTITION PF_Test ALL TO ([Primary])
CREATE TABLE A
(
DateKey int not null,
Type int not null,
constraint PK_A primary key (DateKey, Type) on PS_Test(DateKey)
)
INSERT INTO A (DateKey, Type)
SELECT
DateKey = N1.n + 20140801,
Type = N2.n + 1
FROM dbo.Numbers N1
cross join dbo.Numbers N2
WHERE N1.n BETWEEN 0 AND 2
and N2.n BETWEEN 0 AND 10000 - 1
UPDATE STATISTICS A (PK_A) WITH FULLSCAN, INCREMENTAL = ON
CREATE TABLE B
(
DateKey int not null,
SubType int not null,
Type int not null,
constraint PK_B primary key (DateKey, SubType) on PS_Test(DateKey)
)
INSERT INTO B (DateKey, SubType, Type)
SELECT
DateKey,
SubType = Type * 10000 + N.n,
Type
FROM A
cross join dbo.Numbers N
WHERE N.n BETWEEN 1 AND 10
UPDATE STATISTICS B (PK_B) WITH FULLSCAN, INCREMENTAL = ON
したがって、セットアップはかなり単純で、統計情報が用意されており、SQL Serverは1つのテーブルに対してクエリを実行したときに正しい推定を生成できます。
select COUNT(*) from A where DateKey = 20140802
--10000
select COUNT(*) from B where DateKey = 20140802
--100000
しかし、この単純な選択では見積もりがかなりずれているため、理由はわかりません。
SELECT a.DateKey, a.Type
FROM A
JOIN B
ON b.DateKey = a.DateKey
AND b.Type = a.Type
WHERE a.DateKey = 20140802
Clustered Index Seekの推定値は、実際の推定値の57%です。実際のクエリはさらに悪く、見積もりは実際の2%です。
追伸設定を再現するための番号表
DECLARE @UpperBound INT = 1000000;
;WITH cteN(Number) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY s1.[object_id]) - 1
FROM sys.all_columns AS s1
CROSS JOIN sys.all_columns AS s2
)
SELECT n = [Number] INTO dbo.Numbers
FROM cteN WHERE [Number] <= @UpperBound;
CREATE UNIQUE CLUSTERED INDEX CIX_Number ON dbo.Numbers(n)
WITH
(
FILLFACTOR = 100, -- in the event server default has been changed
DATA_COMPRESSION = ROW -- if Enterprise & table large enough to matter
);
PPS同じシナリオですが、パーティション化されていないものでも完全に機能します。
推定(新しいカーディナリティエスティメータを使用)は通常の結合では問題ありませんが、オプティマイザが同じ場所にある結合のオプションを検討する場合、精度は低くなります。
同じ方法でパーティション化された2つのテーブルを結合する場合、共存結合(パーティションごとの結合とも呼ばれます)を使用できます。アイデアは、一定のスキャン(メモリ内の値のテーブル)によって提供されるパーティションIDによって駆動されるネストされたループの適用を使用して、一度に1つのパーティションを結合することです。
同じ場所にある結合にはネストされたループの適用が含まれるため、OPTION (HASH JOIN)
を指定することで、オプティマイザにこれを回避させることができます。次に例を示します。
その計画の2つのシークは次のとおりです。
_Seek Keys[1]: Prefix:
PtnId1000, [dbo].[A].DateKey = Scalar Operator((3)), Scalar Operator((20140802))
Seek Keys[1]: Prefix:
PtnId1003, [dbo].[B].DateKey = Scalar Operator((3)), Scalar Operator((20140802))
_
オプティマイザは両方のケースで静的パーティションの削除を適用し、両方のシークと次の結合の正確な見積もりを提供します。
オプティマイザが(質問に示されているように)共存結合を検討する場合、シークは次のとおりです。
_Seek Keys[1]: Prefix:
PtnId1000, [dbo].[A].DateKey = Scalar Operator([Expr1006]), Scalar Operator((20140802))
Seek Keys[1]: Prefix:
PtnId1003, [dbo].[B].DateKey = Scalar Operator([Expr1006]), Scalar Operator((20140802))
_
...ここで、_[Expr1006]
_は、定数スキャン演算子によって返される値です。
カーディナリティエスティメータは、リテラル定数が使用されたときのように、DateKey
値とパーティションIDが相互に依存していることを認識できなくなりました。言い換えると、_[Expr1006]
_内の値が_DateKey = 20140802
_と同じパーティションを指定していることは推定器には明らかではありません。
結果として、CEは(デフォルトで)通常の 指数バックオフ方式 を使用して2つの(明らかに独立した)述語の選択性を推定することを選択します。
これは、カーディナリティの推定値が減少しているため、ジョインに供給されていることを説明しています。このオプションの見かけ上のコストが低い(誤解のため)ことは、オプティマイザが値を提供しないことが(人間には)明らかであるにもかかわらず、通常の結合ではなくコロケート結合を選択することを意味します。
クエリヒントUSE HINT ('ASSUME_MIN_SELECTIVITY_FOR_FILTER_ESTIMATES')
の使用など、ロジック内のこのギャップを回避する方法はいくつかありますが、これは問題のある同じ場所にある結合の代替方法だけでなく、クエリ全体に影響します。エリックが彼の回答で指摘しているように、レガシーCEの使用を示唆することもできます。
同じ場所にある結合の詳細については、私の記事を参照してください パーティション分割テーブルの結合パフォーマンスの向上
これは、SQL Server 2014で導入された新しいカーディナリティエスティメータが原因であると思われます。
古いクエリを使用するようにクエリに指示すると、別の計画と正しい見積もりが得られます。
SELECT a.DateKey, a.Type
FROM A AS a
JOIN B AS b
ON b.DateKey = a.DateKey
AND b.Type = a.Type
WHERE a.DateKey = 20140802
OPTION(USE HINT('FORCE_LEGACY_CARDINALITY_ESTIMATION'));
詳細については、次のリンクを参照してください。