回答:
データベースに外部キーを配置します。保存する前にアプリケーションのデータを検証したとしても、FKは優れた品質のQAバックアップです。最初の近似として、アプリケーションには常にデータの問題があります。このような制御をシステムから外すと、データが静かに破損する障害モードが発生します。
これを実際に見るために数年間データウェアハウスで働くことほど素晴らしいものはありません。アプリケーションコードでデータの整合性を強制できると考えたアプリケーション開発者によるうっかりミスがあった後、時間をかけて作品を拾います。これを行うときはいつでも、あなたはアプリケーション管理のデータの整合性はうぬぼれにすぎないと結論付けます。
さらに、クエリオプティマイザーは外部キーを使用してテーブル結合に関することを推測できるため、FKを使用するとクエリプランがより効率的になります。
外部キーには他にも多くの利点があります。みんなにお願いします-FKをデータベースに置きます。
参照整合性は、可能な限り低いレベル(基礎となるデータベース)で処理する必要があります。リレーショナルデータベース管理システムは、これを処理するために最適化されています。ことわざの輪を再発明することは意味がありません。
アプリケーションコードでドメインロジックを定義して、DMLステートメントがRI例外を引き起こすことを防ぐことは許容されますが、これはデータベース内の外部キー関係の代替と見なされるべきではありません。
これはDBAに焦点を当てたグループであるため、これがダウン投票されることを完全に期待して、ここで四肢に出かけます。
ほとんどのシナリオでは、厳密な外部キーを使用することが最善の決定であることに同意します。ただし、外部キーが解決するよりも多くの問題を引き起こす場合があります。
トラフィックの多いWebアプリケーションなどの非常に高度なコンカレント環境を扱っており、十分に確立された堅牢なORMを使用している場合、外部キーによりロックの問題が発生し、サーバーのスケーリングと保守が困難になります。子テーブルの行を更新すると、親行もロックされます。多くのシナリオで、これはロック競合のために並行性を大幅に制限する可能性があります。さらに、少なくとも一時的に参照整合性ルールを(意図的に)破る必要があるアーカイブプロセスなど、個々のテーブルのメンテナンスを実行する必要がある場合があります。外部キーが適切に配置されている場合、これは非常に困難な場合があり、一部のRDBMSでは外部キー制約を無効にすると、テーブルの再構築が発生します。
データベース外部の参照整合性を理解できる堅牢なフレームワークを使用する必要があるという警告を含めていることを理解してください。それでも、参照整合性の問題が発生する可能性があります。ただし、孤立した行や軽微な参照整合性違反があるのはそれほど大した問題ではない場合が多くあります。 私は、Webアプリケーションの大部分がこのカテゴリに分類されると主張します。
そうは言っても、だれもFacebookから始めません。データベースで外部キーを定義することから始めます。モニター。最終的に問題が発生する場合は、これらの制約の一部をドロップしてスケーリングする必要がある場合があることを理解してください。
結論:ほとんどのデータベースには外部キーが必要です。高度な同時実行環境は、外部キーを使用しない方がよい場合があります。そのポイントに達した場合、これらの制約の削除を検討する必要があるかもしれません。
私は今、難燃性のスーツを着に行きます。
編集2012-03-23 7:00 AM
外部キーのロックの結果を考える際に、暗黙的に内部的に生成され、サーバーの負荷を増加させる追加のすべての行ルックアップのコストについて言及するのを怠りました。
最終的に、私のポイントは、外部キーは無料ではないということです。多くの場合、コストはそれだけの価値がありますが、そのコストがメリットを上回るシナリオがあります。
編集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制約のために、InnoDBがfilm.film_id = 508の行で共有ロックを保持しているために発生します。したがって、その行の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
users
多くの場合、関連するテーブルが多数あるWebアプリケーションのテーブルを考えます。基本的に、関連する行に対する操作により、親行への更新が妨げられます。複数の外部キー関係と多くの並行性がある場合、これは難しい問題になります。
FK制約により、テーブルメンテナンスの回避策も困難になります。PerconaのPeter Zaitsevが、これに関するブログ投稿で、私ができる以上にそれを説明しています。InnodbForeign Keysをハイジャックします。