2026年2月20日金曜日

SQL Server 実行計画の応用とパフォーマンスチューニング事例|実務で使える改善手法を解説

実行計画の“読解”から“改善設計”へステップアップ

SQL Serverの実行計画を読み取れるようになったら、次のステップは「改善への応用」です。
実行計画は単なる分析ツールではなく、ボトルネックの特定・インデックス最適化・統計の再構築など、実践的なチューニングに直結します。
本記事では、実際に起こりやすい性能問題の事例を通じて、どのように実行計画を読み・対策するかを具体的に紹介します。

目次

第1章 事例① テーブルスキャンと欠落インデックス

最も頻繁に発生するパフォーマンス問題は、テーブルスキャン(Table Scan)です。
実行計画で「Table Scan」が表示されている場合、そのテーブルに適切なインデックスが存在していない可能性があります。

SELECT * FROM Sales.SalesOrderHeader 
WHERE CustomerID = 11000; -- インデックスなしでスキャン

このような場合、SSMSの「実行計画」上で黄色い警告アイコンが表示され、Missing Index(欠落インデックス)の提案が出ます。

-- 推奨されるインデックス作成例
CREATE INDEX IX_SalesOrderHeader_CustomerID 
ON Sales.SalesOrderHeader (CustomerID);

ただし、提案インデックスを無条件で採用すると重複やメンテナンスコスト増につながるため、更新頻度やSELECT比率を考慮して設計することが重要です。

第2章 事例② 結合順序の誤りと統計情報のずれ

結合順序が最適でない場合、実行計画上で大きなハッシュやソートが発生し、処理コストが急増します。
特に統計情報が古いと、推定行数が実際より小さくなり、誤った結合手法を選択することがあります。

SELECT c.CustomerID, o.SalesOrderID
FROM Customers c
JOIN SalesOrders o ON c.CustomerID = o.CustomerID
WHERE c.Region = 'Kanto';

対策は以下の通りです。

  • 統計情報を最新化(UPDATE STATISTICSsp_updatestats)。
  • 頻繁に結合される列に複合インデックスを作成。
  • 必要に応じて結合順序を固定するヒント句を使用。
SELECT ...
FROM Customers c
INNER JOIN SalesOrders o ON c.CustomerID = o.CustomerID
OPTION (FORCE ORDER);

推定行数と実際行数の乖離が解消されれば、結合処理が軽くなり、ハッシュ結合からネステッドループに変化するケースもあります。

第3章 事例③ ソート・ハッシュ負荷とI/Oボトルネック

実行計画で「Sort」「Hash Match」「Spool」などのオペレーターが連続して出現する場合は、I/O負荷一時領域(TempDB)使用過多が疑われます。

SELECT TOP 100 *
FROM Sales.SalesOrderDetail
ORDER BY LineTotal DESC;

このようなクエリでは、ソート処理のために大量のメモリを確保し、溢れた分をTempDBに書き出します。
対策としては以下が有効です。

  • 不要なORDER BY・GROUP BYを削減。
  • 適切なインデックス順序(カバリングインデックス)を設計。
  • TempDBのデータファイルを分割・高速ストレージへ配置。

第4章 プランガイドとヒント句の応用

同一クエリであっても、環境や統計により実行計画が変化します。
再現性が求められる場合や、一部クエリのみ固定計画を適用したい場合はプランガイド(Plan Guide)を利用します。

EXEC sp_create_plan_guide 
    @name = N'SalesGuide',
    @stmt = N'SELECT * FROM Sales.SalesOrderHeader WHERE CustomerID = @id',
    @type = N'SQL',
    @module_or_batch = NULL,
    @params = NULL,
    @hints = N'OPTION (OPTIMIZE FOR (@id UNKNOWN))';

これにより、アプリ側のSQLを変更せずに特定ヒントを適用可能です。
一方で、誤った固定化は他の環境で逆効果になるため、利用範囲を明確にします。

第5章 インデックス断片化とメンテナンス戦略

実行計画改善の前提として、インデックスの健全性維持は欠かせません。
断片化が進むと、I/Oが増加して計画上のコストも上昇します。

SELECT 
    dbschemas.name AS SchemaName,
    dbtables.name AS TableName,
    dbindexes.name AS IndexName,
    indexstats.avg_fragmentation_in_percent
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, NULL) AS indexstats
JOIN sys.tables dbtables ON dbtables.object_id = indexstats.object_id
JOIN sys.schemas dbschemas ON dbtables.schema_id = dbschemas.schema_id
JOIN sys.indexes dbindexes ON dbindexes.object_id = indexstats.object_id
AND indexstats.index_id = dbindexes.index_id
WHERE indexstats.avg_fragmentation_in_percent > 20;

再構築(REBUILD)または再編成(REORGANIZE)を定期ジョブ化し、断片化率に応じて実行するのが理想です。

第6章 実行計画キャッシュの監視と最適化

SQL Serverはクエリごとに実行計画をキャッシュし、再利用することで性能を向上させています。
しかし、過剰なキャッシュやパラメータスニッフィングにより逆効果になることもあります。

-- 実行計画キャッシュの統計を確認
SELECT TOP 50 
    qs.execution_count,
    qs.total_elapsed_time / qs.execution_count AS AvgTime,
    st.text
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st
ORDER BY AvgTime DESC;

パラメータスニッフィング対策: OPTIMIZE FOR句やRECOMPILEオプションを活用し、安定した実行計画を維持します。

-- クエリごとに再コンパイル
SELECT * FROM Orders WHERE CustomerID = @id
OPTION (RECOMPILE);

まとめ:計画を“読む”から“育てる”へ

実行計画はSQL Serverチューニングの中心的ツールです。
Table ScanやSortの発生箇所を把握し、統計・インデックス・キャッシュの三要素を調整すれば、クエリ性能は劇的に改善します。
単なる分析に留まらず、実行計画を継続的に監視・改善する文化を持つことが、安定運用の鍵です。

参考リンク(公式ドキュメント)

SQL Server 解説用イメージ

0 件のコメント:

コメントを投稿