web-dev-qa-db-ja.com

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

試行錯誤と実行計画を使用して最適化しようと過去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つのステートメントは、ほぼ瞬時に戻ります。

実行プランは this link でXMLとして利用できます。

ブラウザーで開くのではなく、右クリックして保存し、SQL Sentry Plan Explorerまたは他のいくつかの表示ソフトウェアで開くことをお勧めします。

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

15
Neo

主な答えに進む前に、更新が必要なソフトウェアが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をどこにも貼り付ける必要はありません!)

クエリプラン分析

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

table variable estimate

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

first join estimate

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

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を挿入することもできます(正しいデータ型はプランに表示されていないため、その部分は実行できません)。新しい一時テーブルは、クラスター化インデックスと非クラスター化インデックスのメリットがある場合とない場合があります。

22
Paul White 9