DataBase / SQL Server / レポート取得ガイド
PART 03 — Query Store による取得とセクション定義
Query Store は SQL Server 2016 以降で利用できる機能で、 クエリのテキスト・実行プラン・実行統計履歴をデータベース内に自動保存します。 Oracle AWR の SQL Statistics セクションに最も近い機能であり、 期間指定でのクエリ性能比較・プランのリグレッション検知・プラン固定化が可能です。
1. Query Store の概要とアーキテクチャ
Query Store はデータベースレベルで有効化し、以下の情報を自動収集します。
- クエリテキスト(query_store_query_text)
- コンパイル済み実行プラン(query_store_plan)
- 実行統計(query_store_runtime_stats)
- 統計集計期間(query_store_runtime_stats_interval)
- 待機統計 SQL Server 2017+(query_store_wait_stats)
💡 Oracle AWR との違い
Oracle AWR はインスタンス全体のスナップショットを定期取得するのに対し、 Query Store はクエリ単位で実行統計を連続的に記録します。 AWR の「SQL ordered by Elapsed Time」相当の情報を任意の期間で抽出でき、 プランの変遷もビューで追跡できる点が AWR を超える強みです。
Oracle AWR はインスタンス全体のスナップショットを定期取得するのに対し、 Query Store はクエリ単位で実行統計を連続的に記録します。 AWR の「SQL ordered by Elapsed Time」相当の情報を任意の期間で抽出でき、 プランの変遷もビューで追跡できる点が AWR を超える強みです。
⚠️ バージョン要件
Query Store は SQL Server 2016 (13.0) 以降で利用可能です。 待機統計の記録は SQL Server 2017 (14.0) 以降が必要です。 Azure SQL Database では全バージョンで有効化されています。
Query Store は SQL Server 2016 (13.0) 以降で利用可能です。 待機統計の記録は SQL Server 2017 (14.0) 以降が必要です。 Azure SQL Database では全バージョンで有効化されています。
2. 有効化と設定
Query Store の有効化
-- データベース単位で有効化
ALTER DATABASE [YourDatabase]
SET QUERY_STORE = ON (
OPERATION_MODE = READ_WRITE, -- READ_WRITE: 収集有効 / READ_ONLY: 収集停止・参照のみ
CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 30), -- データ保持日数
DATA_FLUSH_INTERVAL_SECONDS = 900, -- メモリ→ストレージへのフラッシュ間隔(秒)
INTERVAL_LENGTH_MINUTES = 60, -- 統計集計インターバル(分)
MAX_STORAGE_SIZE_MB = 1000, -- Query Store が使用する最大ストレージ (MB)
QUERY_CAPTURE_MODE = AUTO, -- AUTO: 重要クエリのみ / ALL: 全クエリ
SIZE_BASED_CLEANUP_MODE = AUTO, -- ストレージ上限到達時の自動クリーンアップ
MAX_PLANS_PER_QUERY = 200, -- クエリ1件に保持するプランの最大数
WAIT_STATS_CAPTURE_MODE = ON -- 待機統計収集 (SQL Server 2017+)
);
GO
-- 有効化の確認
SELECT name, is_query_store_on
FROM sys.databases
WHERE name = 'YourDatabase';
設定のチューニングポイント
| 設定項目 | 推奨値(OLTP) | 推奨値(DWH) | 説明 |
|---|---|---|---|
| INTERVAL_LENGTH_MINUTES | 60 | 60 ~ 120 | 統計集計の粒度。細かいほど詳細だがストレージ消費増 |
| MAX_STORAGE_SIZE_MB | 500 ~ 2048 | 2048 ~ 10240 | 少なすぎると古いデータが早期削除される |
| QUERY_CAPTURE_MODE | AUTO | AUTO | ALL は全クエリ記録のためストレージ消費が大きい |
| STALE_QUERY_THRESHOLD_DAYS | 30 | 60 ~ 90 | 長期傾向分析が必要な場合は延ばす |
3. 主要カタログビューとセクション定義
query_store_query
クエリのコンパイル情報。query_hash・オブジェクト ID・コンテキスト設定などを保持。クエリ識別の基点となるビュー。
query_store_query_text
クエリテキスト(SQL 文)を保持。query_text_id で query_store_query と結合する。
query_store_plan
実行プランの XML(query_plan 列)と強制フラグ(is_forced_plan)を保持。プランの変遷を追跡できる。
query_store_runtime_stats
インターバルごとの実行統計(CPU・経過時間・論理読み取りなど)。Oracle AWR の SQL Statistics 直接対応ビュー。
query_store_runtime_stats_interval
統計集計インターバルの開始・終了時刻。期間指定クエリで結合して時間帯を絞り込む。
query_store_wait_stats
クエリ単位の待機統計(SQL Server 2017+)。wait_category_desc・total_query_wait_time_ms などを保持。
query_store_runtime_stats の主要列定義
| 列名 | 説明 | Oracle AWR 対応 |
|---|---|---|
| execution_type_desc | 実行種別(Regular, Aborted, Exception) | — |
| count_executions | インターバル内の実行回数 | Executions |
| avg_duration | 平均経過時間(マイクロ秒) | Avg Elapsed Time |
| total_duration | インターバル内の合計経過時間(マイクロ秒) | Total Elapsed Time |
| avg_cpu_time | 平均 CPU 時間(マイクロ秒) | Avg CPU Time |
| avg_logical_io_reads | 平均論理読み取り数(8KB ページ単位) | Avg Buffer Gets |
| avg_physical_io_reads | 平均物理読み取り数 | Avg Disk Reads |
| avg_rowcount | 平均処理行数 | Rows Processed |
| avg_memory_grant_kb | 平均メモリ許可量(KB) | — |
| last_execution_time | 最後の実行時刻 | — |
4. 期間指定 SQL 統計取得クエリ集
Top SQL by 合計経過時間(特定期間)
-- 特定期間のTop SQL by 合計経過時間
-- @start_time / @end_time で分析期間を指定
DECLARE @start_time DATETIME2 = '2026-05-29 09:00:00';
DECLARE @end_time DATETIME2 = '2026-05-29 12:00:00';
SELECT TOP 20
qt.query_sql_text,
q.query_id,
p.plan_id,
rs.count_executions,
rs.avg_duration / 1000.0 AS avg_duration_ms,
rs.total_duration / 1000.0 AS total_duration_ms,
rs.avg_cpu_time / 1000.0 AS avg_cpu_ms,
rs.avg_logical_io_reads AS avg_logical_reads,
rs.avg_physical_io_reads AS avg_physical_reads,
rs.avg_rowcount,
rs.avg_memory_grant_kb
FROM sys.query_store_runtime_stats rs
JOIN sys.query_store_runtime_stats_interval rsi
ON rs.runtime_stats_interval_id = rsi.runtime_stats_interval_id
JOIN sys.query_store_plan p
ON rs.plan_id = p.plan_id
JOIN sys.query_store_query q
ON p.query_id = q.query_id
JOIN sys.query_store_query_text qt
ON q.query_text_id = qt.query_text_id
WHERE rsi.start_time >= @start_time
AND rsi.end_time <= @end_time
AND rs.execution_type = 0 -- Regular execution のみ
ORDER BY rs.total_duration DESC;
プランのリグレッション検知(今日 vs 昨日)
-- プランのリグレッション検知:昨日と今日で同一クエリの性能を比較
WITH today AS (
SELECT
q.query_id,
SUM(rs.count_executions) AS exec_today,
AVG(rs.avg_duration) AS avg_dur_today
FROM sys.query_store_runtime_stats rs
JOIN sys.query_store_runtime_stats_interval rsi
ON rs.runtime_stats_interval_id = rsi.runtime_stats_interval_id
JOIN sys.query_store_plan p ON rs.plan_id = p.plan_id
JOIN sys.query_store_query q ON p.query_id = q.query_id
WHERE rsi.start_time >= DATEADD(HOUR, -24, GETUTCDATE())
GROUP BY q.query_id
),
yesterday AS (
SELECT
q.query_id,
SUM(rs.count_executions) AS exec_yesterday,
AVG(rs.avg_duration) AS avg_dur_yesterday
FROM sys.query_store_runtime_stats rs
JOIN sys.query_store_runtime_stats_interval rsi
ON rs.runtime_stats_interval_id = rsi.runtime_stats_interval_id
JOIN sys.query_store_plan p ON rs.plan_id = p.plan_id
JOIN sys.query_store_query q ON p.query_id = q.query_id
WHERE rsi.start_time >= DATEADD(HOUR, -48, GETUTCDATE())
AND rsi.start_time < DATEADD(HOUR, -24, GETUTCDATE())
GROUP BY q.query_id
)
SELECT
t.query_id,
qt.query_sql_text,
y.avg_dur_yesterday / 1000.0 AS avg_dur_yesterday_ms,
t.avg_dur_today / 1000.0 AS avg_dur_today_ms,
CAST(
(t.avg_dur_today - y.avg_dur_yesterday) * 100.0
/ NULLIF(y.avg_dur_yesterday, 0) AS DECIMAL(6,1)
) AS pct_change
FROM today t
JOIN yesterday y ON t.query_id = y.query_id
JOIN sys.query_store_query q ON t.query_id = q.query_id
JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
WHERE t.avg_dur_today > y.avg_dur_yesterday * 1.5 -- 50% 以上悪化したもの
ORDER BY pct_change DESC;
5. Query Store 待機統計(SQL Server 2017+)
query_store_wait_stats ビューにより、クエリ単位の待機統計が取得できます。
「このクエリは何の待機で時間を使っているか」が一目でわかります。
| 列名 | 説明 |
|---|---|
| wait_category_desc | 待機カテゴリ(CPU, Lock, Latch, Network IO, User IO, Log IO など) |
| total_query_wait_time_ms | 合計待機時間(ミリ秒) |
| avg_query_wait_time_ms | 平均待機時間(ミリ秒) |
-- クエリごとの待機カテゴリ内訳
SELECT TOP 20
qt.query_sql_text,
ws.wait_category_desc,
SUM(ws.total_query_wait_time_ms) AS total_wait_ms,
AVG(ws.avg_query_wait_time_ms) AS avg_wait_ms
FROM sys.query_store_wait_stats ws
JOIN sys.query_store_plan p ON ws.plan_id = p.plan_id
JOIN sys.query_store_query q ON p.query_id = q.query_id
JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
JOIN sys.query_store_runtime_stats_interval rsi
ON ws.runtime_stats_interval_id = rsi.runtime_stats_interval_id
WHERE rsi.start_time >= DATEADD(HOUR, -24, GETUTCDATE())
GROUP BY qt.query_sql_text, ws.wait_category_desc
ORDER BY total_wait_ms DESC;
6. SSMS の Query Store GUI 操作
SSMS のオブジェクトエクスプローラーで対象データベースを展開すると、 「クエリストア」フォルダが表示されます。以下のレポートが利用可能です。
| レポート名 | 内容 | Oracle AWR 対応 |
|---|---|---|
| 最もリソースを消費するクエリ | 期間・指標(CPU/経過時間/論理読み取り/実行回数等)でフィルタした Top SQL。プランをグラフで並べて比較できる。 | SQL Statistics |
| クエリウォーター フォール | 時系列での実行統計の推移をバブルチャートで表示。性能劣化の時点を視覚的に特定できる。 | — |
| プランの変更クエリ | プランが変わったクエリを列挙。変更前後の実行時間を比較し、リグレッションを検知。 | — |
| 強制されたクエリ | プランを強制固定しているクエリの一覧。 | — |
| 全クエリのクエリ | Query Store に記録された全クエリの一覧。フリーテキスト検索も可能。 | — |
7. プラン固定化(Plan Forcing)
プランのリグレッションが確認された場合、既知の良好なプランを固定できます。 Oracle の SQL Plan Management(SPM)のベースライン固定に相当します。
-- プランを固定する(plan_id は query_store_plan ビューで確認)
EXEC sys.sp_query_store_force_plan
@query_id = 42,
@plan_id = 101;
-- 固定を解除する
EXEC sys.sp_query_store_unforce_plan
@query_id = 42,
@plan_id = 101;
-- 現在強制されているプランの確認
SELECT
q.query_id, qt.query_sql_text,
p.plan_id, p.is_forced_plan,
p.force_failure_count,
p.last_force_failure_reason_desc
FROM sys.query_store_plan p
JOIN sys.query_store_query q ON p.query_id = q.query_id
JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
WHERE p.is_forced_plan = 1;
✅ Automatic Plan Correction(SQL Server 2017+)
ALTER DATABASE [YourDB] SET AUTOMATIC_TUNING (FORCE_LAST_GOOD_PLAN = ON) を設定すると、
SQL Server がプランのリグレッションを検知して自動的に最良プランを固定します。
Oracle の SQL Plan Management の自動発展に相当する機能です。