MySQL | SQLite経験者向け、30日で習得するMySQL:差分理解 - Day5 制約の強化

SQL MySQL
スポンサーリンク

Day5 前半のゴール

「“あいまいなデータ”をDBレベルで拒否できるようになる」

今日は「制約(constraint)」の中でも、特に重要な2つに絞ります。

PRIMARY KEY
FOREIGN KEY

SQLiteでも出てきましたが、MySQLでは「本気で使う」前提になります。
Day5 前半のゴールは、

PRIMARY KEY が“単なるID”ではなく“行の一意性の保証”だと理解する
FOREIGN KEY が“テーブル間の関係を壊させないためのガード”だと理解する
SQLiteとMySQLでのFOREIGN KEYの扱いの差を意識できる

ここまで行ければ、テーブル設計のレベルが一段上がります。


制約とは何か

「“こういうデータは入れてはいけない”というルール」

制約(constraint)は、
「このテーブルには、こういうデータしか入れてはいけない」というルールです。

NOT NULL
→ このカラムは必ず値が必要

UNIQUE
→ 重複してはいけない

PRIMARY KEY
→ 行を一意に識別する“代表”

FOREIGN KEY
→ 他のテーブルに存在する値しか入れてはいけない

アプリ側のバリデーション(if文など)と違って、
DB側が「絶対に破らせないルール」として持ちます。

これがあると、

アプリのバグや不正なリクエストが来ても、
DBが「それはおかしい」と弾いてくれる

という“最後の砦”になります。


PRIMARY KEY の役割

「“この行はこれで一意に決まる”という“代表カラム”」

PRIMARY KEY は、
「このテーブルの1行を一意に識別するためのカラム(またはカラムの組み合わせ)」です。

よくあるのは、AUTO_INCREMENT の整数IDです。

CREATE TABLE users (
  id    INT AUTO_INCREMENT PRIMARY KEY,
  name  VARCHAR(50)  NOT NULL,
  email VARCHAR(255) NOT NULL
);
SQL

ここでの id は、

NULL になってはいけない
重複してはいけない
行を特定するときの“代表”

という性質を持ちます。

SQLiteでも INTEGER PRIMARY KEY をよく使っていましたが、
MySQLでは「PRIMARY KEY をどう設計するか」が、
インデックスやJOINの性能にも直結します。


なぜ“意味のある値”をPRIMARY KEYにしないのか

「メールアドレスや会員番号を“主キーにしない”理由」

初心者がやりがちなのが、

メールアドレスをPRIMARY KEYにする
会員番号(人間が見るID)をPRIMARY KEYにする

という設計です。

一見便利そうですが、問題が多いです。

メールアドレスが変わったら?
→ PRIMARY KEY を更新することになる(重い・危険)

会員番号のルールを変えたくなったら?
→ 既存データとの整合性が崩れる

漏えいしたときに、
→ PRIMARY KEY だけで個人が特定されやすくなる

だからこそ、MySQLでは、

内部用の連番ID(INT AUTO_INCREMENT)をPRIMARY KEYにする
メールアドレスや会員番号は「ただのカラム」+必要ならUNIQUE制約

という設計が基本になります。

PRIMARY KEY は「システム内部の都合で安定している値」にする。
これが、セキュリティと運用の両面で重要です。


複合PRIMARY KEYという考え方

「“2つセットで一意”というケースもある」

PRIMARY KEY は、1カラムとは限りません。
「この2つの組み合わせで一意」というケースもあります。

例えば、「ユーザーごとの設定」を持つテーブル。

CREATE TABLE user_settings (
  user_id INT NOT NULL,
  key     VARCHAR(50) NOT NULL,
  value   VARCHAR(255),
  PRIMARY KEY (user_id, key)
);
SQL

ここでは、

user_id だけでは一意にならない(1ユーザーに複数設定がある)
key だけでも一意にならない(全ユーザーで同じkeyがありうる)
user_id + key の組み合わせなら一意

という状況です。

このように、

「何をもって“1行”とみなすか」

を考えて PRIMARY KEY を決めるのが、本来の設計です。

ただし、初心者のうちは、

基本は単一の AUTO_INCREMENT ID
必要になったら複合PRIMARY KEYも検討

くらいのスタンスでOKです。


FOREIGN KEY の役割

「“親にいない子ども”をDBレベルで禁止する」

次に、今日のメインテーマの1つ、FOREIGN KEY です。

FOREIGN KEY は、

「このカラムに入る値は、必ず別のテーブルの特定カラムに存在していなければならない」

という制約です。

典型的な例は、「ユーザー」と「注文」です。

CREATE TABLE users (
  id   INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(50) NOT NULL
) ENGINE=InnoDB;

CREATE TABLE orders (
  id      INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT NOT NULL,
  amount  INT NOT NULL,
  CONSTRAINT fk_orders_user
    FOREIGN KEY (user_id)
    REFERENCES users(id)
) ENGINE=InnoDB;
SQL

ここでのルールは、

orders.user_id に入れてよいのは、
users.id に存在する値だけ

ということです。

つまり、

存在しないユーザーIDで注文を作ろうとすると、
DBがエラーにしてくれる

という“ガード”になります。


SQLite と MySQL の FOREIGN KEY の違い

「SQLiteは“オプション”、MySQL+InnoDBは“本気で使う前提”」

SQLiteにもFOREIGN KEYはありますが、
デフォルトで無効だったり、
アプリ側があまり使っていなかったりするケースが多いです。

PRAGMA foreign_keys = ON; を明示しないと効かない、
という罠もありました。

一方、MySQL+InnoDBでは、

FOREIGN KEY を前提に設計する
→ DBがテーブル間の整合性を守る

という文化が強いです。

その結果、

アプリ側で「この user_id は本当に存在するか?」を毎回チェックしなくても、
DBが「存在しないIDは受け付けない」と保証してくれる

という状態を作れます。

これは、セキュリティ的にも品質的にも非常に大きいです。


FOREIGN KEY が守ってくれるもの

「“孤児データ”と“おかしな参照”を防ぐ」

FOREIGN KEY がないと、こんなことが起こります。

users からユーザーを削除したのに、
orders にはそのユーザーの注文が残り続ける

存在しない user_id を持つ注文が大量に溜まる

こうした「親のいない子ども」のようなデータを、
孤児レコード(orphan)と呼びます。

FOREIGN KEY を張っておけば、

親がいないIDを子テーブルに入れようとするとエラー
親を消そうとしたとき、子が残っていればエラー(または連動削除)

という形で、DBが整合性を守ってくれます。

SQLiteでは「アプリ側で気をつける」ことが多かったかもしれませんが、
MySQLでは「DBに任せる」方向にシフトできます。


Day5 前半のまとめ

PRIMARY KEY は「行を一意に識別するための代表カラム(または組み合わせ)」であり、NULL不可・重複不可という性質を持つ。MySQLでは、内部用の連番ID(INT AUTO_INCREMENT)をPRIMARY KEYにするのが基本で、メールアドレスや会員番号のような“意味のある値”を主キーにしないことで、セキュリティと運用の柔軟性を確保できる。
複合PRIMARY KEYは、「user_id+key」のように2つ以上のカラムの組み合わせで1行を一意にする設計で、何をもって“1行”とみなすかを考えるときの選択肢になるが、まずは単一のAUTO_INCREMENT IDを基本形として押さえておけばよい。
FOREIGN KEY は「このカラムの値は、必ず別テーブルの特定カラムに存在していなければならない」という制約であり、存在しないユーザーIDを持つ注文などの“おかしな参照”や“孤児データ”をDBレベルで防いでくれる。
SQLiteではFOREIGN KEYがデフォルト無効だったり、アプリ側でチェックしていたりすることが多かったのに対し、MySQL+InnoDBではFOREIGN KEYを前提に設計し、DBに整合性チェックを任せる文化が強く、これが品質とセキュリティの大きな支えになる。

後半では、
実際にPRIMARY KEY+FOREIGN KEY付きのテーブルを作り、
「存在しないIDをINSERTしようとするとどうなるか」「親を消そうとするとどうなるか」を
具体的なクエリで確認しながら、制約の“効き方”を体で覚えていきます。

タイトルとURLをコピーしました