Skip to content
Published on

データベース基礎完全ガイド — SQL、正規化、インデックス、トランザクション、NoSQL

Authors

1. データベースとは

データベース(Database)は、構造化されたデータを保存・管理するシステムである。現代のソフトウェアにおいてデータの保存と検索は中核機能であり、適切なデータベースの選定と設計がアプリケーションの性能とスケーラビリティを左右する。

RDBMS vs NoSQL

リレーショナルデータベース(RDBMS)とNoSQLは根本的に異なる思想を持つ。

項目RDBMSNoSQL
データモデルテーブル(行と列)ドキュメント、キーバリュー、カラム、グラフ
スキーマ固定スキーマ柔軟なスキーマ
スケーリング垂直(スケールアップ)水平(スケールアウト)
トランザクションACID保証BASE(一部ACID対応)
クエリ言語SQL各DB固有のAPI
適したケース複雑な関係、整合性重視大量データ、柔軟なスキーマが必要

主要データベース比較

DB種類特徴主な用途
PostgreSQLRDBMSJSONB、拡張性、豊富なデータ型汎用
MySQLRDBMS高い互換性、InnoDBエンジンWebサービス
MongoDBDocument柔軟なスキーマ、集約パイプラインコンテンツ管理
RedisKey-Valueインメモリ、超高速キャッシュ、セッション
CassandraWide Column高い書き込みスループットIoT、時系列
Neo4jGraph関係探索に最適化ソーシャルネットワーク

2. SQL基礎

SQL(Structured Query Language)はリレーショナルデータベースを操作するための標準言語である。

SELECTと基本構文

-- 基本的なクエリ
SELECT name, email, created_at
FROM users
WHERE status = 'active'
ORDER BY created_at DESC
LIMIT 10;
-- エイリアスの活用
SELECT
    u.name AS user_name,
    COUNT(o.id) AS order_count,
    SUM(o.total_amount) AS total_spent
FROM users u
JOIN orders o ON u.id = o.user_id
GROUP BY u.name;

JOINの種類

JOINは複数テーブルのデータを結合する中核的な操作である。

-- INNER JOIN: 両方に一致する行のみ
SELECT u.name, o.order_date
FROM users u
INNER JOIN orders o ON u.id = o.user_id;

-- LEFT JOIN: 左テーブル全体 + 一致する右側
SELECT u.name, o.order_date
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;

-- RIGHT JOIN: 右テーブル全体 + 一致する左側
SELECT u.name, o.order_date
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;

-- FULL OUTER JOIN: 両方全て含む
SELECT u.name, o.order_date
FROM users u
FULL OUTER JOIN orders o ON u.id = o.user_id;

JOIN選択ガイド:

  • INNER JOIN -- 両方に存在するデータだけが必要な場合
  • LEFT JOIN -- 基準テーブルの全行を保持する必要がある場合
  • RIGHT JOIN -- LEFT JOINで代替可能なため、実務では稀
  • FULL OUTER JOIN -- 両方の全データを統合する場合

GROUP BYとHAVING

-- 平均給与が500万円以上の部署
SELECT
    department,
    COUNT(*) AS employee_count,
    AVG(salary) AS avg_salary
FROM employees
GROUP BY department
HAVING AVG(salary) >= 5000000
ORDER BY avg_salary DESC;

WHEREは個々の行をフィルタリングし、HAVINGはグループ化された結果をフィルタリングする。

サブクエリ

-- 平均給与より高い給与の社員
SELECT name, salary
FROM employees
WHERE salary > (
    SELECT AVG(salary) FROM employees
);

-- EXISTSを使ったサブクエリ
SELECT u.name
FROM users u
WHERE EXISTS (
    SELECT 1 FROM orders o
    WHERE o.user_id = u.id
    AND o.created_at >= '2026-01-01'
);

-- INを使ったサブクエリ
SELECT name, department
FROM employees
WHERE department IN (
    SELECT department
    FROM employees
    GROUP BY department
    HAVING COUNT(*) > 10
);

3. 正規化(Normalization)

正規化はデータの冗長性を削減し、整合性を高めるためにテーブルを分離するプロセスである。

第1正規形(1NF)

全てのカラム値がアトミック(不可分)でなければならない。1つのセルに複数の値は入れない。

-- 1NF違反: phone_numbersに複数値を格納
-- name: '田中太郎', phone_numbers: '090-1234-5678, 090-9876-5432'

-- 1NF準拠: 別テーブルに分離
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

CREATE TABLE phone_numbers (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id),
    phone VARCHAR(20) NOT NULL
);

第2正規形(2NF)

1NFを満たし、部分関数従属(Partial Dependency)がない。複合キーの一部にのみ従属するカラムを分離する。

-- 2NF違反: student_nameはstudent_idにのみ従属
-- (student_id, course_id) -> grade  (OK)
-- student_id -> student_name  (部分従属!)

-- 2NF準拠
CREATE TABLE students (
    student_id SERIAL PRIMARY KEY,
    student_name VARCHAR(100) NOT NULL
);

CREATE TABLE enrollments (
    student_id INTEGER REFERENCES students(student_id),
    course_id INTEGER REFERENCES courses(course_id),
    grade CHAR(2),
    PRIMARY KEY (student_id, course_id)
);

第3正規形(3NF)

2NFを満たし、推移的関数従属(Transitive Dependency)がない。

-- 3NF違反: department_nameはdepartment_idを経由して推移的に従属
-- employee_id -> department_id -> department_name

-- 3NF準拠
CREATE TABLE departments (
    department_id SERIAL PRIMARY KEY,
    department_name VARCHAR(100) NOT NULL
);

CREATE TABLE employees (
    employee_id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    department_id INTEGER REFERENCES departments(department_id)
);

BCNF(ボイス・コッド正規形)

3NFを満たし、全ての決定子が候補キーである形式。3NFより厳密な条件を適用する。

非正規化はいつ行うか

非正規化(Denormalization)は検索性能のために意図的に冗長性を許容することである。

非正規化を検討するケース:

  • 読み取りが書き込みより圧倒的に多い場合
  • 複雑なJOINが繰り返し発生する場合
  • リアルタイム集計が必要な場合
  • キャッシュだけでは性能要件を満たせない場合
-- 非正規化の例: 注文テーブルにユーザー名を重複格納
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id),
    user_name VARCHAR(100),  -- 非正規化: 読み取り時のJOINを排除
    total_amount DECIMAL(10, 2),
    created_at TIMESTAMP DEFAULT NOW()
);

4. インデックス(Index)

インデックスはデータ検索速度を向上させるデータ構造である。書籍の索引と同じ役割を果たす。

B-Treeインデックス

最も汎用的なインデックスタイプ。範囲検索、ソート、等値比較の全てに効果的。

-- 単一カラムインデックス
CREATE INDEX idx_users_email ON users(email);

-- インデックスの使用例
SELECT * FROM users WHERE email = 'test@example.com';  -- インデックス使用
SELECT * FROM users WHERE email LIKE 'test%';           -- インデックス使用
SELECT * FROM users WHERE email LIKE '%test';           -- インデックス未使用(前方ワイルドカード)

Hashインデックス

等値比較(=)にのみ使用可能。範囲検索は不可だが、等値比較ではB-Treeより高速な場合がある。

CREATE INDEX idx_users_email_hash ON users USING HASH (email);

GINインデックス(Generalized Inverted Index)

配列、JSONB、全文検索に適したインデックス。

-- JSONBカラムにGINインデックス
CREATE INDEX idx_products_tags ON products USING GIN (tags);

-- 全文検索用GINインデックス
CREATE INDEX idx_articles_search ON articles USING GIN (
    to_tsvector('japanese', title || ' ' || content)
);

GiSTインデックス(Generalized Search Tree)

空間データ、範囲型、全文検索に使用する。

-- 空間データインデックス
CREATE INDEX idx_locations_coords ON locations USING GIST (coordinates);

複合インデックス

複数のカラムを組み合わせたインデックス。カラムの順序が非常に重要である。

-- 複合インデックス: 先頭カラムからマッチング
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at);

-- このインデックスが効果的なクエリ:
SELECT * FROM orders WHERE user_id = 1;                          -- 使用
SELECT * FROM orders WHERE user_id = 1 AND created_at > '2026-01-01'; -- 使用
SELECT * FROM orders WHERE created_at > '2026-01-01';            -- 未使用(先頭カラム欠落)

カバリングインデックス

クエリに必要な全カラムをインデックスに含め、テーブルアクセスなしにインデックスのみで結果を返す。

-- カバリングインデックス
CREATE INDEX idx_orders_covering ON orders(user_id, created_at, total_amount);

-- インデックスのみで処理可能(Index Only Scan)
SELECT user_id, created_at, total_amount
FROM orders
WHERE user_id = 1 AND created_at > '2026-01-01';

インデックス設計原則

  1. カーディナリティ(ユニーク値の数)が高いカラムにインデックスを作成する
  2. WHERE、JOIN、ORDER BYで頻繁に使用されるカラムを優先する
  3. 書き込み性能とストレージ容量のトレードオフを考慮する
  4. 複合インデックスでは選択性が高いカラムを先頭に置く

5. トランザクション(Transaction)

トランザクションはデータベースの状態を変化させる1つの論理的な作業単位である。

ACID属性

  • Atomicity(原子性) -- トランザクション内の全操作は全て成功するか全て失敗する
  • Consistency(一貫性) -- トランザクション前後でデータベースは一貫した状態を維持する
  • Isolation(分離性) -- 同時に実行されるトランザクションは互いに影響を与えない
  • Durability(耐久性) -- 完了したトランザクションの結果は永続的に保存される
-- 口座振替トランザクションの例
BEGIN;

UPDATE accounts SET balance = balance - 100000
WHERE account_id = 'A001';

UPDATE accounts SET balance = balance + 100000
WHERE account_id = 'B001';

-- 残高確認
SELECT balance FROM accounts WHERE account_id = 'A001';

COMMIT;  -- または問題発生時は ROLLBACK;

分離レベル(Isolation Level)

分離レベルが高いほどデータ整合性は上がるが、同時実行性は下がる。

分離レベルDirty ReadNon-Repeatable ReadPhantom Read性能
Read Uncommittedありありあり最高
Read Committedなしありあり高い
Repeatable Readなしなしあり普通
Serializableなしなしなし低い
-- 分離レベルの設定
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

BEGIN;
SELECT balance FROM accounts WHERE account_id = 'A001';
-- このトランザクション中、他のトランザクションの変更は見えない
COMMIT;

各分離レベルの問題:

  • Dirty Read -- コミットされていないデータを読み取る現象
  • Non-Repeatable Read -- 同じクエリを2回実行した時に結果が異なる現象
  • Phantom Read -- 同じ条件で検索した時に新しい行が出現する現象

デッドロック(Deadlock)

2つのトランザクションが互いに相手が保持するロックを待ち続け、永久に待機する状態。

-- デッドロックシナリオ
-- トランザクション1: A -> B の順でロック
-- トランザクション2: B -> A の順でロック

-- 解決方法:
-- 1. ロック順序を統一する(常にIDが小さい方から)
-- 2. タイムアウトを設定する
-- 3. トランザクション範囲を最小化する

SET lock_timeout = '5s';

6. クエリ最適化

遅いクエリはサービス全体の性能を低下させる。体系的な最適化が必須である。

EXPLAIN ANALYZE

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 >= '2026-01-01'
GROUP BY u.name
ORDER BY order_count DESC
LIMIT 10;

実行計画で主に確認する項目は以下の通り。

  • Seq Scan -- テーブル全体スキャン。大規模テーブルで発生する場合はインデックスが必要
  • Index Scan -- インデックスを使用した検索。効率的
  • Index Only Scan -- インデックスのみでデータを返す。最も効率的
  • Nested Loop -- 小規模データのJOINに適する
  • Hash Join -- 中大規模データのJOINに適する
  • Merge Join -- ソート済み大規模データのJOINに適する
  • Actual Time -- 実際の実行時間
  • Rows -- 推定行数 vs 実際行数の差異を確認

N+1問題

N+1問題は、1回のクエリの後にN回の追加クエリが発生する非効率的なパターンである。

-- N+1問題の発生
-- 1回: SELECT * FROM users LIMIT 100;
-- 100回: SELECT * FROM orders WHERE user_id = ?;  (各ユーザーごと)

-- 解決: JOINで一度に取得
SELECT u.*, o.*
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id IN (SELECT id FROM users LIMIT 100);

-- またはサブクエリを活用
SELECT u.*, (
    SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id
) AS order_count
FROM users u
LIMIT 100;

クエリ最適化チェックリスト

  1. 不必要なSELECT *の代わりに必要なカラムのみ指定する
  2. WHERE句で関数を使用するとインデックスが無効化される可能性がある
  3. LIKE検索で前方ワイルドカードを避ける
  4. 大量INSERTはバッチで処理する
  5. 適切なインデックスを作成する
-- 悪い例: インデックス無効化
SELECT * FROM users WHERE YEAR(created_at) = 2026;

-- 良い例: インデックス活用可能
SELECT * FROM users
WHERE created_at >= '2026-01-01'
  AND created_at < '2027-01-01';

7. PostgreSQL応用

PostgreSQLは拡張性と標準準拠の面で最も強力なオープンソースRDBMSである。

JSONB

-- JSONBカラムの作成と活用
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(200),
    attributes JSONB
);

-- データ挿入
INSERT INTO products (name, attributes)
VALUES ('ノートPC', '{"brand": "Samsung", "ram": 16, "storage": "512GB", "tags": ["electronics", "portable"]}');

-- JSONBクエリ
SELECT name, attributes->>'brand' AS brand
FROM products
WHERE attributes->>'ram' = '16';

-- JSONB包含演算子
SELECT * FROM products
WHERE attributes @> '{"brand": "Samsung"}';

-- JSONB配列検索
SELECT * FROM products
WHERE attributes->'tags' ? 'electronics';

CTE(共通テーブル式)

-- 再帰CTE: 組織図の探索
WITH RECURSIVE org_tree AS (
    -- 起点: 最上位管理者
    SELECT id, name, manager_id, 1 AS depth
    FROM employees
    WHERE manager_id IS NULL

    UNION ALL

    -- 再帰: 配下の社員を探索
    SELECT e.id, e.name, e.manager_id, t.depth + 1
    FROM employees e
    INNER JOIN org_tree t ON e.manager_id = t.id
)
SELECT * FROM org_tree ORDER BY depth, name;

ウィンドウ関数

-- 部署別給与ランキング
SELECT
    name,
    department,
    salary,
    ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rank_in_dept,
    RANK() OVER (ORDER BY salary DESC) AS overall_rank,
    AVG(salary) OVER (PARTITION BY department) AS dept_avg_salary,
    salary - LAG(salary) OVER (PARTITION BY department ORDER BY salary DESC) AS diff_from_prev
FROM employees;

-- 累計合計
SELECT
    order_date,
    amount,
    SUM(amount) OVER (ORDER BY order_date ROWS UNBOUNDED PRECEDING) AS running_total
FROM daily_sales;

パーティショニング

-- 範囲パーティショニング
CREATE TABLE events (
    id BIGSERIAL,
    event_type VARCHAR(50),
    payload JSONB,
    created_at TIMESTAMP NOT NULL
) PARTITION BY RANGE (created_at);

-- 月別パーティション作成
CREATE TABLE events_2026_01 PARTITION OF events
    FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');

CREATE TABLE events_2026_02 PARTITION OF events
    FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');

CREATE TABLE events_2026_03 PARTITION OF events
    FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');

-- パーティション別インデックス自動作成
CREATE INDEX idx_events_type ON events(event_type);

8. NoSQLの種類と選定基準

ドキュメントストア -- MongoDB

ドキュメント指向データベースで、JSON形式の柔軟なスキーマをサポートする。

// MongoDBクエリ例
db.users.insertOne({
  name: "田中太郎",
  email: "tanaka@example.com",
  address: {
    city: "東京",
    ward: "渋谷区"
  },
  tags: ["premium", "active"]
});

// 集約パイプライン
db.orders.aggregate([
  { $match: { status: "completed" } },
  { $group: {
      _id: "$user_id",
      totalSpent: { $sum: "$amount" },
      orderCount: { $sum: 1 }
  }},
  { $sort: { totalSpent: -1 } },
  { $limit: 10 }
]);

適したケース: スキーマが頻繁に変更されるサービス、コンテンツ管理システム、カタログ

キーバリューストア -- Redis

インメモリデータストアで、超高速な読み書きを提供する。(Redisの詳細は第9章で扱う。)

ワイドカラムストア -- Cassandra

大規模分散環境で高い書き込みスループットを提供する。

-- Cassandra CQL
CREATE TABLE sensor_data (
    sensor_id UUID,
    event_time TIMESTAMP,
    temperature DOUBLE,
    humidity DOUBLE,
    PRIMARY KEY (sensor_id, event_time)
) WITH CLUSTERING ORDER BY (event_time DESC);

適したケース: IoTデータ、時系列データ、大規模ログ保存

グラフデータベース -- Neo4j

ノードとリレーションシップ(エッジ)を基盤にデータを保存・探索する。

// Neo4j Cypherクエリ
// 友達の友達を探す(2段階関係)
MATCH (me:Person)-[:FRIEND]->(friend)-[:FRIEND]->(fof)
WHERE me.name = '田中太郎'
  AND NOT (me)-[:FRIEND]->(fof)
  AND fof <> me
RETURN DISTINCT fof.name AS recommendation
LIMIT 10;

適したケース: ソーシャルネットワーク、レコメンドシステム、不正検知、ナレッジグラフ

NoSQL選定ガイド

要件推奨DB
柔軟なスキーマ + 複雑なクエリMongoDB
超高速キャッシュ/セッションRedis
大量書き込み + 高可用性Cassandra
関係探索 + グラフ分析Neo4j
全文検索Elasticsearch
時系列データTimescaleDB, InfluxDB

9. Redis活用

Redisはインメモリのデータ構造ストアで、多様なデータ構造をサポートする。

キャッシュ(Cache)

# キャッシュ設定(TTL 3600秒)
SET user:1001:profile '{"name":"田中太郎","email":"tanaka@example.com"}' EX 3600

# キャッシュ取得
GET user:1001:profile

# キャッシュ無効化
DEL user:1001:profile

キャッシュ戦略:

  • Cache-Aside(遅延ロード) -- リクエスト時にキャッシュを確認し、なければDBから取得してキャッシュに保存
  • Write-Through -- データをDBとキャッシュに同時に書き込む
  • Write-Behind -- キャッシュに先に書き込み、非同期でDBに反映する

セッション管理

# セッション保存(30分期限)
HSET session:abc123 user_id 1001 role admin login_time "2026-04-12T10:00:00"
EXPIRE session:abc123 1800

# セッション取得
HGETALL session:abc123

リーダーボード

# スコア追加
ZADD game:leaderboard 1500 "player:001"
ZADD game:leaderboard 2300 "player:002"
ZADD game:leaderboard 1800 "player:003"

# 上位10名取得(高スコア順)
ZREVRANGE game:leaderboard 0 9 WITHSCORES

# 特定プレイヤーの順位取得
ZREVRANK game:leaderboard "player:001"

Pub/Sub

# チャンネル購読
SUBSCRIBE notifications:user:1001

# メッセージ発行
PUBLISH notifications:user:1001 '{"type":"order","message":"注文が完了しました"}'

分散ロック(Distributed Lock)

# ロック取得(NX: 存在しない場合のみ、EX: 有効期限)
SET lock:order:process "worker-1" NX EX 30

# ロック解放(Luaスクリプトでアトミックに処理)
# 自身が設定したロックのみ解放するため値を確認
EVAL "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 lock:order:process "worker-1"

10. データモデリング

ERダイアグラム

Entity-Relationshipダイアグラムはデータベース設計の視覚的な表現である。

主要な構成要素は以下の通り。

  • エンティティ(Entity) -- テーブルに変換される現実世界のオブジェクト
  • 属性(Attribute) -- エンティティの特性(カラム)
  • 関係(Relationship) -- エンティティ間の関連

関係の種類

1:1関係(One-to-One)

-- ユーザーとプロフィール: 1:1
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL
);

CREATE TABLE user_profiles (
    user_id INTEGER PRIMARY KEY REFERENCES users(id),
    bio TEXT,
    avatar_url VARCHAR(500)
);

1:N関係(One-to-Many)

-- ユーザーと投稿: 1:N
CREATE TABLE posts (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id),
    title VARCHAR(200) NOT NULL,
    content TEXT,
    created_at TIMESTAMP DEFAULT NOW()
);

M:N関係(Many-to-Many)

-- 投稿とタグ: M:N(中間テーブルが必要)
CREATE TABLE tags (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50) UNIQUE NOT NULL
);

CREATE TABLE post_tags (
    post_id INTEGER REFERENCES posts(id),
    tag_id INTEGER REFERENCES tags(id),
    PRIMARY KEY (post_id, tag_id)
);

モデリングチェックリスト

  1. ビジネス要件を先に分析する
  2. エンティティと関係を特定する
  3. 正規化を適用する(最低3NF)
  4. 性能要件に基づき非正規化を検討する
  5. インデックス戦略を策定する
  6. データ増加量を予測する

11. マイグレーション

なぜマイグレーションが必要か

データベーススキーマはコードと共に変化する。マイグレーションツールはスキーマ変更をバージョン管理し、チーム全体が同一のスキーマを維持することを保証する。

Flyway(Java/JVMエコシステム)

-- V1__create_users_table.sql
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    name VARCHAR(100) NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);

-- V2__add_status_to_users.sql
ALTER TABLE users ADD COLUMN status VARCHAR(20) DEFAULT 'active';
CREATE INDEX idx_users_status ON users(status);

-- V3__create_orders_table.sql
CREATE TABLE orders (
    id BIGSERIAL PRIMARY KEY,
    user_id BIGINT REFERENCES users(id),
    total_amount DECIMAL(10, 2) NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);

Flywayのルールは以下の通り。

  • ファイル名はV番号__説明.sql形式で記述する
  • 一度適用されたマイグレーションは変更しない
  • 新しい変更は新しいファイルとして追加する

Alembic(Python/SQLAlchemyエコシステム)

# alembic/versions/001_create_users.py
from alembic import op
import sqlalchemy as sa

def upgrade():
    op.create_table(
        'users',
        sa.Column('id', sa.Integer(), primary_key=True),
        sa.Column('email', sa.String(255), nullable=False, unique=True),
        sa.Column('name', sa.String(100), nullable=False),
        sa.Column('created_at', sa.DateTime(), server_default=sa.func.now()),
    )

def downgrade():
    op.drop_table('users')

無停止マイグレーション

本番環境でサービスを停止せずにスキーマを変更する方法。

カラム追加(安全)

-- NOT NULLなしで追加(既存行に影響なし)
ALTER TABLE users ADD COLUMN phone VARCHAR(20);

カラム削除(段階的アプローチ)

ステップ1 -- コードから該当カラムの使用を除去してデプロイする。

ステップ2 -- 十分な時間が経過した後、カラムを削除する。

ALTER TABLE users DROP COLUMN phone;

テーブル名変更(安全な方法)

ステップ1 -- 新しいテーブルを作成する。

ステップ2 -- 両方に書き込むようコードを修正する。

ステップ3 -- データをマイグレーションする。

ステップ4 -- 新テーブルのみ読み取るよう切り替える。

ステップ5 -- 旧テーブルを削除する。

大規模インデックス作成

-- CONCURRENTLYオプション: テーブルロックなしでインデックス作成
CREATE INDEX CONCURRENTLY idx_orders_user ON orders(user_id);

マイグレーションツール比較

ツールエコシステム特徴
FlywayJava/JVMSQLベース、シンプルで直感的
LiquibaseJava/JVMXML/YAML/JSON/SQL対応
AlembicPythonSQLAlchemy統合、自動検出
Prisma MigrateNode.jsPrisma ORM統合
golang-migrateGo軽量、CLIベース
Knex.jsNode.jsJavaScriptマイグレーション

まとめ

データベースはソフトウェアの心臓である。SQL基礎をしっかり固め、正規化とインデックスの原理を理解し、トランザクションのACID属性を体得する必要がある。その上にクエリ最適化能力と適切なNoSQL選定基準を備えれば、どんな規模のサービスでも堅牢なデータ層を設計できる。

重要ポイントのまとめ:

  • SQLを深く理解せよ -- JOIN、サブクエリ、ウィンドウ関数は必須
  • 正規化の原則に従いつつ、性能のための非正規化も検討せよ
  • インデックスは読み取り性能の鍵だが、書き込みコストを伴う
  • トランザクション分離レベルは整合性と同時実行性のトレードオフ
  • EXPLAIN ANALYZEでクエリ性能を計測し改善せよ
  • RDBMSとNoSQLの長所・短所を理解し、状況に応じて選択せよ
  • マイグレーションは常にバージョン管理し、無停止戦略を策定せよ