Answers:
将外键放在数据库上。即使在保存应用程序之前验证了数据,FK也是很好的质量检查备份。首先,应用程序始终会出现数据问题。将这样的控件留在系统之外只会引发故障模式,在这种模式下数据会被静默破坏。
几年来没有什么比在数据仓库中工作更实际的了。在应用程序开发人员犯了点头错误之后,您就花费了很多时间来整理这些碎片,他们认为应用程序开发人员可以在应用程序代码中实现数据完整性。花任何时间进行操作,您将得出结论,由应用程序管理的数据完整性仅是一种假设。
此外,查询优化器可以使用外键来推断有关表联接的内容,因此FK将导致更有效的查询计划。
外键还有很多其他好处。大家帮个忙-将FK放在数据库中。
由于这是一个专注于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有一篇关于此的博客文章,比我能更好地解释它:劫持Innodb外键。
优良作法是在数据库中使用外键。它有助于-
ON DELETE CASCADE