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

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

soudai sone PRO
February 03, 2026

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

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

soudai sone PRO

February 03, 2026

Transcript

74 slides
  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は誤登録されない 


More Decks by soudai sone

Other Decks in Technology