web-dev-qa-db-ja.com

外部キーはどこに定義する必要がありますか?

データベースまたはアプリケーションのコード部分で外部キーを定義する方が良いですか?

21
newuser

外部キーをデータベースに配置します。保存する前にアプリケーションのデータを検証しても、FKはQAの適切なバックアップです。最初の概算として、アプリケーションalwaysにはデータの問題があります。このような制御をシステムから外すと、データが静かに破損する障害モードが発生します。

これを実際に確認するために、数年間データウェアハウジングで作業するようなものはありません。アプリケーションコードでデータの整合性を強制できると考えていたアプリケーション開発者によるうっとうしい間違いの後、断片を拾うことに時間を費やします。これを行う時間を費やすと、アプリケーションで管理されたデータの整合性は単なる考えに過ぎないと結論付けられます。

さらに、クエリオプティマイザーは外部キーを使用してテーブルの結合について推測できるため、FKを使用するとクエリプランがより効率的になります。

外部キーには他にも多くの利点があります。誰もが好む-データベースにFKを置く。

参照整合性は、基礎となるデータベースとなる最低レベルで処理する必要があります。リレーショナルデータベース管理システムは、これを処理するために最適化されています。ことわざを再発明することは意味がありません。

アプリケーションコードでドメインロジックを定義して、DMLステートメントでRI例外が発生するのを防ぐこともできますが、これはデータベースの外部キー関係の代わりとは見なされません。

15
Thomas Stringer

これはDBAに焦点を当てたグループであるため、これが反対投票になることを完全に期待して、ここで手足を出します。

厳密な外部キーを使用することが、ほとんどのシナリオで最良の決定であることに同意します。ただし、外部キーが解決するよりも多くの問題を引き起こす場合があります。

トラフィックの多いWebアプリケーションなどの非常に並行性の高い環境を扱い、確立された堅牢なORMを使用している場合、外部キーがロックの問題を引き起こし、サーバーのスケーリングと保守が困難になる可能性があります。子テーブルの行を更新すると、親行もロックされます。多くのシナリオで、これはロック競合のために並行性を大幅に制限する可能性があります。さらに、少なくとも一時的に参照整合性ルールを(意図的に)破る必要があるアーカイブプロセスなど、個々のテーブルでメンテナンスを実行する必要がある場合もあります。外部キーを配置すると、これは非常に困難になる可能性があり、一部のRDBMSで外部キー制約を無効にすると、テーブルの再構築が発生します。これは、かなりのダウンタイムを必要とする時間のかかるプロセスです。

データベースの外部の参照整合性を理解できる堅牢なフレームワークを使用する必要があるという警告が含まれていることを理解してください。それでも、参照整合性の問題が発生する可能性があります。ただし、孤立した行や小さな参照整合性違反があることはそれほど大きな問題ではない場合が多くあります。私は大多数と主張しますWebアプリケーションのこのカテゴリに分類されます。

とはいえ、Facebookから始める人はいません。まず、データベースに外部キーを定義します。モニター。問題が発生した場合は、これらの制約の一部を削除してスケーリングする必要がある場合があることを理解してください。

結論:ほとんどのデータベースには外部キーが必要です。並行性の高い環境では、外部キーがないとうまくいく場合があります。その時点に達した場合は、これらの制約を削除することを検討する必要があるかもしれません。

今から難燃スーツを着用します。

EDIT 2012-03-23 7:00 AM

外部キーのロックの影響について考える際に、暗黙的に内部的に生成されるすべての追加の行ルックアップのコストについて言及するのを怠り、サーバーの負荷が増加しました。

結局、私のポイントは、外部キーは無料ではないということです。多くの場合、コストはそれだけの価値がありますが、そのコストがメリットを超えるシナリオがあります。

EDIT 2012-03-23 7:38 AM

具体的にしましょう。この例ではMySQL/InnoDBを選択していますが、これは外部キーの動作についてはあまり尊重されていませんが、私が最もよく知っているものであり、最も一般的に使用されているWebデータベースです。これから紹介する例で他のデータベースの方がうまくいくかどうかはわかりません。

親を参照する外部キーを持つ子テーブルを考えます。例として、MySQLのsakilaサンプルデータベースのfilmおよびfilm_actorテーブルを参照してください。

CREATE TABLE `film` (
  `film_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `description` text,
  `release_year` year(4) DEFAULT NULL,
  `language_id` tinyint(3) unsigned NOT NULL,
  `original_language_id` tinyint(3) unsigned DEFAULT NULL,
  `rental_duration` tinyint(3) unsigned NOT NULL DEFAULT '3',
  `rental_rate` decimal(4,2) NOT NULL DEFAULT '4.99',
  `length` smallint(5) unsigned DEFAULT NULL,
  `replacement_cost` decimal(5,2) NOT NULL DEFAULT '19.99',
  `rating` enum('G','PG','PG-13','R','NC-17') DEFAULT 'G',
  `special_features` set('Trailers','Commentaries','Deleted Scenes','Behind the Scenes') DEFAULT NULL,
  `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`film_id`),
  KEY `idx_title` (`title`),
  KEY `idx_fk_language_id` (`language_id`),
  KEY `idx_fk_original_language_id` (`original_language_id`),
  CONSTRAINT `fk_film_language` FOREIGN KEY (`language_id`) REFERENCES `language` (`language_id`) ON UPDATE CASCADE,
  CONSTRAINT `fk_film_language_original` FOREIGN KEY (`original_language_id`) REFERENCES `language` (`language_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8

CREATE TABLE `film_actor` (
  `actor_id` smallint(5) unsigned NOT NULL,
  `film_id` smallint(5) unsigned NOT NULL,
  `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`actor_id`,`film_id`),
  KEY `idx_fk_film_id` (`film_id`),
  CONSTRAINT `fk_film_actor_actor` FOREIGN KEY (`actor_id`) REFERENCES `actor` (`actor_id`) ON UPDATE CASCADE,
  CONSTRAINT `fk_film_actor_film` FOREIGN KEY (`film_id`) REFERENCES `film` (`film_id`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8

私の例では、関連する制約はfilm_actor(fk_film_actor_film)です。

session1> BEGIN;
session1> INSERT INTO film_actor (actor_id, film_id) VALUES (156, 508);
Query OK, 1 row affected (0.00 sec)

session2> BEGIN;
session2> UPDATE film SET release_year = 2005 WHERE film_id = 508;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

子テーブルに挿入しているときに、親行の無関係なフィールドを更新できなかったことに注意してください。これは、film_actorのFK制約により、film.film_id = 508の行でInnoDBが共有ロックを保持しているために発生し、その行へのUPDATEは必要な排他ロックを取得できません。その操作を元に戻し、最初にUPDATEを実行すると、同じ動作になりますが、INSERTはブロックされます。

session1> BEGIN;
session1> UPDATE film SET release_year = 2005 WHERE film_id = 508;
Query OK, 1 row affected (0.00 sec)

session2> BEGIN;
session2> INSERT INTO film_actor (actor_id, film_id) VALUES (156, 508);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

多くの場合、関連するテーブルが数十もあるWebアプリケーションのusersテーブルについて考えてみます。基本的に、関連する行に対する操作は、親行の更新を防ぎます。複数の外部キー関係があり、同時実行性が高い場合、これは困難な問題になる可能性があります。

FK制約により、テーブルのメンテナンスの回避策も困難になる場合があります。 PerconaのPeter Zaitsevが、これについて私よりも説明しているブログ投稿を公開しています Innodb外部キーのハイジャック

12
Aaron Brown

データベースで外部キーを使用することをお勧めします。助けになる-

  • 不要なデータの可能性を排除してデータの整合性を維持する
  • パフォーマンスを向上させます。フィールドに自動インデックスを付けるシステムでは、外部キー参照によりパフォーマンスが向上します。
  • プログラマーが書くコードを少なくする。のように、ON DELETE CASCADE
6
Abdul Ahad