LESSON 30分

ストーリー

「インデックスを貼ったのに速くなりません…」

あなたが困っていると、高橋アーキテクトが一言。

「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分