リレーショナル データベース
~クエリチューニングの基礎知識~
April 2022
Tomoyuki Oota
Cloud Solution Architect - Data & Analytics
はじめに
データベースの性能とは
タスクの状態遷移
タスク
開始
タスク
終了
実行可能状態
Runnable
実行状態
Running
待ち状態
Suspended
クエリ
開始
クエリ
終了
実行可能状態
Runnable
実行状態
Running
待ち状態
Suspended
CPU待ち
ロック待ち
並列同期待ち
I/O待ち
メモリ確保待ち
バッファ確保待ち
タスクの状態遷移
Docs: 待機の種類
クエリコンパイル
クエリ実行
クエリ
開始
クエリ
終了
実行可能状態
Runnable
実行状態
Running
待ち状態
Suspended
CPU待ち
タスクの状態遷移
クエリコンパイル
クエリ実行
ロック待ち
並列同期待ち
I/O待ち
メモリ確保待ち
バッファ確保待ち
Docs: 待機の種類
① クエリ処理の全体像
クエリ処理
パーサー
クエリ
結果
オプティマイザー
エクゼキューター
クエリ処理
SQLクエリの構文解析
パーサー
実行プランの検討と選択
オプティマイザー
実行プランの実行
エクゼキューター
パーサー
部署ID
構文ツリー
文字トークン抽出
シンタックスチェック
構文ツリーへ追加
セマンティックチェック
カタログ
プラン
キャッシュ
文字終端
オプティマイザー
エクゼキューター
構文ツリー
キャッシュ有無
オプティマイザー 論理プラン
選手番号 選手名 利き腕 チーム番号
1 AAA 右 1
2 BBB 左 1
3 CCC 右 3
4 DDD 右 2
5 EEE 右 1
6 FFF 左 2
チーム番号 チーム名
1 XXX
2 YYY
3 ZZZ
選手表とチーム表で「直積」
チーム番号が等しく
利き腕が”左”の行を「選択」
選手名とチーム名を「射影」
SELECT 選手名, チーム名 FROM 選手, チーム
WHERE 選手.チーム番号 = チーム.チーム番号 AND 利き腕 = ‘左’
選手表とチーム表を
チーム番号で「結合」
利き腕が”左”の行を「選択」
選手名とチーム名を「射影」
選手表から
利き腕が”左”の行を「選択」
上の結果とチーム表を
チーム番号で「結合」
選手名とチーム名を「射影」
6×18
6×2
2×2 2×2
6×2
6×6
4×2
6×2
2×2
選
手
表
チ
ー
ム
表
クエリトランスフォーム
エスティメート
プランジェネレート
統計情報
時間余
エクゼキューター
パーサー
構文ツリー
論理プラン
カタログ
物理プラン
物理プラン
オプティマイザー 物理プラン
選手番号 選手名 利き腕 チーム番号
1 AAA 右 1
2 BBB 左 1
3 CCC 右 3
4 DDD 右 2
5 EEE 右 1
6 FFF 左 2
チーム番号 チーム名
1 XXX
2 YYY
3 ZZZ
SELECT 選手名, チーム名 FROM 選手, チーム
WHERE 選手.チーム番号 = チーム.チーム番号 AND 利き腕 = ‘左’
選手表から
利き腕が”左”の行を「選択」
上の結果とチーム表を
チーム番号で「結合」
選手名とチーム名を「射影」
4×2
6×2
2×2
選
手
表
チ
ー
ム
表
論理プラン 物理プラン
2×2
Index Seek Table Scan
Nested Loop Join Hash Join
Filter
Merge Join
クエリトランスフォーム
エスティメート
プランジェネレート
統計情報
時間余
エクゼキューター
パーサー
構文ツリー
論理プラン
カタログ
物理プラン
物理プラン
オプティマイザー 物理プラン
選手番号 選手名 利き腕 チーム番号
1 AAA 右 1
2 BBB 左 1
3 CCC 右 3
4 DDD 右 2
5 EEE 右 1
6 FFF 左 2
チーム番号 チーム名
1 XXX
2 YYY
3 ZZZ
SELECT 選手名, チーム名 FROM 選手, チーム
WHERE 選手.チーム番号 = チーム.チーム番号 AND 利き腕 = ‘左’
選手表から
利き腕が”左”の行を「選択」
上の結果とチーム表を
チーム番号で「結合」
選手名とチーム名を「射影」
4×2
6×2
2×2
選
手
表
チ
ー
ム
表
論理プラン 物理プラン
2×2
Index Seek Table Scan
Nested Loop Join Hash Join
Filter
Merge Join
クエリトランスフォーム
エスティメート
プランジェネレート
統計情報
時間余
エクゼキューター
パーサー
構文ツリー
論理プラン
カタログ
物理プラン
物理プラン
オプティマイザー
SQL Server のインデックスのアーキテクチャとデザイン ガイド - SQL Server | Microsoft Docs
1
3
2
4
6
5
7
9
8
1
2
3 5
4
ヒープ
→ テーブルスキャン → インデックスシーク
+インデックス
オプティマイザー
番号 氏名 誕生 性別
1 ・・・ ・・・ ・・・
2 ・・・ ・・・ ・・・
~ ・・・ ・・・ ・・・
50 ・・・ ・・・ ・・・
番号 氏名 名 性別
51 ・・・ ・・・ ・・・
52 ・・・ ・・・ ・・・
~ ・・・ ・・・ ・・・
100 ・・・ ・・・ ・・・
番号 氏名 誕生 性別
1 ・・・ ・・・ ・・・
102 ・・・ ・・・ ・・・
52 ・・・ ・・・ ・・・
~ ・・・ ・・・ ・・・
50 ・・・ ・・・ ・・・
51 ・・・ ・・・ ・・・
101 ・・・ ・・・ ・・・
~ ・・・ ・・・ ・・・
100 ・・・ ・・・ ・・・
2 ・・・ ・・・ ・・・
51 ・・・ ・・・ ・・・
番号
1
番号
51
番号
1
番号
101
番号 氏名 名 性別
101 ・・・ ・・・ ・・・
102 ・・・ ・・・ ・・・
~ ・・・ ・・・ ・・・
150 ・・・ ・・・ ・・・
番号
101
番号
151
番号 氏名 名 性別
151 ・・・ ・・・ ・・・
152 ・・・ ・・・ ・・・
~ ・・・ ・・・ ・・・
200 ・・・ ・・・ ・・・
番号
root
ヒープ クラスタ化インデックス
オプティマイザー
誕生 番号
2000/01/01 ・・・
~ ・・・
2000/12/28
誕生
2000/01/01
非クラスタ化インデックス
誕生
2001/01/03
誕生
2000/01/01
誕生 番号
2001/01/03 ・・・
~ ・・・
2001/11/14 ・・・
誕生 番号
2002/01/05 ・・・
~ ・・・
2002/12/31 ・・・
誕生
2002/01/05
誕生
2003/01/01
誕生
2002/01/05
誕生 番号
2003/01/01 ・・・
~ ・・・
2003/12/24 ・・・
102
誕生 RID
2000/01/01 ・・・
~ ・・・
2000/12/28
誕生
2000/01/01
誕生
2001/01/03
誕生
2000/01/01
誕生 RID
2001/01/03 ・・・
~ ・・・
2001/11/14 ・・・
誕生 RID
2002/01/05 ・・・
~ ・・・
2002/12/31 ・・・
誕生
2002/01/05
誕生
2003/01/01
誕生
2002/01/05
誕生 RID
2003/01/01 ・・・
~ ・・・
2003/12/24 ・・・
X:Y:Z
データ部がヒープの場合:
ブックマークとしてヒープの
RIDを保持し、RIDを利用
してヒープにアクセスする。
データ部がクラスタ化インデックスの場合:
ブックマークとしてクラスタ化インデックスの
キー値を保持し、キー値以外の列値が必要
なケースはキー値を利用してクラスタ化イン
デックスにアクセスする
クラスタ化インデックス
非クラスタ化インデックス
ヒープ
オプティマイザー
例)SELECT c1, c2 FROM t1 WHERE c1 = 1
c1
1
c1
・・・
c1
・・・
c1
1
c1
・・・
c1 c2 c3 c4 c5 c6 c7
1 hoge ・・・ ・・・ ・・・ ・・・ ・・・
・・・ ・・・ ・・・ ・・・ ・・・ ・・・ ・・・
・・・ ・・・ ・・・ ・・・ ・・・ ・・・ ・・・
c1 c2
1 hoge
c1 c2
・・・ ・・・
c1 c2
・・・ ・・・
c1 c2
1 hoge
c1 c2
・・・ ・・・
c1 c2 c3 c4 c5 c6 c7
1 hoge ・・・ ・・・ ・・・ ・・・ ・・・
・・・ ・・・ ・・・ ・・・ ・・・ ・・・ ・・・
・・・ ・・・ ・・・ ・・・ ・・・ ・・・ ・・・
c1 c2 c3 c4 c5 c6 c7
1 hoge ・・・ ・・・ ・・・ ・・・ ・・・
・・・ ・・・ ・・・ ・・・ ・・・ ・・・ ・・・
・・・ ・・・ ・・・ ・・・ ・・・ ・・・ ・・・
c1 c2
1 hoge
c1 c2
・・・ ・・・
c1 c2
・・・ ・・・
c1
1
c1
・・・
カバリング
インデックス
付加列
インデックス
オプティマイザー
オプティマイザー
例)シーク可能 例)シーク不可
• c1 = 1
• c1 < 1
• c1 > 1
• c1 <> 1
• c1 + 1 = 2
• AVG(c1) > 1
• c1 IN (1, 2, 3)
• BETWEEN c1 1 AND 3
• c1 NOT IN (1, 2, 3)
• c1 = ‘hoge’ • c1 != ‘hoge’
• c1 like ‘ho%’ • c1 like ‘%ge’
• c1 like ‘%og%’
例)シーク可能 例)シーク不可
• c1 = 1 AND c2 = ‘hoge’ • c1 <> 1 AND c2 = ‘hoge’
• c1 = 1 • c2 = 1
• c1 = 1 OR c2 = ‘hoge’
<複合インデックス>
CREATE INDEX idx_c1_c2 ON t1(c1, c2)
<単一列インデックス>
CREATE INDEX idx_c1 ON t1(c1)
複合インデックスは列指定順序が重要
→インデックスの並び順は一番最初に指定した列の順に配置
idx_c1_c2 ON t1(c1, c2)
c1 c2
1 hoge
c1 c2
5 aaa
c1 c2
19 sss
c1 c2
13 zzz
idx_c2_c1 ON t1(c2, c1)
c2 c1
aaa 5
c2 c1
hoge 1
c2 c1
zzz 13
c2 c1
sss 19
オプティマイザー
注文ID 商品ID 日付 売上
101 52 201801 100
・・・ ・・・ ・・・ ・・・
110 23 201812 300
集
計
処
理
101 52 201801 100
・・・ ・・・ ・・・ ・・・
110 23 201212 300
バッファプール
101 52 201801 100
・・・ ・・・ ・・・ ・・・
110 23 201212 300
集計に不要な列
もI/Oされる
集計に不要な列
もキャッシュされる
注文ID 商品ID 日付 売上
101 52 201801 100
・・・ ・・・ ・・・ ・・・
110 23 201812 300
集
計
処
理
100
・・・
300
バッファプール
100
・・・
300
行ストア
列ストア
集計に不要
な列はキャッ
シュされない
同じ属性なので
圧縮効果が高く
I/O負荷軽減
列ストア インデックス: 概要 - SQL Server | Microsoft Docs
VS 集計に不要な列
はI/Oされない
オプティマイザー 物理プラン
選手番号 選手名 利き腕 チーム番号
1 AAA 右 1
2 BBB 左 1
3 CCC 右 3
4 DDD 右 2
5 EEE 右 1
6 FFF 左 2
チーム番号 チーム名
1 XXX
2 YYY
3 ZZZ
SELECT 選手名, チーム名 FROM 選手, チーム
WHERE 選手.チーム番号 = チーム.チーム番号 AND 利き腕 = ‘左’
選手表から
利き腕が”左”の行を「選択」
上の結果とチーム表を
チーム番号で「結合」
選手名とチーム名を「射影」
4×2
6×2
2×2
選
手
表
チ
ー
ム
表
論理プラン 物理プラン
2×2
Index Seek Table Scan
Nested Loop Join Hash Join
Filter
Merge Join
クエリトランスフォーム
エスティメート
プランジェネレート
統計情報
時間余
エクゼキューター
パーサー
構文ツリー
論理プラン
カタログ
物理プラン
物理プラン
オプティマイザー
Nested Loop Join Hash Join
Merge Join
一方のテーブルの対象データ量が少
ない場合やインデックスで絞り込め
る場合に最適(主としてOLTP)
結合列が並び替えられている
大量の入力を処理する場合
に最適(主としてOLAP)
結合 (SQL Server) - SQL Server | Microsoft Docs
結合列が並び替えられていない
大量の入力を処理する場合に
最適(主としてOLAP)
オプティマイザー 物理プラン
選手番号 選手名 利き腕 チーム番号
1 AAA 右 1
2 BBB 左 1
3 CCC 右 3
4 DDD 右 2
5 EEE 右 1
6 FFF 左 2
チーム番号 チーム名
1 XXX
2 YYY
3 ZZZ
SELECT 選手名, チーム名 FROM 選手, チーム
WHERE 選手.チーム番号 = チーム.チーム番号 AND 利き腕 = ‘左’
選手表から
利き腕が”左”の行を「選択」
上の結果とチーム表を
チーム番号で「結合」
選手名とチーム名を「射影」
4×2
6×2
2×2
選
手
表
チ
ー
ム
表
論理プラン 物理プラン
2×2
Index Seek Table Scan
Nested Loop Join Hash Join
Filter
Merge Join
クエリトランスフォーム
エスティメート
プランジェネレート
統計情報
時間余
エクゼキューター
パーサー
構文ツリー
論理プラン
カタログ
物理プラン
物理プラン
オプティマイザー
密度
ヒストグラム
行数、ページ数
カーディナリティ
カーディナリティ推定値
推定コスト
物理プランA 物理プランB
物理プランA 物理プランB
統計 - SQL Server | Microsoft Docs
カーディナリティが高いほどインデックスの利用効果が高い
逆に低い場合はフルスキャンが効率的
カーディナリティが高い例:性別、都道府県 etc…
カーディナリティが低い例:会員番号、更新日時 etc…
コストの大小=プランの良し悪し
処理される推定行(カーディナリティ×行数やページ数)
統計情報
コスト=10 コスト=25
カーディナリティ推定 (SQL Server) - SQL Server | Microsoft Docs
※. SQLDBはクエリ実行の中で統計情報の鮮度
を確認し古いと判断した場合は統計情報を更
新したのちクエリ処理を再開する(クエリ処理と
同期せず非同期に更新させることもパラメータ設
定で可能)。
オプティマイザー
ヒント (Transact-SQL) - SQL Server | Microsoft Docs
例)結合順序の指定
SELECT * FROM A
JOIN B on A.ID = B.ID
JOIN C on A.ID = C.ID
OPTION (FORCE ORDER)
例)利用したい結合方式の指定
SELECT * FROM A
JOIN B on A.ID = B.ID
OPTION (MERGE JOIN)
例)実行プランを直指定
SELECT * FROM A
JOIN B on A.ID = B.ID
OPTION (USE PLAN N’XMLフォーマットのプラン’)
例)利用したいインデックスの指定
SELECT * FROM A
WHERE IDX_A_COL = 1
OPTION (TABLE HINT(A, INDEX(IDX_A_COL)))
オプティマイザー 物理プラン
選手番号 選手名 利き腕 チーム番号
1 AAA 右 1
2 BBB 左 1
3 CCC 右 3
4 DDD 右 2
5 EEE 右 1
6 FFF 左 2
チーム番号 チーム名
1 XXX
2 YYY
3 ZZZ
SELECT 選手名, チーム名 FROM 選手, チーム
WHERE 選手.チーム番号 = チーム.チーム番号 AND 利き腕 = ‘左’
選手表から
利き腕が”左”の行を「選択」
上の結果とチーム表を
チーム番号で「結合」
選手名とチーム名を「射影」
4×2
6×2
2×2
選
手
表
チ
ー
ム
表
論理プラン 物理プラン
2×2
Index Seek Table Scan
Nested Loop Join Hash Join
Filter
Merge Join
クエリトランスフォーム
エスティメート
プランジェネレート
統計情報
時間余
エクゼキューター
パーサー
構文ツリー
論理プラン
カタログ
物理プラン
物理プラン
エクゼキューター
オプティマイザー
物理プラン
パーサー
構文ツリー
メモリ確保
ストレージI/O
CPU演算
結果送信
ユーザーによるチューニング関与
パーサー
クエリ
結果
オプティマイザー
エクゼキューター
クエリ処理 ユーザーによるチューニング余地は限定的
(プランキャッシュヒットを上げることによるCPU負荷低減くらい)
パーサー
ユーザーの関与によってより望ましいプランを生成させる
(=クエリのTAT短縮 & 非効率なリソース消費の回避)
オプティマイザー
インスタンスチューニング(並列処理に伴う各種待機の改善など)
リソーススケール(リソースネックが明らかな場合)
エクゼキューター
② クエリストアの活用
クエリストアとは
クエリのストアを使用した、パフォーマンスの監視 - SQL Server | Microsoft Docs
クエリストアの設定
ALTER DATABASE の SET オプション (Transact-SQL) - SQL Server | Microsoft Docs
クエリ ストアを使用する際のベスト プラクティス - SQL Server | Microsoft Docs
クエリストアのしくみ
パーサー
クエリ
結果
オプティマイザー
エクゼキューター
クエリ処理
Query Store
実行統計
実行プラン
フラッシュのインターバルはパラメータ
DATA_FLUSH_INTERNAL_SECONDSで制御
可能(耐久性とI/Oオーバヘッドのトレー
ドオフ)
sp_query_store_flush_dbで明示的強制フ
ラッシュ可能
クエリ ストアでデータを収集する方法 - SQL Server | Microsoft Docs
クエリストア内の実行プランを指定することも可
能(sp_query_store_force_planもしくはGUI
で該当プランを指定する)
On Memory
非同期ライトバック
クエリストア用GUI分析ツール
クエリのストアを使用した、パフォーマンスの監視 - SQL Server
| Microsoft Docs
Query Performance Insight - Azure SQL Database
| Microsoft Docs
(SQL Server Management Studio) (Query Performance Insight)
クエリストア用カタログ
クエリストアカタログビュー (Transact-sql) - SQL Server | Microsoft Docs
③ DB運用における日常の営み
メトリック、ログ
クエリストア
DMV
インデックスの定期メンテナンス
✓ データの変更に伴いデータが断片化
→ I/O量やインストラクションが増大
→ 性能劣化
性能劣化予防のために定期的にデフラグする
キー ポインタ
10 ・・・
20 ・・・
30 ・・・
40 ・・・
50 ・・・
60 ・・・
キー ポインタ
10 ・・・
20 ・・・
21 ・・・
30 ・・・
キー ポインタ
40 ・・・
50 ・・・
60 ・・・
INSERT
キー = 21
インデックスの定期メンテナンス
⚫ 内部断片化
➢ ページ内のデータの装填率(密度)を示す
➢ 未使用領域分の余計なI/Oによるアクセス効率低下
• FILLFACTORで装填率をコントロールしているケースも
断片化の種類とその影響
同じデータ量に対して装填率100%
と50%ではI/Oページ量が2倍になる
装填率
100%
装填率
50%
vs
インデックスの定期メンテナンス
⚫ 外部断片化
➢ インデックス論理順とファイル内物理順の不一致度合いを示す
➢ データ順が不連続状態になることでのアクセス効率低下
断片化の種類とその影響
10
- 11
index
record
11
10 20
index
record
12
p n
index
record
13
p n
index
record
14
p n
index
record
15
26 16
index
record
16
15 17
index
record
17
16 18
index
record
18
p n
index
record
19
p n
index
record
20
11 23
index
record
21
p n
index
record
22
p n
index
record
23
20 24
index
record
24
23 15
index
record
25
p n
index
record
~
ファイル内の物理番地 →
インデックスの論理順 →
インデックスAの断片
(大きさ=2)
インデックスAの断片
(大きさ=3)
インデックスAの断片
(大きさ=1)
インデックスAの断片化率 = =37.5%
3(赤線ホップ数)
8(総ページ数)
インデックスAの断片
(大きさ=2)
インデックスの定期メンテナンス
⚫ 再構成( ALTER INDEX REORGANIZE )
➢ すでに確保したエクステントの中で断片化を解消する(内部断片化と論理断片化を解消する)。
⚫ 再構築( ALTER INDEX REBUILD )
➢ 新たなエクステントを確保して断片化を解消する(エクステントの断片化を含むすべての断片化を解消する。ただし、エクス
テントの断片化解消はどれだけ連続領域が確保できたかに依存する) 。
➢ 再構築は最大精度(サンプリング100%)での統計情報の更新を伴う。よって再構築直後の統計情報更新は不要。
※. 使い分けの目安(参考)
➢ 再構成:外部断片化率(avg_fragmentation_in_percent)が5~30%
➢ 再構築:外部断片化率(avg_fragmentation_in_percent)30%~ または エクステントごとの平均ページ数
(avg_fragment_size_in_pages)が8未満
断片化解消の2つのモード
インデックスの定期メンテナンス
DROP INDEX自動チューニング
統計情報の定期メンテナンス
⚫ 統計情報のメンテナンス挙動把握が大事。
➢ 作成されるタイミング
• Indexが作成された列WHERE句やJOIN句の条件となった列に対して自動作成(手動作成も可)
➢ 更新されるタイミング
• ざっくり目安としてレコード数の20%に相当するレコードが変更されたタイミングで自動更新(レコード数増加に
応じて20%閾値は自動調整される)。
• インデックス再構築の延長でサンプリング100%で自動更新
統計情報の精度・鮮度は性能に直結
統計情報の定期メンテナンス
参考:統計情報の自動更新の詳細
① クエリプラン
キャッシュの検索
.
② 関連する統計情報
のロード
成功
③b 古い統計情報
はあるか
Yes
No
③a 更新が必要な統計情報すべてを更新
④ クエリプラン生成とリコンパイル閾値の設定
⑤ クエリプランのテスト(スキーマチェック)
⑥ スキーマは
有効か
Yes
⑦ 新しい統計情報
を使用できるか
⑧ 古い統計情報は
あるか
⑨ クエリ
実行
Yes Yes
No No
⑩ リコンパイル
No
失敗
.
S E
クエリ
実行開始
クエリ
実行終了
統計情報の定期メンテナンス
計画的な手動更新が必要となる場合もある
一定のデータ量 手動更新不要
一貫した増加傾向 基本的には自動更新に任せておけばよい
一定間隔の増減反復 バッチ処理のような大量更新を行う場合は手動更新
とセットで実施(初回クエリが犠牲にならないように)
データの分布変化 自動更新しきい値未満でもデータ分布に影響を与える
変更を行う場合は手動更新をセットで実施
新しいデータの参照 新しいデータのみ参照したい場合は手動更新をセット
で実施
統計情報の自動更新が ON の時には統計情報を手動で更新する必要はない? | Microsoft Docs
まとめ
Q&A
© 2022 Microsoft Corporation. All rights reserved.
本情報の内容(添付文書、リンク先などを含む)は、作成日時点でのものであり、予告なく変更される場合があります。

SQL Server チューニング基礎