SQL Serverで多対多の結合を示唆する方法は?


9

1組の列(両方int)で結合する3つの「大きな」テーブルがあります。

  • Table1には2億行まで
  • Table2には約150万行あります
  • Table3には約600万行あります

各テーブルには、上のクラスタ化インデックスを持っているKey1Key2して、1つの以上の列。Key1カーディナリティが低く、非常にゆがんでいます。これは常にWHERE句で参照されます。条項でKey2言及されていないWHERE。各結合は多対多です。

問題は、カーディナリティの推定にあります。各結合の出力見積もりは、大きくなるのではなく小さくなります。これにより、実際の結果が数百万に相当する場合、最終的な推定値は数百になります。

CEを手掛かりにしてより良い推定を行う方法はありますか?

SELECT 1
FROM Table1 t1
     JOIN Table2 t2
       ON t1.Key1 = t2.Key1
          AND t1.Key2 = t2.Key2
     JOIN Table3 t3
       ON t1.Key1 = t3.Key1
          AND t1.Key2 = t3.Key2
WHERE t1.Key1 = 1;

私が試したソリューション:

  • 上の複数列の統計情報を作成しKey1Key2
  • 大量のフィルターされた統計を作成するKey1(これはかなり役に立ちますが、ユーザーが作成した何千もの統計がデータベースに残ることになります。)

マスクされた実行計画(悪いマスキングのため申し訳ありません)

私が見ている場合、結果には900万行があります。新しいCEは180行を推定します。従来のCEでは6100行と推定されています。

これは再現可能な例です:

DROP TABLE IF EXISTS #Table1, #Table2, #Table3;
CREATE TABLE #Table1 (Key1 INT NOT NULL, Key2 INT NOT NULL, T1Key3 INT NOT NULL, CONSTRAINT pk_t1 PRIMARY KEY CLUSTERED (Key1, Key2, T1Key3));
CREATE TABLE #Table2 (Key1 INT NOT NULL, Key2 INT NOT NULL, T2Key3 INT NOT NULL, CONSTRAINT pk_t2 PRIMARY KEY CLUSTERED (Key1, Key2, T2Key3));
CREATE TABLE #Table3 (Key1 INT NOT NULL, Key2 INT NOT NULL, T3Key3 INT NOT NULL, CONSTRAINT pk_t3 PRIMARY KEY CLUSTERED (Key1, Key2, T3Key3));

-- Table1 
WITH Numbers
     AS (SELECT TOP (1000000) Number = ROW_NUMBER() OVER(ORDER BY t1.number)
         FROM master..spt_values t1
              CROSS JOIN master..spt_values t2),
     DataSize (Key1, NumberOfRows)
     AS (SELECT 1, 2000 UNION
         SELECT 2, 10000 UNION
         SELECT 3, 25000 UNION
         SELECT 4, 50000 UNION
         SELECT 5, 200000)
INSERT INTO #Table1
SELECT Key1
     , Key2 = ROW_NUMBER() OVER (PARTITION BY Key1, T1Key3 ORDER BY Number)
     , T1Key3
FROM DataSize
     CROSS APPLY (SELECT TOP(NumberOfRows) 
                         Number
                       , T1Key3 = Number%(Key1*Key1) + 1 
                  FROM Numbers
                  ORDER BY Number) size;

-- Table2 (same Key1, Key2 values; smaller number of distinct third Key)
WITH Numbers
     AS (SELECT TOP (1000000) Number = ROW_NUMBER() OVER(ORDER BY t1.number)
         FROM master..spt_values t1
              CROSS JOIN master..spt_values t2)
INSERT INTO #Table2
SELECT DISTINCT 
       Key1
     , Key2
     , T2Key3
FROM #Table1
     CROSS APPLY (SELECT TOP (Key1*10) 
                         T2Key3 = Number
                  FROM Numbers
                  ORDER BY Number) size;

-- Table2 (same Key1, Key2 values; smallest number of distinct third Key)
WITH Numbers
     AS (SELECT TOP (1000000) Number = ROW_NUMBER() OVER(ORDER BY t1.number)
         FROM master..spt_values t1
              CROSS JOIN master..spt_values t2)
INSERT INTO #Table3
SELECT DISTINCT 
       Key1
     , Key2
     , T3Key3
FROM #Table1
     CROSS APPLY (SELECT TOP (Key1) 
                         T3Key3 = Number
                  FROM Numbers
                  ORDER BY Number) size;


DROP TABLE IF EXISTS #a;
SELECT col = 1 
INTO #a
FROM #Table1 t1
     JOIN #Table2 t2
       ON t1.Key1 = t2.Key1
          AND t1.Key2 = t2.Key2
WHERE t1.Key1 = 1;

DROP TABLE IF EXISTS #b;
SELECT col = 1 
INTO #b
FROM #Table1 t1
     JOIN #Table2 t2
       ON t1.Key1 = t2.Key1
          AND t1.Key2 = t2.Key2
     JOIN #Table3 t3
       ON t1.Key1 = t3.Key1
          AND t1.Key2 = t3.Key2
WHERE t1.Key1 = 1;

回答:


5

明確にするために、オプティマイザはすでに多対多の結合であることを知っています。マージ結合を強制して推定プランを見ると、結合が多対多であるかどうかを示す結合演算子のプロパティを確認できます。ここで解決する必要のある問題は、カーディナリティの見積もりを増やすことです。おそらく、除外したクエリの部分に対して、より効率的なクエリプランが得られるでしょう。

私は結果を入れているしようとするだろうという最初から参加Object3し、Object5一時テーブルに。あなたが投稿した計画の場合、それは51393行の単一の列であるため、tempdbのスペースをほとんど占有しません。一時テーブルで完全な統計を収集できますが、それだけで十分な正確な最終カーディナリティの見積もりを取得できます。完全な統計を収集するObject1ことも役立ちます。カーディナリティーの見積もりは、プランから右から左に移動するにつれて悪化することがよくあります。

それが機能しない場合ENABLE_QUERY_OPTIMIZER_HOTFIXESは、データベースレベルまたはサーバーレベルでクエリヒントを有効にしていない場合に、クエリヒントを試すことができます。Microsoftは、SQL Server 2016の計画に影響するパフォーマンス修正をその設定の背後にロックしています。それらのいくつかはカーディナリティの見積もりに関連しているので、おそらくあなたは幸運になり、修正の1つがクエリに役立つでしょう。FORCE_LEGACY_CARDINALITY_ESTIMATIONクエリヒントを使用して、従来のカーディナリティエスティメータを使用することもできます。レガシーCEを使用すると、特定のデータセットでより適切な推定が得られる場合があります。

最後の手段として、Adam MachanicのMANY()関数を使用して、任意の係数でカーディナリティの見積もりを手動で増やすことができます。別の回答でそれについて話しますが、リンクが機能していないようです。興味があれば、掘り起こしてみます。


Adamのmake_parallel関数は問題の緩和に役立ちます。見てみましょうmany。かなり大きなバンドエイドのようです。
Steven Hibble

2

SQL Server統計には、統計オブジェクトの先頭列のヒストグラムのみが含まれます。したがって、の値のヒストグラムを提供するフィルターされた統計を作成できますKey2が、のある行の間のみKey1 = 1です。各テーブルでこれらのフィルター処理された統計を作成すると、推定が修正され、テストクエリに期待される動作が発生します。新しい結合はそれぞれ、最終的な基数の推定に影響を与えません(SQL 2016 SP1とSQL 2017の両方で確認)。

-- Note: Add "WITH FULLSCAN" to each if you want a perfect 20,000 row estimate
CREATE STATISTICS st_#Table1 ON #Table1 (Key2) WHERE Key1 = 1
CREATE STATISTICS st_#Table2 ON #Table2 (Key2) WHERE Key1 = 1
CREATE STATISTICS st_#Table3 ON #Table3 (Key2) WHERE Key1 = 1

これらのフィルタリングされた統計がない場合、SQL Serverは、よりヒューリスティックベースのアプローチを使用して、結合のカーディナリティを推定します。次のホワイトペーパーには、SQL Serverが使用するいくつかのヒューリスティックの高レベルな説明が含まれています。SQL Server 2014 Cardinality Estimatorによるクエリプランの最適化

たとえばUSE HINT('ASSUME_JOIN_PREDICATE_DEPENDS_ON_FILTERS')、クエリにヒントを追加すると、結合の包含ヒューリスティックが変更され、Key1述語とKey2結合述語の間に(独立ではなく)相関関係があると想定されます。これは、クエリにとって有益な場合があります。最後のテストクエリでは、このヒントによって基数の推定値がから1,175に増加しますが、フィルターされた統計で生成され7,551た正しい20,000行の推定値にはまだかなり恥ずかしいです。

同様の状況で使用したもう1つのアプローチは、データの関連サブセットを#tempテーブルに抽出することです。特に、新しいバージョンのSQL Serverが#tempテーブルをディスクに熱心に書き込むことがなくなった今、このアプローチで良い結果が得られました。多対多結合の説明は、ケースの個々の#tempテーブルが比較的小さい(または少なくとも最終結果セットよりも小さい)ことを意味しているため、このアプローチは試す価値があります。

DROP TABLE IF EXISTS #Table1_extract, #Table2_extract, #Table3_extract, #c
-- Extract only the subset of rows that match the filter predicate
-- (Or better yet, extract only the subset of columns you need!)
SELECT * INTO #Table1_extract FROM #Table1 WHERE Key1 = 1
SELECT * INTO #Table2_extract FROM #Table2 WHERE Key1 = 1
SELECT * INTO #Table3_extract FROM #Table3 WHERE Key1 = 1
-- Now perform the join on those extracts, removing the filter predicate
SELECT col = 1
INTO #c 
FROM #Table1_extract t1
JOIN #Table2_extract t2
    ON t1.Key2 = t2.Key2
JOIN #Table3_extract t3
    ON t1.Key2 = t3.Key2

フィルタリングされた統計を広範囲に使用していKey1ますが、各テーブルの値ごとに1 つにしています。今では数千に上ります。
Steven Hibble 2017年

2
@StevenHibble何千ものフィルタリングされた統計が管理を困難にする可能性があるという良い点。(プランのコンパイル時間にマイナスの影響があることも確認しました。)ユースケースに適合しない可能性がありますが、何度か正常に使用した別の#temp tableアプローチも追加しました。
ジェフパターソン

-1

リーチ。試す以外に本当の根拠はありません。

SELECT 1
FROM Table1 t1
     JOIN Table2 t2
       ON t1.Key2 = t2.Key2
      AND t1.Key1 = 1
      AND t2.Key1 = 1
     JOIN Table3 t3
       ON t2.Key2 = t3.Key2
      AND t3.Key1 = 1;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.