DB設計チェックリスト
1. モデリング(概念設計・論理設計)
テーブルの責務
- 複数の目的に使われるテーブルはないか:
- 影響:
- 条件分岐(if会員 then... if管理者 then...)がアプリ側に散在し、片方の変更がもう片方に影響するリスクが生まれる
- NULL許容カラムが増え、テーブルの意味が曖昧になる
- 対応方針:
- 役割ごとにテーブルを分離するか、共通部分を親テーブル、差分をサブタイプテーブルに分ける(スーパータイプ/サブタイプパターン)
- 影響:
- エンティティの粒度は適切か:
- 影響:
- 粒度が粗すぎると更新時のロック範囲が広がり同時実行性が下がる
- 細かすぎると意味のあるまとまりを再構築するためのJOINが増え、可読性・性能が悪化する
- 対応方針:
- 業務上「常に一緒に読み書きされるか」を基準に凝集度を見直し、独立してライフサイクルを持つ情報は分割する
- 影響:
カラムの責務
- 複数の目的に使われるカラムはないか:
- 影響: カラムの意味を読み解くのにレコードの他の値を参照する必要があり、クエリやアプリコードにドメイン知識が埋め込まれてバグの温床になる
- 対応方針: 属性ごとに別カラム、または別テーブルに分離する。共通の型を持たせたい場合はEAVではなく明示的なサブタイプテーブルを検討
- 導出項目を保存していないか:
- 影響: 元データが更新された際に導出項目との不整合(更新漏れ)が発生する。二重管理によるバグ
- 対応方針: 原則としてクエリ時に計算する。パフォーマンス上必要な場合のみキャッシュとして保存し、更新経路を一元化する(トリガーやアプリ層で保証)
- 有意コードを使っていないか:
- 影響: コード体系が変わった際に桁の意味が崩壊し、システム全体に影響する。新しい種別が増えると桁数設計からやり直しになる
- 対応方針: 種別は別カラム(type, categoryなど)として明示的に持たせ、IDは意味を持たない連番やUUIDにする
- カラム数は適切か:
- 影響: 12を大きく超えると1レコードの意味が把握しづらくなり、更新の影響範囲が広がる。テーブルロック時の競合も増えやすい
- 対応方針: 関連の強いカラム群をサブテーブルに切り出す。ただしエンティティが本質的に多属性な場合は無理に分割しない
正規化
- 第一正規形(繰り返し属性):
- 影響:
tag1, tag2, tag3のようなカラムは検索・集計が困難になり、上限を超えるデータに対応できない - 対応方針: 繰り返し項目は子テーブルに切り出し、1対多の関係で表現する
- 影響:
- 第二正規形(部分関数従属):
- 影響: 複合主キーの一部にしか依存しない値を同居させると、更新時異状(一部だけ更新されて矛盾が起きる)が発生する
- 対応方針: 従属する値を主キーの単位に応じたテーブルへ分離する
- 第三正規形(推移的関数従属):
- 影響: 主キー以外の項目に従属する値(例:顧客IDに従属する顧客名)を同じテーブルに持たせると、元テーブルの値が更新されても複製先が更新されず、データ不整合が生じる
- 対応方針: 推移的に従属する値は参照先テーブルから都度JOINして取得する。パフォーマンス上のキャッシュが必要な場合は、更新の伝播経路を明確にした上で非正規化する
- JSON型・配列型の濫用:
- 影響: RDBの整合性制約(型・NOT NULL・外部キー)が効かなくなり、値の検索・集計・インデックスが困難になる
- 対応方針: 構造が事前に分かっているデータは列またはテーブルに正規化する。真にスキーマレスな要件のみJSON型を許容し、理由をコメントで残す
データのライフサイクル
- 異なるライフサイクルのデータが同居:
- 影響: INSERT時にNULLで後からUPDATEされる項目が多いと、レコードの状態遷移がカラムの有無でしか判断できず、不完全な状態のレコードが増える
- 対応方針: ライフサイクルのフェーズごとにテーブルを分割する(例:注文テーブルと発送情報テーブル)
- デフォルトでNULLになるカラム:
- 影響: そのカラムがいつ埋まるか不明確になり、アプリ側で毎回NULLチェックが必要になる。本当に必須でない属性なのか判断がつかない
- 対応方針: 別テーブルに切り出すか、業務上必須なら初期値を設計時に決めてNOT NULL化する
- イベントエンティティのタイムスタンプ:
- 影響: タイムスタンプ不要なのに追加していると、そのテーブルが本当は「状態」を表しているのか「出来事」を表しているのか設計意図が曖昧になる
- 対応方針: テーブルが表すのが「状態(マスタ)」か「出来事(ログ・トランザクション)」かを明確にし、後者のみタイムスタンプを必須とする
キー・リレーション
- 主キー設計(サロゲート vs 自然キー):
- 影響: 自然キー(メールアドレス等)を主キーにすると、値の変更時に外部キー参照先すべてに影響が波及する
- 対応方針: 原則サロゲートキー(連番/UUID)を主キーとし、自然キーは業務上のユニーク制約として別途保持する
- 外部キー制約の有無:
- 影響: 外部キー制約がないと、参照整合性がアプリコードに依存し、孤児レコード(親のない子レコード)が発生しうる
- 対応方針: 原則外部キー制約を張る。パフォーマンス上外す場合は理由と整合性担保の代替手段(バッチチェック等)を明記する
- 多対多の中間テーブル:
- 影響: 中間テーブルを作らず配列やカンマ区切りで表現すると、片方向の検索・集計ができなくなる
- 対応方針: 中間テーブルを作成し、必要であれば関係自体の属性(例:役職の開始日)もそこに持たせる
フラグ・ステータス表現
- 削除フラグ:
- 影響: 全クエリに
WHERE deleted = 0が必要になり、忘れると論理削除済みデータが表示されるバグにつながる。インデックスの効きも悪化しやすい - 対応方針: ステータスカラムでの表現、または削除済みデータを別テーブル(アーカイブ)に移動する方式を検討する
- 影響: 全クエリに
2. 命名・型・制約(論理設計〜物理設計)
命名規則
- テーブル名・カラム名の一貫性(単数/複数形、スネークケースなど):
- 影響: 命名規則が混在すると、開発者がテーブル名を毎回確認する必要があり、タイポによるクエリエラーが増える。ORMの自動マッピング規則から外れて余計な設定が必要になることもある
- 対応方針: プロジェクト冒頭で命名規則(例:テーブル名は複数形スネークケース、カラム名は単数形スネークケース)を文書化し、レビューでチェックする
- 予約語との衝突(
order,group,keyなど):- 影響: クエリでバッククォートやダブルクォートでのエスケープが常に必要になり、可読性が落ちる。DBMS移行時に別の予約語と衝突する二次被害も起きうる
- 対応方針: 予約語リストと照合し、
order_noのように接尾辞・接頭辞をつけて回避する
- 略語の氾濫:
- 影響: 略語の意味がドキュメント化されないままだと、新規参加者がスキーマを読み解けず、誤った略語解釈でバグを生む
- 対応方針: 略語は共通の用語集(Glossary)で管理し、新しい略語を追加する際はレビューを通す。迷ったら省略しない
- 同じ概念に別の名前が付いていないか(
user_idvsmember_id):- 影響: 同一概念を指すカラム名が揺れていると、JOIN条件を誤りやすく、検索性も落ちる
- 対応方針: 概念ごとに正式名称を1つ定め、全テーブルで統一する。命名揺れをチェックするlintルールを設けるのも有効
- 真偽値カラムのprefix(
is_/has_):- 影響: prefixがないと、そのカラムが真偽値かどうかコードを見ないと判断できず、誤って数値として扱われるバグが起きうる
- 対応方針: 真偽値には必ず
is_/has_/can_などのprefixを付け、型もBOOLEAN(または0/1のTINYINT)に統一する
型・制約
- データの性質に合った型か:
- 影響: 金額をfloatで持つと丸め誤差が発生し、会計処理で実害が出る。日付をvarcharで持つとソート・範囲検索が正しく機能しない
- 対応方針: 金額はDECIMAL/NUMERIC、日時はDATE/DATETIME/TIMESTAMP型を使う。型選定の根拠を設計書に残す
- 桁数・長さの設計根拠:
- 影響: 根拠のない桁数設定は、後から桁あふれでエラーになったり、逆に無駄に大きく確保してストレージを圧迫したりする
- 対応方針: 実際の業務データの最大値・将来の増加見込みを調査し、桁数の根拠をコメントに残す
- NOT NULL / UNIQUE / CHECK制約の妥当性:
- 影響: 業務上必須の値にNOT NULLが付いていないと不完全なデータが混入する。UNIQUE制約漏れは重複データを許してしまい、後から一意性を担保する改修コストが大きい
- 対応方針: 業務ルールを洗い出し、DB層でも制約として強制する(アプリ層のバリデーションだけに頼らない=多層防御)
- 文字コード・照合順序(collation)の統一:
- 影響: テーブル間で文字コード・照合順序が異なると、JOIN時に暗黙の変換が発生してインデックスが使われずパフォーマンスが劣化する。絵文字や特定言語で文字化けも起きる
- 対応方針: DB/テーブル作成時に標準の文字コード・照合順序(例:utf8mb4)をプロジェクト全体で統一する
3. パフォーマンス・運用(物理設計・非機能)
インデックス設計
- 検索・JOIN・ソートで使う列にインデックスがあるか:
- 影響: インデックスがないと該当列でフルスキャンが発生し、データ量増加に比例してクエリが遅くなる。特にJOINキーの欠如は結合コストを爆発的に増やす
- 対応方針: 実際のクエリパターン(WHERE/JOIN/ORDER BY句)を洗い出し、それに基づいてインデックスを設計する。EXPLAINで実行計画を確認する
- 複合インデックスの列順序:
- 影響: 列順序がクエリの条件順と合っていないと、インデックスが部分的にしか使われず期待した性能が出ない
- 対応方針: 等価条件で絞り込む列を先頭に、範囲条件・ソート列を後ろに置く。主要クエリごとに必要な複合インデックスを洗い出す
- 不要なインデックスがないか:
- 影響: 使われないインデックスはINSERT/UPDATE/DELETE時の書き込みコストを増やし、ストレージも消費する
- 対応方針: 定期的に未使用インデックスを棚卸しし(DBMSの統計情報を利用)、削除を検討する
レコード数・スケーラビリティ
- レコード数が膨大になるテーブルの対策:
- 影響: 行動ログなど増加が止まらないテーブルは、インデックスの肥大化・バックアップ時間の増大・クエリ性能劣化を招く
- 対応方針: 時系列パーティショニング、古いデータのアーカイブ、要件次第ではRDBMS以外(データウェアハウス等)への切り出しを検討する
- シャーディング/マルチテナント分離の要否:
- 影響: テナント分離が不十分だと、あるテナントの負荷が他テナントの性能に影響したり、最悪データ漏洩につながったりする
- 対応方針: テナントIDを全テーブルに持たせる、またはスキーマ/DB自体を分離する方式を要件の重大度に応じて選択する
監査・履歴
created_at/updated_at等の一貫付与:- 影響: 一部のテーブルにしか付与されていないと、障害調査やデータ不整合の原因追跡ができない
- 対応方針: 全テーブル共通の監査カラムをテンプレート化し、テーブル作成時に必ず含めるルールにする
- 変更者(
created_by/updated_by)の追跡要件:- 影響: 誰が変更したか分からないと、不正操作やオペレーションミスの原因特定ができず、監査要件を満たせない
- 対応方針: 業務上の責任追跡が必要なテーブルには変更者カラムを追加する。認証基盤とのID連携を設計する
- 履歴・変更前データを残す要件:
- 影響: 上書き更新のみだと過去の状態が失われ、監査・分析・「いつ何が変わったか」の問い合わせに答えられない
- 対応方針: 履歴テーブル(変更のたびにレコードを追加)や、変更ログテーブルを別途設計する
トランザクション・整合性
- 複数テーブル更新の整合性担保:
- 影響: トランザクションで括られていないと、途中で失敗した際に一部のテーブルだけ更新された不整合状態が残る
- 対応方針: 関連する更新は明示的にトランザクションで囲む。分散システムの場合はSagaパターン等の補償トランザクションを検討する
- ロック方式(楽観/悲観)の検討:
- 影響: 同時更新の考慮がないと、Lost Update(後勝ちで一方の更新が消える)が発生する。逆に悲観ロックを乱用すると同時実行性が落ちる
- 対応方針: 更新頻度・競合可能性に応じて楽観ロック(バージョンカラム)か悲観ロック(SELECT FOR UPDATE)を選択する
- 冪等性への対応:
- 影響: リトライ処理で同じINSERTが複数回実行されると、重複データが生成される
- 対応方針: 一意制約やリクエストIDによる重複排除キーを設け、同じ操作を複数回実行しても結果が変わらないようにする
セキュリティ・プライバシー
- PIIの暗号化・マスキング:
- 影響: 個人情報が平文で保存されていると、DB漏洩時の被害が甚大になり、法令(個人情報保護法等)違反のリスクもある
- 対応方針: 機密性の高い項目はアプリ層またはDB機能で暗号化する。ログ出力・画面表示時もマスキングする
- 機密情報の平文保存有無:
- 影響: パスワード等を平文保存すると、DB漏洩=即アカウント乗っ取りにつながる
- 対応方針: パスワードは不可逆なハッシュ(bcrypt等)で保存し、平文は一切保持しない
- アクセス権限設計との整合性:
- 影響: DBのアクセス権限がアプリの権限モデルとずれていると、想定外のユーザーが直接DBアクセスした際に情報が見えてしまう
- 対応方針: DBユーザー/ロールを業務ロールに合わせて分離し、必要最小限の権限のみ付与する(最小権限の原則)
マイグレーション
- 既存データを壊さないスキーマ変更(後方互換性):
- 影響: カラム削除やNOT NULL化を無計画に行うと、稼働中のアプリがエラーを起こしたり、既存データが変更に対応できずマイグレーションが失敗したりする
- 対応方針: 段階的マイグレーション(新カラム追加→両方書き込み→切り替え→旧カラム削除)を採用し、ロールバック手順も用意する