21
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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
20
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
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?