チューニングは推測で始めない
DBチューニングでよくある失敗は、遅い理由を見ずにインデックスを貼ることです。
スロークエリ改善は、発見、分析、仮説、修正、計測、監視の順で進めます。
速くなった気がする、ではなく、改善前後の数値で判断します。
全体の流れ
1. 遅いクエリを見つける
2. 実行頻度と影響範囲を見る
3. EXPLAIN / EXPLAIN ANALYZE を読む
4. 原因を仮説化する
5. SQLまたはインデックスを修正する
6. 改善前後を測る
7. 監視して再発を防ぐ
1回だけ遅いクエリと、毎秒大量に呼ばれる少し遅いクエリでは、優先度が変わります。
Step 1: 遅いクエリを見つける
PostgreSQLなら、スロークエリログや pg_stat_statements を使います。
見る指標:
- 平均実行時間
- p95 / p99
- 呼び出し回数
- 合計時間
- 読み取りブロック数
- 返却行数
- 一時ファイル使用
平均だけでは見落とします。普段は速いが、特定条件で極端に遅いクエリもあります。
Step 2: EXPLAINを見る
まず実行計画を見ます。
EXPLAIN
SELECT *
FROM orders
WHERE user_id = 123
ORDER BY created_at DESC
LIMIT 20;
実測も見る場合:
EXPLAIN ANALYZE
SELECT *
FROM orders
WHERE user_id = 123
ORDER BY created_at DESC
LIMIT 20;
本番で EXPLAIN ANALYZE を使うと実際にクエリが実行されます。更新系SQLでは特に注意します。必要ならステージングやトランザクション内で確認します。
Step 3: よく見るポイント
実行計画では、次を見ます。
| 観点 | 見ること |
|---|---|
| Seq Scan | 全件スキャンしていないか |
| Index Scan | 期待したインデックスが使われているか |
| rows | 推定行数と実測行数が大きくズレていないか |
| Sort | 大量ソートしていないか |
| Hash Join / Nested Loop | JOIN方式が妥当か |
| Filter | インデックスで絞れず後段で捨てていないか |
| Buffers | 読み取り量が大きすぎないか |
特に、推定行数と実測行数のズレは重要です。統計情報が古い、条件の偏りが大きい、複合条件をDBが見積もれていない、などが原因になります。
Step 4: インデックスを考える
例のクエリ:
SELECT *
FROM orders
WHERE user_id = 123
ORDER BY created_at DESC
LIMIT 20;
候補:
CREATE INDEX idx_orders_user_created
ON orders(user_id, created_at DESC);
このインデックスは、user_id で絞り、created_at DESC の順で取り出す目的に合います。
インデックス設計では、次を確認します。
WHEREの等価条件- 範囲条件
ORDER BYLIMIT- JOIN条件
- 返す列
- 書き込み頻度
インデックスは読み取りを速くする一方で、INSERT/UPDATE/DELETEを重くします。不要なインデックスは負債です。
Step 5: SQLを書き換える
インデックスだけでなく、SQL自体を変える方が効くこともあります。
例: 不要な SELECT * をやめる
SELECT id, total_amount, created_at
FROM orders
WHERE user_id = 123
ORDER BY created_at DESC
LIMIT 20;
例: N+1をJOINやINにする
SELECT *
FROM orders
WHERE user_id IN (1, 2, 3, 4);
例: 集計対象を先に絞る
SELECT user_id, COUNT(*)
FROM orders
WHERE created_at >= now() - interval '30 days'
GROUP BY user_id;
DBに大量データを返してアプリ側で絞る設計は、データ量が増えるほど厳しくなります。
Step 6: ロックと書き込みも見る
遅さはSELECTだけではありません。
見るもの:
- ロック待ち
- 長いトランザクション
- 大量UPDATE
- 外部キー制約チェック
- インデックス更新負荷
- コネクション枯渇
更新系の遅さは、単純なインデックス追加では解決しないことがあります。トランザクションを短くする、バッチサイズを小さくする、対象行を先に絞る、といった対策が必要です。
Step 7: 改善前後を測る
改善したら、必ず前後を比較します。
記録するもの:
変更前:
avg 850ms
p95 1800ms
calls/min 120
plan: Seq Scan + Sort
変更後:
avg 40ms
p95 90ms
calls/min 120
plan: Index Scan
ローカルで速くなっただけでは不十分です。ステージングや本番に近いデータ量で見ます。
Step 8: 監視する
チューニングは一度直して終わりではありません。
監視するもの:
- スロークエリ件数
- クエリ時間のp95 / p99
- DB CPU
- メモリ
- ディスクI/O
- コネクション数
- ロック待ち
- レプリカ遅延
- エラー率
データ量や利用者が増えると、以前は速かったSQLが遅くなることがあります。スキーマ変更や機能追加でクエリパターンが変わることもあります。
よくあるアンチパターン
| アンチパターン | 問題 |
|---|---|
| 遅いからとりあえずインデックス | 原因が違うと効かない |
| すべての列にインデックス | 書き込みが重くなる |
SELECT * を放置 | 不要なI/Oが増える |
| N+1を放置 | API全体が遅くなる |
| 本番データ量で見ない | 小規模では問題が見えない |
| 監視しない | 再発に気づけない |
| 平均だけ見る | p95/p99の悪化を見逃す |
まとめ
スロークエリ改善は、感覚ではなく計測で進めます。
- 遅いクエリを見つける
- 実行頻度と影響範囲を見る
- EXPLAINで実行計画を読む
- インデックスとSQLの両方を検討する
- ロックや書き込み負荷も見る
- 改善前後を数値で比較する
- 監視して再発を防ぐ
チューニング力は、SQL、テーブル設計、運用監視がつながった総合力です。