なぜSQLが遅くなるのか
アプリケーションのパフォーマンス問題の多くはデータベースのクエリに起因します。適切なインデックス設計と実行計画の理解が、SQL最適化の鍵です。
インデックスの基本
インデックスは本の索引と同じで、特定の列の値を高速に検索するためのデータ構造(B-Tree)です。
-- インデックス作成
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at);
-- 複合インデックスは左端から使われる
-- idx_orders_user_date は以下に有効:
-- WHERE user_id = 5
-- WHERE user_id = 5 AND created_at > '2024-01-01'
-- 以下には無効:
-- WHERE created_at > '2024-01-01' ← user_idなし
EXPLAINで実行計画を読む
EXPLAIN ANALYZE
SELECT u.name, COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > '2024-01-01'
GROUP BY u.id, u.name;
見るべきポイント
| キーワード | 意味 |
|---|---|
Seq Scan |
テーブル全スキャン(遅い) |
Index Scan |
インデックス使用(速い) |
Hash Join |
ハッシュ結合 |
Nested Loop |
ネストループ(小テーブル向き) |
rows= |
推定行数 |
cost= |
相対コスト |
よくあるアンチパターンと対策
-- NG: WHERE句で関数使用(インデックス無効化)
SELECT * FROM users WHERE YEAR(created_at) = 2024;
-- OK: 範囲で書く
SELECT * FROM users
WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';
-- NG: SELECT *(不要な列を取得)
SELECT * FROM users;
-- OK: 必要な列のみ
SELECT id, name, email FROM users;
-- NG: N+1問題
for user in users:
orders = "SELECT * FROM orders WHERE user_id = ?" # ユーザー数分クエリ発行
-- OK: JOINでまとめて取得
SELECT u.id, u.name, o.id, o.total
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
クエリ最適化チェックリスト
EXPLAIN ANALYZEで実行計画確認- Seq Scanを探してインデックス追加検討
SELECT *を必要列のみに絞る- サブクエリをJOINに変換検討
- 大量データは
LIMITでページネーション - 定期的な
VACUUM ANALYZE(PostgreSQL)
適切なSQLを書く力はどんなシステムでも求められる普遍的なスキルです。





