- 1. データベースとは
- 2. SQL基礎
- 3. 正規化(Normalization)
- 4. インデックス(Index)
- 5. トランザクション(Transaction)
- 6. クエリ最適化
- 7. PostgreSQL応用
- 8. NoSQLの種類と選定基準
- 9. Redis活用
- 10. データモデリング
- 11. マイグレーション
- まとめ
1. データベースとは
データベース(Database)は、構造化されたデータを保存・管理するシステムである。現代のソフトウェアにおいてデータの保存と検索は中核機能であり、適切なデータベースの選定と設計がアプリケーションの性能とスケーラビリティを左右する。
RDBMS vs NoSQL
リレーショナルデータベース(RDBMS)とNoSQLは根本的に異なる思想を持つ。
| 項目 | RDBMS | NoSQL |
|---|---|---|
| データモデル | テーブル(行と列) | ドキュメント、キーバリュー、カラム、グラフ |
| スキーマ | 固定スキーマ | 柔軟なスキーマ |
| スケーリング | 垂直(スケールアップ) | 水平(スケールアウト) |
| トランザクション | ACID保証 | BASE(一部ACID対応) |
| クエリ言語 | SQL | 各DB固有のAPI |
| 適したケース | 複雑な関係、整合性重視 | 大量データ、柔軟なスキーマが必要 |
主要データベース比較
| DB | 種類 | 特徴 | 主な用途 |
|---|---|---|---|
| PostgreSQL | RDBMS | JSONB、拡張性、豊富なデータ型 | 汎用 |
| MySQL | RDBMS | 高い互換性、InnoDBエンジン | Webサービス |
| MongoDB | Document | 柔軟なスキーマ、集約パイプライン | コンテンツ管理 |
| Redis | Key-Value | インメモリ、超高速 | キャッシュ、セッション |
| Cassandra | Wide Column | 高い書き込みスループット | IoT、時系列 |
| Neo4j | Graph | 関係探索に最適化 | ソーシャルネットワーク |
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';
インデックス設計原則
- カーディナリティ(ユニーク値の数)が高いカラムにインデックスを作成する
- WHERE、JOIN、ORDER BYで頻繁に使用されるカラムを優先する
- 書き込み性能とストレージ容量のトレードオフを考慮する
- 複合インデックスでは選択性が高いカラムを先頭に置く
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 Read | Non-Repeatable Read | Phantom 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;
クエリ最適化チェックリスト
- 不必要なSELECT *の代わりに必要なカラムのみ指定する
- WHERE句で関数を使用するとインデックスが無効化される可能性がある
- LIKE検索で前方ワイルドカードを避ける
- 大量INSERTはバッチで処理する
- 適切なインデックスを作成する
-- 悪い例: インデックス無効化
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)
);
モデリングチェックリスト
- ビジネス要件を先に分析する
- エンティティと関係を特定する
- 正規化を適用する(最低3NF)
- 性能要件に基づき非正規化を検討する
- インデックス戦略を策定する
- データ増加量を予測する
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);
マイグレーションツール比較
| ツール | エコシステム | 特徴 |
|---|---|---|
| Flyway | Java/JVM | SQLベース、シンプルで直感的 |
| Liquibase | Java/JVM | XML/YAML/JSON/SQL対応 |
| Alembic | Python | SQLAlchemy統合、自動検出 |
| Prisma Migrate | Node.js | Prisma ORM統合 |
| golang-migrate | Go | 軽量、CLIベース |
| Knex.js | Node.js | JavaScriptマイグレーション |
まとめ
データベースはソフトウェアの心臓である。SQL基礎をしっかり固め、正規化とインデックスの原理を理解し、トランザクションのACID属性を体得する必要がある。その上にクエリ最適化能力と適切なNoSQL選定基準を備えれば、どんな規模のサービスでも堅牢なデータ層を設計できる。
重要ポイントのまとめ:
- SQLを深く理解せよ -- JOIN、サブクエリ、ウィンドウ関数は必須
- 正規化の原則に従いつつ、性能のための非正規化も検討せよ
- インデックスは読み取り性能の鍵だが、書き込みコストを伴う
- トランザクション分離レベルは整合性と同時実行性のトレードオフ
- EXPLAIN ANALYZEでクエリ性能を計測し改善せよ
- RDBMSとNoSQLの長所・短所を理解し、状況に応じて選択せよ
- マイグレーションは常にバージョン管理し、無停止戦略を策定せよ
현재 단락 (1/513)
データベース(Database)は、構造化されたデータを保存・管理するシステムである。現代のソフトウェアにおいてデータの保存と検索は中核機能であり、適切なデータベースの選定と設計がアプリケーションの性...