LoginSignup
21
19

More than 5 years have passed since last update.

チューニング: 複雑なSQLで効率の悪い実行プランが採用されてしまう場合の対処法

Last updated at Posted at 2016-12-06

※検証にはSQLServerを使用しています。

プロダクション環境で1分ほどかかってしまうクエリを実行する機能があったため、1秒程度までチューニングしたときのお話です。
該当のSQLは、20個ほどのテーブルをJOINして結果をSELECTする複雑なSQLでした。

クエリの中にはサブクエリのJOINも含まれており、調査の結果このサブクエリがボトルネックになっているようでした。
実行プランをみると、IndexScanの実際の行数が2億オーバーです。該当テーブルは本当は50万レコードくらいしかないので、最初はバグかな?と思いました。
統計情報は更新されているし、option(recompile)をつけて実行しても同じ実行プランで実行されてしまいます。

    --問題のSQL概要(ざっくりしててすみません。。)
    SELECT
        いろんなカラム
    FROM
        いろんなテーブル
    -- ↓ ここがボトルネック
    JOIN (
        サブクエリ
    ) A ON A.ColumnA = Column1
    -- ↑ ここがボトルネック
    WHERE
        いろんな条件

実行プランの一部(テーブルサイズ50万レコードくらいなのに、Index Scanの行数がすごいことになってる)
図1.png

調べていくうちに、「実際の行数」というのは、「GetNext()関数がコールされた回数」のことだということが分かりました。
参考: http://stackoverflow.com/questions/1006385/sql-execution-plan-shows-an-actual-number-of-rows-which-is-larger-than-the-tab?rq=1

ただ、なぜGetNext()関数が大量にコールされてしまったのか、そこまではつきとめることができませんでした。
(心当たりがある方は是非教えてください!)

サブクエリの中身はこんな感じでした

    -- ボトルネックとなっていたサブクエリ
    SELECT
        ColumnA
    FROM TableA
    WHERE ColumnB IN (
            SELECT MAX(ColumnB)
            FROM TableB
            JOIN TableC ON TableB.ColumnC = TableC.ColumnC
            GROUP BY ColumnD
        )

ためしに、このサブクエリの中身だけ単体で実行すると1秒もかからずに結果が返ってきます。
このことから、該当のSQLが遅い原因は「複雑なSQL文の解析時に、本当はもっと高速なプランがあるのに、効率の悪いプランが採用されてしまった」ということだと判断しました。

調査結果から、単体で実行する分には問題ないサブクエリを切り出すようにクエリを修正しました。

    -- 問題のサブクエリだけ切り出して結果を一時テーブルに保存
    SELECT
        ColumnA
    INTO #TmpTable
    FROM TableA
    WHERE ColumnB IN (
            SELECT MAX(ColumnB)
            FROM TableB
            JOIN TableC ON TableB.ColumnC = TableC.ColumnC
            GROUP BY ColumnD
        )

    -- 一時テーブル使用
    SELECT
        いろんなカラム
    FROM
        いろんなテーブル
    -- ↓ 一時テーブルに書き換え
    JOIN #TmpTable A ON A.ColumnA = Column1
    -- ↑ 一時テーブルに書き換え
    WHERE
        いろんな条件

    -- 後処理
    DROP Table #TmpTable

修正後のクエリは1,2秒で実行結果が返ってきました。
プランでも、「実際の行数」が332行となっており、ボトルネックとなっていたSQLが先ほどとは違うプランで実行されていることが分かります。
図2.png

今回のチューニングをまとめますと、
・SQL文が複雑になると、最適なプランが生成されない可能性が上がる
特にSQLServerでは経験上、GROUPBYを含むサブクエリだと今回の現象が発生する可能性が高いです
・複雑なSQLの中ではボトルネックなのに、単体で実行すると早いサブクエリがある場合は、前もって結果を一時テーブルに保存しておくと効果的な場合がある

以上、クエリの書き換えによるチューニングの例でした。

21
19
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
19