ストーリー
「インデックスを貼ったのに速くなりません…」
あなたが困っていると、高橋アーキテクトが一言。
「EXPLAIN ANALYZEは見たか? インデックスを貼っただけでは安心できない。実行計画を読めなければ、最適化は勘頼みになる。データベースがクエリをどう解釈しているか、その目で確認するんだ」
EXPLAIN と EXPLAIN ANALYZE
EXPLAIN: 推定実行計画
EXPLAIN SELECT * FROM users WHERE email = 'user@example.com';
-- 結果例
-- Index Scan using idx_users_email on users (cost=0.42..8.44 rows=1 width=128)
-- Index Cond: ((email)::text = 'user@example.com'::text)
EXPLAIN ANALYZE: 実際の実行結果
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'user@example.com';
-- 結果例
-- Index Scan using idx_users_email on users
-- (cost=0.42..8.44 rows=1 width=128)
-- (actual time=0.025..0.026 rows=1 loops=1)
-- Index Cond: ((email)::text = 'user@example.com'::text)
-- Planning Time: 0.085 ms
-- Execution Time: 0.045 ms
実行計画の読み方
基本要素
Index Scan using idx_users_email on users
(cost=0.42..8.44 rows=1 width=128)
(actual time=0.025..0.026 rows=1 loops=1)
| 要素 | 意味 |
|---|---|
| Index Scan | スキャン方式 |
| cost=0.42..8.44 | 推定コスト(開始..合計) |
| rows=1 | 推定行数 |
| width=128 | 推定行サイズ(バイト) |
| actual time=0.025..0.026 | 実際の時間(ms) |
| rows=1 | 実際の行数 |
| loops=1 | 実行回数 |
主要なスキャン方式
| スキャン方式 | 説明 | 速度 |
|---|---|---|
| Seq Scan | 全行スキャン | 遅い(大テーブル) |
| Index Scan | インデックス経由 | 速い |
| Index Only Scan | インデックスのみで完結 | 最速 |
| Bitmap Index Scan | ビットマップで候補抽出 | 中間 |
パターン別の実行計画
パターン1: Seq Scan(フルスキャン)
EXPLAIN ANALYZE SELECT * FROM orders WHERE total > 1000;
-- Seq Scan on orders (cost=0.00..25000.00 rows=50000 width=64)
-- Filter: (total > 1000)
-- Rows Removed by Filter: 950000
-- Execution Time: 250.000 ms
改善策: total にインデックスを追加
CREATE INDEX idx_orders_total ON orders(total);
パターン2: Index Scan
EXPLAIN ANALYZE SELECT * FROM users WHERE id = 42;
-- Index Scan using users_pkey on users
-- (cost=0.42..8.44 rows=1 width=128)
-- (actual time=0.020..0.021 rows=1 loops=1)
-- Index Cond: (id = 42)
-- Execution Time: 0.040 ms
パターン3: Nested Loop Join
EXPLAIN ANALYZE
SELECT u.name, o.total
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE u.id = 1;
-- Nested Loop (cost=0.85..24.95 rows=5 width=132)
-- -> Index Scan using users_pkey on users u
-- (cost=0.42..8.44 rows=1 width=104)
-- Index Cond: (id = 1)
-- -> Index Scan using idx_orders_user_id on orders o
-- (cost=0.43..16.47 rows=5 width=36)
-- Index Cond: (user_id = 1)
-- Execution Time: 0.080 ms
パターン4: Hash Join(大量データの結合)
EXPLAIN ANALYZE
SELECT u.name, COUNT(o.id)
FROM users u
JOIN orders o ON o.user_id = u.id
GROUP BY u.name;
-- HashAggregate (cost=35000.00..35100.00 rows=10000 width=112)
-- Group Key: u.name
-- -> Hash Join (cost=300.00..30000.00 rows=1000000 width=108)
-- Hash Cond: (o.user_id = u.id)
-- -> Seq Scan on orders o
-- -> Hash
-- -> Seq Scan on users u
-- Execution Time: 500.000 ms
危険なサイン
実行計画で以下を見つけたら改善のチャンス。
| 危険サイン | 意味 | 対策 |
|---|---|---|
| Seq Scan(大テーブル) | インデックスが使われていない | インデックス追加 |
| Rows Removed by Filter が大きい | 不要な行を大量にスキャン | WHERE句の最適化 |
| 推定rows と実際のrows が大幅に乖離 | 統計情報が古い | ANALYZE実行 |
| Sort Method: external merge | メモリ不足でディスクソート | work_mem増加 or インデックス |
| Nested Loop(大量データ) | N+1問題の可能性 | Hash Join誘導 or クエリ改善 |
統計情報の更新
-- テーブルの統計情報を更新
ANALYZE users;
ANALYZE orders;
-- 統計情報の確認
SELECT
relname,
n_live_tup,
n_dead_tup,
last_analyze
FROM pg_stat_user_tables
WHERE relname IN ('users', 'orders');
TypeScriptでの実行計画確認
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// Prisma で EXPLAIN ANALYZE を実行
async function analyzeQuery() {
const result = await prisma.$queryRaw`
EXPLAIN ANALYZE
SELECT * FROM users
WHERE email = 'user@example.com'
`;
console.log(result);
}
// クエリパフォーマンスのログ
const prismaWithLogging = new PrismaClient({
log: [
{ level: 'query', emit: 'event' },
],
});
prismaWithLogging.$on('query', (e) => {
console.log(`Query: ${e.query}`);
console.log(`Duration: ${e.duration}ms`);
});
まとめ
| ポイント | 内容 |
|---|---|
| EXPLAIN | 推定実行計画を表示 |
| EXPLAIN ANALYZE | 実際に実行して結果を表示 |
| 読み方 | スキャン方式、cost、rows、actual time を確認 |
| 危険サイン | Seq Scan、大量の Rows Removed、統計乖離 |
| 対策 | インデックス追加、ANALYZE、クエリ改善 |
理解度チェックリスト
- EXPLAIN と EXPLAIN ANALYZE の違いを説明できる
- 実行計画の各要素(cost, rows, actual time)を読める
- Seq Scan, Index Scan, Index Only Scan の違いを説明できる
- 実行計画から改善ポイントを見つけられる
次のステップ
次のレッスンではクエリチューニングの技法を学ぶ。実行計画で問題を発見した後、どう改善するかの具体的な手法を身につけよう。
推定読了時間: 30分