DB 設計レビュー(包括的観点)
SQL アンチパターン本のような「局所的・短期的な失敗パターン」のチェックでは拾いきれない、 横断的・長期的な観点で DB スキーマをレビューする。
主に以下 4 軸で評価する:
- 軸 A: 正しさ・整合性 — バグや不整合を生む設計
- 軸 B: 長期負債化しやすい設計選択 — 初期は便利でも運用年数とともに利息が膨らむ選択
- 軸 C: 非機能要件 — パフォーマンス、セキュリティ、コンプライアンス、運用性
- 軸 D: 命名・一貫性・変更容易性 — 未来の変更コストを左右する設計
レビュー観点
軸 A: 正しさ・整合性
A-1: 制約網羅性
NOT NULL/CHECK/UNIQUE/FOREIGN KEYが業務不変条件を表現しているか- 「アプリ側で担保している」前提の暗黙制約が DB に落ちていないか
ON DELETE/ON UPDATEのアクション(CASCADE / SET NULL / RESTRICT)が業務意味と一致しているか- enum 的な値が
CHECK (col IN (...))または別マスタテーブルで縛られているか
A-2: NULL セマンティクス
- 「未入力」「該当なし」「未確定」などの異なる意味が同じ NULL で表現されていないか
- NULL を許容することで 3 値論理の罠(
WHERE col != 'x'が NULL を除外する等)が生じないか - 「A が NULL のときは B が必須」のような相関制約が CHECK で表現されているか
A-3: TOCTOU・競合
- 「存在チェック → INSERT」「残数チェック → 減算」のような 2 段階処理が想定されている箇所はないか
UNIQUE制約 + UPSERT 系構文で原子化できる構造になっているか- カウンタ・残高系の更新が
UPDATE ... SET col = col + ?形式(CAS 相当)になっているか、楽観ロック用カラム(version)が必要か - 在庫・残高系で行ロックが必要なフローはあるか(採用 DB の分離レベル既定値を踏まえて評価)
A-4: 時刻制御の落とし穴
- 時刻系カラムがタイムゾーン情報を保持する型を採用しているか(DB により型名は異なる)
- デフォルト値関数(
now()/current_timestamp等)のタイムゾーン挙動の理解は正しいか created_at/updated_at/deleted_at/*_expires_atなどの命名・粒度の一貫性- アプリ層の時刻と DB 時刻のどちらを正にするかが決まっているか
- 「未来日時」「9999-12-31 終端」「epoch 0」などのセンチネル値の混入
軸 B: 長期負債化しやすい設計選択
短期的には便利・安全に見えて、運用年数とともに利息が膨らむ設計選択。 「変更容易性」観点と独立して評価すること(変更容易性 = 未来の変更コスト、こちらは過去の選択の利息)。
B-1: 論理削除負債(soft delete)
参照: https://syu-m-5151.hatenablog.com/entry/2025/12/24/110101
deleted_at/is_deletedカラムを使った soft delete を採用していないか- 採用している場合:
- 全クエリで
WHERE deleted_at IS NULLが必須となり、付け忘れによるバグ温床になっていないか UNIQUE制約が論理削除済みレコードと衝突しないか(部分 UNIQUE インデックスや別カラムで回避できているか)- 関連レコードの整合性(親が論理削除されたとき子はどうなるか)が定義されているか
- GDPR・個人情報保護法の「削除権」と両立できるか(論理削除では消えていない)
- 不要レコードの蓄積で集計・インデックスが重くなっていないか
- そもそも「履歴を残す」目的なら、論理削除ではなく履歴テーブル / イベントテーブルの方が適切ではないか
- 全クエリで
B-2: 状態フラグの増殖
is_active/is_archived/is_published/is_deletedなどの boolean が同じテーブルに 3 つ以上ないか- これらの組合せで表現される「状態」が指数的に増え、暗黙の状態機械化していないか
- → 単一の
statusカラム(CHECK 制約付き)または状態遷移テーブルへの集約を検討
B-3: 「便利カラム」(集計値のキャッシュ)
comment_count,like_count,view_countのような集計値キャッシュカラムがあるか- ある場合:
- 整合性維持コード(トリガー or アプリ層更新)が散らばっていないか
- 不整合時の再計算ジョブが用意されているか
- そもそもオンデマンド計算で十分ではないか
B-4: enum / マスタテーブルの選択ミス
- CHECK 制約で済むか、別マスタテーブルにすべきか
- 取り得る値が頻繁に増減する / 値ごとに付帯情報がある → マスタテーブル化
- 固定的・少数 → CHECK 制約
- 逆方向への変更コストが高いため、現状の選択が業務実態と合っているか
B-5: JSON / 配列の濫用
- スキーマレス領域(
JSON/JSONB/ 配列型)に重要なデータを格納していないか - 入っている場合:
- 検索・JOIN・集計に使われていないか(使うなら正規化が必要)
- インデックスは適切か
- 「とりあえず」で入れた結果、後から正規化できなくなっていないか
B-6: 採番戦略の硬直化
- 主キーが単調増加(連番)になっていないか — 分散DB やホットレンジを生む書き込みパターンに弱い
- ソート性が必要な場合に UUID v7 / ULID など時系列ソート可能な ID 形式の検討余地があるか
- 外部公開する ID と内部 PK が同一だと、後から ID 形式を変えにくい
- 採用 ID 形式と業務要件(推測されにくさ・URL 短さ・分散性・ソート性)が一致しているか
軸 C: 非機能要件
C-1: パフォーマンス・コスト
- 想定アクセスパターン(クエリ)に対するインデックスがあるか
- 過剰なインデックス(書き込みコストとストレージ)はないか
- カバリングインデックス活用余地
- ホットテーブルのアクセスパターン設計
- マネージド DB の場合、課金単位(RU / IOPS / DTU 等)を意識した設計か
C-2: セキュリティ
- 認証情報(パスワードハッシュ、API キー、OAuth トークン)の保存形式
- ハッシュアルゴリズム / ソルトの仕様は決まっているか
- 平文保存していないか
- PII(メール、氏名、IP、位置情報)のカラム特定と取り扱い方針
- 行レベルセキュリティ (RLS) の要否
- 最小権限:アプリ用 DB ロールが必要以上の権限を持っていないか
- アプリ層との SQL インジェクション耐性(プレースホルダ利用の徹底)
C-3: コンプライアンス
- GDPR / 個人情報保護法等の「削除権」「アクセス権」に対応できる設計か
- データ保持期間 / TTL の方針
- 監査ログテーブルの有無と保管期間
- データ越境(リージョン)の制約
C-4: 運用・可観測性
- スロークエリ特定のための索引情報・統計が取れるか
- バックアップ / PITR の戦略
- メトリクス用カラムが分析クエリに耐えるか
- スキーマ変更時のオンライン DDL 可否(大規模テーブルの
ADD COLUMN NOT NULL DEFAULT影響など)
軸 D: 命名・一貫性・変更容易性
D-1: 命名規則
- テーブル名(単複の統一)、列名(snake_case / camelCase などの統一)、PK 命名(
idvs${entity}_id)の一貫性 - 予約語との衝突回避
- 時刻系列名の規約(
_atsuffix 等)
D-2: 監査・履歴
created_at/updated_atの全テーブル一貫採用- 監査ログ要件(誰がいつ何を変更したか)
- 履歴保持が必要なテーブルで、現状の設計が要件を満たすか
D-3: スキーマ進化容易性
- 後方互換の取りやすい設計か(カラム追加でなく削除を要する変更が頻発しないか)
- マイグレーションがオンライン DDL で実行可能か
- expand-contract パターンでアプリのローリングデプロイと両立できるか
- 既存マイグレーション修正禁止ルールが運用に定着しているか
D-4: アプリ層・ORM 接続点
- DB の型がアプリ層の型に自然にマッピングできるか
- N+1 を誘発する深い親子関係になっていないか
- DTO へのマッピングが容易か(過度に正規化されすぎていないか)
D-5: 国際化
- 文字コード / コレーションの方針
- 多言語コンテンツを保持する場合の設計(言語別カラム vs 翻訳テーブル)
- 通貨・単位の保持方式(最小単位整数 vs 小数)
レビュー手順
- 軸 A → B → C → D の順でチェックリストを走査
- 各観点で「該当あり / 該当なし / 要確認」を判定
- 該当ありは具体的な箇所(テーブル名・列名)と改善策を提示
- 業務要件が判断材料として必要な観点は「要確認」とし、判断に必要な情報を質問形式で残す
- 最後に「優先度高(業務影響大 or 後から直しにくい)」観点を 3 件以内に絞った総評を書く
出力形式
## DB 設計レビュー結果(包括的観点)
### 対象
- ファイル / スキーマ: ...
- 主要テーブル数: N
### 軸 A: 正しさ・整合性
- A-1 制約網羅性: [該当あり/なし/要確認]
- 箇所:
- 問題:
- 改善策:
- A-2 NULL セマンティクス: ...
- A-3 TOCTOU・競合: ...
- A-4 時刻制御: ...
### 軸 B: 長期負債化しやすい設計選択
- B-1 論理削除負債: ...
- B-2 状態フラグの増殖: ...
- B-3 便利カラム: ...
- B-4 enum/マスタ選択: ...
- B-5 JSON/配列濫用: ...
- B-6 採番戦略: ...
### 軸 C: 非機能要件
- C-1 パフォーマンス・コスト: ...
- C-2 セキュリティ: ...
- C-3 コンプライアンス: ...
- C-4 運用・可観測性: ...
### 軸 D: 命名・一貫性・変更容易性
- D-1 命名規則: ...
- D-2 監査・履歴: ...
- D-3 スキーマ進化容易性: ...
- D-4 ORM 接続点: ...
- D-5 国際化: ...
### 総評
優先度高(業務影響大 or 後から直しにくいもの)を 3 件以内:
1. ...
2. ...
3. ...
留意事項
- 断定を避ける: スキーマだけでは判断できない観点(業務要件・運用方針・コンプライアンス)は「要確認」として、判断に必要な情報を質問形式で残す
- DB エンジン固有の詳細: 採用 DB エンジン(PostgreSQL / MySQL / SQL Server / 分散 DB 等)固有の落とし穴がある場合は、エンジン名を明示した上で言及する
- 既存決定との整合: 既に決定済みの ADR / 設計ドキュメントと矛盾する提案をする場合は、該当ドキュメントを引用した上で「再検討の必要あり」と明示する