Upgrade to Pro — share decks privately, control downloads, hide ads and more …

外部キー制約の知っておいて欲しいこと - RDBMSを正しく使うために必要なこと / FORE...

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

外部キー制約の知っておいて欲しいこと - RDBMSを正しく使うために必要なこと / FOREIGN KEY Night

外部キーナイトの登壇資料です。
https://connpass.com/event/380098/

Avatar for soudai sone

soudai sone PRO

February 03, 2026
Tweet

More Decks by soudai sone

Other Decks in Technology

Transcript

  1. 自己紹介
 曽根 壮大(41歳)
 Have Fun Tech LLC 代表社員
 株式会社リンケージ CTO

    兼 COO
 
 そ
 • 日本PostgreSQLユーザ会 勉強会分科会 担当
 • 3人の子供がいます(長女、次女、長男)
 • 技術的にはWeb/LL言語/RDBMSが好きです
 • コミュニティが好き たけ
 ね
 とも

  2. • RESTRICT / NO ACTION
 ◦ 子がいる限り、親を消せない
 • CASCADE
 ◦

    親を消すと子も消える
 ◦ 更新の場合は同様の値に変更を追従する
 • SET NULL
 ◦ 親が消えると子のFKをNULLにする
 • SET DEFAULT
 ◦ 親が消えると子のFKをDEFAULTにする
 ◦ DEFAULT値に対応する親に存在しないと失敗する
 外部キー制約はどのように動くか
  3. • MySQLには遅延制約がないため、実行時に即チェック
 ◦ 遅延制約がないのでRESTRICTとNO ACTIONは実質同様
 ◦ foreign_key_checks=0で制約無効にできるけど……
 • 文字列の場合はcharsetとcollationも一致する必要がある
 ◦

    ちなみに外部キー制約が無くてもJOINのときにINDEXを使って くれないので外部キー制約を使っていなくても注意
 • InnoDBはSET DEFAULTを設定しようとするとREJECTされるので 実質MySQLではSET DEFAULTは使えない
 MySQLの外部キー制約の実装

  4. • MySQL(InnoDB)にはMATCH句が存在しない
 ◦ DDLに書けるし、実行できるがスルーされる
 ◦ 昔のCHECK制約と同じ挙動
 ◦ 実際にはMATCH SIMPLE相当
 ▪

    PostgreSQLのときに説明します
 • 指定するとON DELETE/ON UPDATEが無視される
 ◦ 指定は非推奨とされている(公式ドキュメント記載)
 MySQLの外部キー制約の実装

  5. • PostgreSQLは対象のイベント発生時にトリガが起動して
 対象のテーブルにSQLを実行する
 ◦ 子テーブルにはINDEXは自動的に設定されない
 ◦ なので、親テーブルのDELETEのときにINDEXがない場合は テーブルスキャンが実行される
 ◦ パフォーマンスのボトルネックになりがち


    ▪ そもそも親テーブルをDELETEするなという話もある
 ◦ もちろんINDEXを作成すればINDEXスキャンになる
 PostgreSQLの外部キー制約の実装
 テーブルスキャン=テーブルロック*対象の子テーブルなので障害に繋がりやすい 

  6. • PostgreSQLはデフォルトは実行時に即チェック
 ◦ PostgreSQLには遅延制約がある
 ◦ 外部キー制約を作成時に遅延制約(DEFERRABLE)を設定す れば、遅延制約を利用できる状態になる
 • 遅延制約を有効にすればCOMMIT時に制約チェックをする
 ◦

    ただし、遅延制約を有効にしていても、対象のテーブルに設定 されているCASCADEのトリガは遅延しない
 PostgreSQLの外部キー制約の実装
 MySQLとの違いにPostgreSQLはCASCADEで実行されたクエリに対してもTrigger Eventを発行する 
 子テーブルが消されたarchiveテーブルに保存するトリガなどを仕掛けることができる 
 暗黙的に動くのでもちろん非推奨だがデバッグやトラブルシューティングで必要になることがある 

  7. • PostgreSQLはデフォルトは実行時に即チェック
 ◦ PostgreSQLには遅延制約がある
 ◦ 外部キー制約を作成時に遅延制約(DEFERRABLE)を設定す れば、遅延制約を利用できる状態になる
 • 遅延制約を有効にすればCOMMIT時に制約チェックをする
 ◦

    ただし、遅延制約を有効にしていても、対象のテーブルに設定 されているCASCADEのトリガは遅延しない
 PostgreSQLの外部キー制約の実装
 あとから遅延制約に変更できないので作り直しになる 
 RoRとかDjangoは最初から遅延制約を設定し、 
 遅延制約を無効の状態で外部キー制約を作成する 

  8. PostgreSQLの場合
 データ登録
 遅延制約
 データ削除
 COMMIT
 チェック
 Transaction
 データ登録
 チェック
 即時チェック


    データ登録
 チェック
 COMMIT
 Transaction
 データ登録
 CASCADE
 トリガー
 チェック

  9. • PostgreSQLの参照アクション
 ◦ SET DEFAULTが使える
 ▪ 削除時に対象の子テーブルにDEFAULT値をセット
 ▪ ただし、DEFAULT値が親テーブルに存在すること
 ◦

    親テーブルの削除や更新の時に利用されてる値があれば RESTRICTは遅延制約が有効時でも即チェック
 ◦ NO ACTIONは遅延制約が有効時はCOMMIT時に
 子テーブルをチェックし、利用されていればエラー
 PostgreSQLの外部キー制約の実装

  10. • PostgreSQLの参照アクション
 ◦ SET DEFAULTが使える
 ▪ 削除時に対象の子テーブルにDEFAULT値をセット
 ▪ ただし、DEFAULT値が親テーブルに存在すること
 ◦

    親テーブルの削除や更新の時に利用されてる値があれば RESTRICTは遅延制約が有効時でも即チェック
 ◦ NO ACTIONは遅延制約が有効時はCOMMIT時に
 子テーブルをチェックし、利用されていればエラー
 PostgreSQLの外部キー制約の実装
 遅延制約を活用したいならNO ACTIONとセットになる 

  11. • PostgreSQLのCREATE TABLEにはMATCH句がある 
 ◦ 複合外部キー制約のときの振る舞いを指定できる 
 • MATCH SIMPLE(デフォルト)


    ◦ 複合外部キー制約の一部がNULLになることを許可し、対象の行は制約のチェック対象 外にする
 ◦ MySQLと一緒
 • MATCH FULL
 ◦ 複合外部キー制約は全部NULLか、全部設定するか強制する 
 • MATCH PARTIALは未実装
 ◦ 外部キー制約の一部のNULLを許可するが、NULL以外の列の組み合わせは 
 親テーブルと一致している必要がある 
 PostgreSQLの外部キー制約の実装

  12. • PostgreSQLのCREATE TABLEにはMATCH句がある 
 ◦ 複合外部キー制約のときの振る舞いを指定できる 
 • MATCH SIMPLE(デフォルト)


    ◦ 複合外部キー制約の一部がNULLになることを許可し、対象の行は制約のチェック対象 外にする
 ◦ MySQLと一緒
 • MATCH FULL
 ◦ 複合外部キー制約は全部NULLか、全部設定するか強制する 
 • MATCH PARTIALは未実装
 ◦ 外部キー制約の一部のNULLを許可するが、NULL以外の列の組み合わせは 
 親テーブルと一致している必要がある 
 PostgreSQLの外部キー制約の実装
 SQLアンチパターン 第二版の追加の章として出てくる 
 みんなMATCH句を知らないって嘆いてた 

  13. create table shop ( id integer not null constraint shop_pk

    primary key, name integer ); 
 外部キー制約が救ってくれること create table "order" ( id integer not null , item_id integer not null constraint order_item_id_fk references item, constraint order_pk primary key (id, item_id) ); create table item ( id integer not null constraint item_pk primary key, shop_id integer constraint item_shop_id_fk references shop );
  14. 外部キー制約が救ってくれること id name 1 shop A 2 shop B id

    shop_id 1 1 2 1 3 2 id item_id 1 1 1 3 2 1 注文が同時に複数の店舗に注文している 
 これを1回の注文では1店舗の注文にしたい場合は? 

  15. create table shop ( id integer not null constraint shop_pk

    primary key, name integer ); 外部キー制約が救ってくれること create table shop_order_detail ( order_id integer not null , shop_id integer not null , item_id integer not null , constraint shop_order_detail_pk primary key (order_id, shop_id, item_id), constraint shop_order_detail_shop_order_id_shop_id_fk foreign key (order_id, shop_id) references shop_order, constraint shop_order_detail_item_id_shop_id_fk foreign key (item_id, shop_id) references item (id, shop_id) ); create table item ( id integer not null constraint item_pk primary key, shop_id integer constraint item_shop_id_fk references shop, constraint item_pk_2 unique (id, shop_id) ); create table shop_order ( id integer not null , shop_id integer not null constraint shop_order_shop_id_fk references shop, constraint shop_order_pk primary key (id, shop_id) ); orderは必ずshopに紐づく 

  16. create table shop ( id integer not null constraint shop_pk

    primary key, name integer ); 外部キー制約が救ってくれること create table shop_order_detail ( order_id integer not null , shop_id integer not null , item_id integer not null , constraint shop_order_detail_pk primary key (order_id, shop_id, item_id), constraint shop_order_detail_shop_order_id_shop_id_fk foreign key (order_id, shop_id) references shop_order, constraint shop_order_detail_item_id_shop_id_fk foreign key (item_id, shop_id) references item (id, shop_id) ); create table item ( id integer not null constraint item_pk primary key, shop_id integer constraint item_shop_id_fk references shop, constraint item_pk_2 unique (id, shop_id) ); create table shop_order ( id integer not null , shop_id integer not null constraint shop_order_shop_id_fk references shop, constraint shop_order_pk primary key (id, shop_id) ); orderに紐づいたshopしか登録できない 

  17. create table shop ( id integer not null constraint shop_pk

    primary key, name integer ); 外部キー制約が救ってくれること create table shop_order_detail ( order_id integer not null , shop_id integer not null , item_id integer not null , constraint shop_order_detail_pk primary key (order_id, shop_id, item_id), constraint shop_order_detail_shop_order_id_shop_id_fk foreign key (order_id, shop_id) references shop_order, constraint shop_order_detail_item_id_shop_id_fk foreign key (item_id, shop_id) references item (id, shop_id) ); create table item ( id integer not null constraint item_pk primary key, shop_id integer constraint item_shop_id_fk references shop, constraint item_pk_2 unique (id, shop_id) ); create table shop_order ( id integer not null , shop_id integer not null constraint shop_order_shop_id_fk references shop, constraint shop_order_pk primary key (id, shop_id) ); itemに紐づいたshopしか登録できない 

  18. create table shop ( id integer not null constraint shop_pk

    primary key, name integer ); 外部キー制約が救ってくれること create table shop_order_detail ( order_id integer not null , shop_id integer not null , item_id integer not null , constraint shop_order_detail_pk primary key (order_id, shop_id, item_id), constraint shop_order_detail_shop_order_id_shop_id_fk foreign key (order_id, shop_id) references shop_order, constraint shop_order_detail_item_id_shop_id_fk foreign key (item_id, shop_id) references item (id, shop_id) ); create table item ( id integer not null constraint item_pk primary key, shop_id integer constraint item_shop_id_fk references shop, constraint item_pk_2 unique (id, shop_id) ); create table shop_order ( id integer not null , shop_id integer not null constraint shop_order_shop_id_fk references shop, constraint shop_order_pk primary key (id, shop_id) ); idだけでユニークだが、外部キー制約はユニークキーしか登録できないのでshop_idと組 み合わせる
 これでshop_order_detailに登録されるときに orderとitemのshopは必ず一致する 
 これによってorderに紐づいていないshopのitemは誤登録されない