データベース・スキーマ設計ガイド
RDBMS + ORM 前提のフレームワーク非依存ガイド。
DB 設計ワークフロー
code
1. 要件からエンティティを抽出 2. 関連(リレーション)を定義 3. 属性と制約を決定 4. 正規化 (3NF を目標) 5. 非正規化の検討(パフォーマンス要件に応じて) 6. インデックス設計 7. マイグレーション作成
テーブル設計の原則
命名規則
code
テーブル名: 複数形・スネークケース (users, order_items)
カラム名: スネークケース (created_at, user_id)
外部キー: {参照テーブル単数}_id (user_id, order_id)
真偽値: is_, has_, can_ プレフィックス (is_active, has_permission)
共通カラム
code
id : 主キー(UUID or 連番) created_at : 作成日時(NOT NULL) updated_at : 更新日時(NOT NULL) deleted_at : 論理削除日時(NULL 許容、必要な場合のみ)
データ型選択の原則
| 用途 | 推奨 | 避ける |
|---|---|---|
| 主キー | UUID / ULID / auto increment | 文字列ID |
| 日時 | timestamp with timezone | 文字列 |
| 金額 | decimal / integer (最小単位) | float |
| 状態 | enum / varchar + CHECK | integer |
| フラグ | boolean | integer (0/1) |
| JSON | jsonb (対応DBの場合) | text |
正規化と非正規化
正規化 (3NF を基本目標)
| レベル | 排除するもの | 例 |
|---|---|---|
| 1NF | 繰り返しグループ | カンマ区切りの値 → 別テーブル |
| 2NF | 部分関数従属 | 複合キーの一部だけで決まる属性 |
| 3NF | 推移関数従属 | 非キー属性が別の非キー属性に依存 |
非正規化の判断基準
| 場面 | 手法 | トレードオフ |
|---|---|---|
| 読み取り頻度が高い集計値 | 事前計算カラム追加 | 更新時の整合性コスト |
| 頻繁な JOIN の回避 | 冗長カラム追加 | ストレージ + 更新コスト |
| 履歴データの保持 | スナップショット | ストレージ増 |
| 検索パフォーマンス | 非正規化ビュー | 同期コスト |
原則: まず正規化し、計測に基づいて非正規化を検討
詳細: references/normalization.md
型設計
Branded Types(ID の混同防止)
code
UserId と OrderId を型レベルで区別 → 引数の取り違えをコンパイル時に検出
Enum / Union Types(状態表現)
code
Status = 'pending' | 'approved' | 'rejected' 設計原則: - 状態遷移を明確に定義 - 無効な遷移を型レベルで防ぐ - DB には文字列 or enum 型で保存
Nullable 設計
code
NOT NULL をデフォルトとし、NULL は明確な理由がある場合のみ使用 NULL の正当な用途: - 任意フィールド(ユーザーが未入力) - 論理削除の deleted_at - 将来の拡張フィールド NULL を避けるべき場面: - デフォルト値で代替可能な場合 - 空文字列/0/false で表現できる場合
インデックス戦略
インデックスを付けるべきカラム
| 対象 | 理由 |
|---|---|
| 外部キー | JOIN パフォーマンス |
| WHERE で頻繁に使うカラム | 検索パフォーマンス |
| ORDER BY で使うカラム | ソートパフォーマンス |
| UNIQUE 制約のあるカラム | 一意性保証 |
インデックスの種類
| 種類 | 用途 |
|---|---|
| 単一カラム | 単純な条件検索 |
| 複合 | 複数条件の AND 検索(左端一致原則) |
| 部分 | 条件付きインデックス(WHERE 句付き) |
| 全文検索 | テキスト検索 |
避けるべきパターン
- •低カーディナリティのカラムへの単独インデックス
- •過剰なインデックス(書き込みパフォーマンス低下)
- •使われないインデックスの放置
詳細: references/indexing-strategy.md
リレーション設計
| 関係 | 実装 | 例 |
|---|---|---|
| 1:1 | 外部キー + UNIQUE 制約 | users ↔ profiles |
| 1:N | 子テーブルに外部キー | users → orders |
| N:M | 中間テーブル | users ↔ roles (user_roles) |
| 自己参照 | 同テーブルの外部キー | categories (parent_id) |
中間テーブルの設計
code
中間テーブル名: {テーブルA}_{テーブルB} (アルファベット順)
例: user_roles, order_items
カラム:
- 各テーブルの外部キー
- created_at
- 追加属性(関係に紐づくデータ)
カスケード設計
| アクション | 用途 | 注意 |
|---|---|---|
| CASCADE | 親削除時に子も削除 | 意図しない大量削除に注意 |
| SET NULL | 親削除時に NULL に | NULL 許容カラムのみ |
| RESTRICT | 子が存在する場合は親削除を拒否 | デフォルト推奨 |
マイグレーション管理の原則
- •マイグレーションは常に前進(ロールバック用ではなく新規マイグレーションで修正)
- •破壊的変更は段階的に行う(カラム追加 → データ移行 → 旧カラム削除)
- •本番データに対する破壊的操作は慎重に(バックアップ必須)
- •マイグレーションファイルはバージョン管理に含める
レビューチェックリスト
- • テーブル名・カラム名が命名規則に従っている
- • 共通カラム(id, created_at, updated_at)が存在する
- • NOT NULL がデフォルトで、NULL には明確な理由がある
- • 外部キーにインデックスが設定されている
- • 3NF 以上に正規化されている(または非正規化の理由がある)
- • カスケード設計が適切(デフォルト RESTRICT)
- • 頻繁な検索条件にインデックスがある
- • マイグレーションが後方互換性を持つ
リファレンス
- •references/normalization.md - 正規化詳細と判断フロー
- •references/indexing-strategy.md - インデックス戦略詳細