実行プランを使用してT-SQLクエリを最適化する方法


15

過去2日間、試行錯誤と実行計画を使用して最適化しようとして費やしたSQLクエリがありますが、役に立ちません。これを行うことを許してください。しかし、私はここに実行計画全体を掲載します。簡潔にするためと会社のIPを保護するために、クエリおよび実行プランのテーブル名と列名を汎用にするように努力しました。実行計画は、SQL Sentry Plan Explorerで開くことができます。

かなりの量のT-SQLを実行しましたが、実行プランを使用してクエリを最適化することは私にとって新しい分野であり、その方法を本当に理解しようとしました。したがって、誰かがこれを手伝って、この実行計画を解読してクエリで最適化する方法を見つける方法を説明できれば、私は永遠に感謝しています。最適化するクエリはさらに多くあります。この最初のクエリを支援するための踏み台が必要です。

これはクエリです:

DECLARE @Param0 DATETIME     = '2013-07-29';
DECLARE @Param1 INT          = CONVERT(INT, CONVERT(VARCHAR, @Param0, 112))
DECLARE @Param2 VARCHAR(50)  = 'ABC';
DECLARE @Param3 VARCHAR(100) = 'DEF';
DECLARE @Param4 VARCHAR(50)  = 'XYZ';
DECLARE @Param5 VARCHAR(100) = NULL;
DECLARE @Param6 VARCHAR(50)  = 'Text3';

SET NOCOUNT ON

DECLARE @MyTableVar TABLE
(
    B_Var1_PK int,
    Job_Var1 varchar(512),
    Job_Var2 varchar(50)
)

INSERT INTO @MyTableVar (B_Var1_PK, Job_Var1, Job_Var2) 
SELECT B_Var1_PK, Job_Var1, Job_Var2 FROM [fn_GetJobs] (@Param1, @Param2, @Param3, @Param4, @Param6);

CREATE TABLE #TempTable
(
    TTVar1_PK INT PRIMARY KEY,
    TTVar2_LK VARCHAR(100),
    TTVar3_LK VARCHAR(50),
    TTVar4_LK INT,
    TTVar5 VARCHAR(20)
);

INSERT INTO #TempTable
SELECT DISTINCT
    T.T1_PK,
    T.T1_Var1_LK,
    T.T1_Var2_LK,
    MAX(T.T1_Var3_LK),
    T.T1_Var4_LK
FROM
    MyTable1 T
    INNER JOIN feeds.MyTable2 A ON A.T2_Var1 = T.T1_Var4_LK
    INNER JOIN @MyTableVar B ON B.Job_Var2 = A.T2_Var2 AND B.Job_Var1 = A.T2_Var3
GROUP BY T.T1_PK, T.T1_Var1_LK, T.T1_Var2_LK, T.T1_Var4_LK

-- This is the slow statement...
SELECT 
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
        WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
        WHEN 'Text3' THEN T.TTVar2_LK
    END,
    T.TTVar4_LK,
    T.TTVar3_LK,
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN F.F_Var1
        WHEN 'Text2' THEN F.F_Var2
        WHEN 'Text3' THEN T.TTVar5
    END,
    A.A_Var3_FK_LK,
    C.C_Var1_PK,
    SUM(CONVERT(DECIMAL(18,4), A.A_Var1) + CONVERT(DECIMAL(18,4), A.A_Var2))
FROM #TempTable T
    INNER JOIN TableA (NOLOCK) A ON A.A_Var4_FK_LK  = T.TTVar1_PK
    INNER JOIN @MyTableVar     B ON B.B_Var1_PK     = A.Job
    INNER JOIN TableC (NOLOCK) C ON C.C_Var2_PK     = A.A_Var5_FK_LK
    INNER JOIN TableD (NOLOCK) D ON D.D_Var1_PK     = A.A_Var6_FK_LK
    INNER JOIN TableE (NOLOCK) E ON E.E_Var1_PK     = A.A_Var7_FK_LK  
    LEFT OUTER JOIN feeds.TableF (NOLOCK) F ON F.F_Var1 = T.TTVar5
WHERE A.A_Var8_FK_LK = @Param1
GROUP BY
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
        WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
        WHEN 'Text3' THEN T.TTVar2_LK
    END,
    T.TTVar4_LK,
    T.TTVar3_LK,
    CASE E.E_Var1_LK 
        WHEN 'Text1' THEN F.F_Var1
        WHEN 'Text2' THEN F.F_Var2
        WHEN 'Text3' THEN T.TTVar5
    END,
    A.A_Var3_FK_LK, 
    C.C_Var1_PK


IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
    DROP TABLE #TempTable
END
IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
    DROP TABLE #TempTable
END

私が見つけたのは、3番目のステートメント(遅いとコメントされている)が最も時間がかかっている部分だということです。前の2つのステートメントは、ほぼ瞬時に戻ります。

実行計画は、このリンクで XMLとして入手できます。

ブラウザで開くのではなく、右クリックして保存し、SQL Sentry Plan Explorerまたはその他の表示ソフトウェアで開く方が適切です。

テーブルやデータに関する情報が必要な場合は、お気軽にお問い合わせください。


2
あなたの統計はかなり外れています。最後にインデックスの断片化を解消したり、統計を更新したのはいつですか?また、オプティマイザはテーブル変数の統計を実際に使用できないため、テーブル変数@MyTableVarの代わりに一時テーブルを使用しようとします。
アダムヘインズ

ご返信ありがとうございます。@MyTableVarを一時テーブルに変更しても効果はありませんが、ほんの数行です(実行計画から確認できます)。実行計画の中で、私の統計がかなり外れていることを示していますか?どのインデックスを再編成または再構築する必要があるか、どのテーブルの統計を更新する必要があるかを示していますか?
ネオ

3
右下のそのハッシュ結合には、ビルド入力に推定24,000行がありますが、実際には3,285,620なので、にあふれている可能性がありますtempdb。間の結合の結果行の見積もりすなわちTableA@MyTableVarオフの仕方をしているし。また、ソートに入る行の数は予想よりもはるかに多いため、同様に流出する可能性があります。
マーティンスミス

回答:


21

主な答えを得る前に、更新する必要があるソフトウェアが2つあります。

必要なソフトウェアの更新

1つ目はSQL Serverです。SQL Server 2008 Service Pack 1(ビルド2531)を実行しています。少なくとも現在のService Pack(SQL Server 2008 Service Pack 3-ビルド5500)にパッチを適用する必要があります。執筆時点でのSQL Server 2008の最新のビルドは、Service Pack 3、累積更新プログラム12(ビルド5844)です。

2番目のソフトウェアはSQL Sentry Plan Explorerですです。最新バージョンには、専門家の分析のためにクエリプランを直接アップロードする機能など、重要な新機能と修正があります(XMLをどこにでも貼り付ける必要はありません!)

クエリプラン分析

ステートメントレベルの再コンパイルのおかげで、テーブル変数のカーディナリティの見積もりは正確です。

テーブル変数推定

残念ながら、テーブル変数は分布統計を保持しません。そのため、オプティマイザーが知っているのは、6つの行があることだけです。これらの6行にある可能性のある値は何も知りません。次の操作が別のテーブルへの結合であることを考えると、この情報は重要です。その結合からのカーディナリティの見積もりは、オプティマイザーによるワイルドな推測に基づいています。

最初の結合推定

その時点から、オプティマイザーによって選択された計画は誤った情報に基づいているため、パフォーマンスが非常に悪いことは不思議ではありません。特に、ソート用に確保されたメモリとハッシュ結合用のハッシュテーブルは小さすぎます。実行時に、オーバーフローするソートとハッシュ操作が物理 tempdbディスクに流出します。

SQL Server 2008は、実行計画でこれを強調していません。拡張イベントまたはプロファイラーの並べ替え警告ハッシュ警告を使用して、流出を監視できます。メモリは、実行を開始する前にカーディナリティの推定値に基づいて並べ替えとハッシュ用に予約されており、SQL Serverの空きメモリ量に関係なく、実行中に増やすことはできません。したがって、ワークスペースのメモリを消費する操作を含む実行計画では、正確な行数の見積もりが重要です。

クエリもパラメータ化されます。OPTION (RECOMPILE)異なるパラメーター値がクエリプランに影響する場合は、クエリへの追加を検討する必要があります。とにかくそれを使用することを検討する必要がありますので、オプティマイザーはの値を見ることができます@Param1コンパイル時に。他に何もない場合、これは、表が非常に大きく、パーティション化されている場合、オプティマイザーが上記のインデックスシークのより合理的な推定値を生成するのに役立ちます。また、静的パーティションの削除が可能になる場合があります。

テーブル変数との 代わりに一時テーブルを使用してクエリを再試行しますOPTION (RECOMPILE)。また、最初の結合の結果を別の一時テーブルに具体化して、それに対してクエリの残りを実行する必要があります。行の数はそれほど大きくありません(3,285,620)ので、これはかなり速いはずです。その後、オプティマイザーは、結合の結果の正確なカーディナリティー推定値と分布統計を取得します。運が良ければ、計画の残りの部分はうまく配置されます。

プランに示されているプロパティから操作すると、具体化クエリは次のようになります。

SELECT
    A.A_Var7_FK_LK,
    A.A_Var4_FK_LK,
    A.A_Var6_FK_LK, 
    A.A_Var5_FK_LK,
    A.A_Var1,
    A.A_Var2,
    A.A_Var3_FK_LK
INTO #AnotherTempTable
FROM @MyTableVar AS B
JOIN TableA AS A
    ON A.Job = B.B_Var1_PK
WHERE
    A_Var8_FK_LK = @Param1;

INSERT事前定義された一時テーブルに入れることもできます(正しいデータ型がプランに表示されないため、その部分はできません)。新しい一時テーブルは、クラスター化インデックスと非クラスター化インデックスの恩恵を受ける場合と得ない場合があります。


この詳細な回答に感謝します。申し訳ありませんが、返信には1週間かかりました。他の作業が散りばめられたこの作業を毎日行っています。TableAへの結合を具体化する提案を実装しました#AnotherTempTable。これが最良の影響を与えるように思われました-他の提案(@MyTableVarにテーブル変数の代わりに一時テーブルを使用OPTION (RECOMPILE)し、使用してもあまり効果がなかった、またはまったく効果がありませんでした。 SQL Sentryの計画Explorerのオプションは素晴らしいです-私はそれらを使用していますanswers.sqlperformance.com/questions/1087
ネオ

-6

@MyTableVarにPKがあるはずで、#MyTableVarの方がパフォーマンスが優れていることに同意します(特に行数が多い場合)。

where句内の条件

   WHERE A.A_Var8_FK_LK = @Param1

内部結合Aに移動する必要があります。私の経験では、オプティマイザーはこれを行うのに十分ではなく(申し訳ありませんが計画は見ていませんでした)、大きな違いを生む可能性があります。

これらの変更で改善が見られない場合、Aの別の一時テーブルと、A.A_Var8_FK_LK = @ Param1によって制約された(素敵な?)に参加するすべてのものを作成します。

次に、次の結合条件のために、その一時テーブルにクラスター化インデックスを作成します(作成前または作成後)。

次に、その結​​果を残りのいくつかのテーブル(FおよびT)に結合します。

Bamは、行の推定値がオフで、とにかく簡単に改善できない場合に悪臭を放つクエリプランを必要とします)。私はあなたが適切なインデックスを持っていると仮定していますが、これは私が計画内で最初にチェックするものです。

トレースは、大幅な影響がある場合とない場合があるtempdbの流出を示すことができます。

別の代替アプローチ-少なくとも試してみる方が速い-は、テーブルを最低行(A)から最高に並べ、結合、マージ、ハッシュ、ループの追加を開始することです。ヒントが存在する場合、結合順序は指定どおりに固定されます。他のユーザーは、相対的な行数が劇的に変化すると長期的には傷つく可能性があるため、このアプローチを賢明に避けます。ヒントの最小数が望ましいです。

これらの多くを行っている場合、おそらく商用オプティマイザーは試してみる価値がある(または試用する価値がある)が、それでも良い学習経験です。


はい、そうです。これにより、Aによって返される行が制約によって制限されます。そうでない場合、オプティマイザーは最初に結合し、後で制約を適用する場合があります。私はこれを毎日扱っています。
crokusek

4
@crokusekあなたは間違っています。SQL-Serverのオプティマイザーは、INNER結合の場合、クエリが同等である(条件がWHERE句であるかON句であるか)ことを知っています。
ypercubeᵀᴹ


そのひどい習慣。たぶん、この特定の場合(1つの制約がある場合)になりますが、私はwhere句でAND条件を積み重ねる複数の開発者の土地から来ています。SQL Server 常にそれらを結合に「移動」しませ
crokusek

外側(および右側の結合)が正しくない。ただし、where句内にANDされた式のみがあり、各用語が特定の内部結合にのみ対応する場合、その用語は最適化およびベストプラクティス(imo)として「オン」の場所に安全かつ確実に移動できます。それが「真の」結合条件であるか、単に固定された制約であるかは、大きなパフォーマンス向上の副次的要因です。そのリンクは些細なケースです。実際には、convert()と数学を使用したwhere条件が複数あるため、ベストプラクティスを導き出すためのより良い候補となります。
crokusek
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.