你应该在哪里定义外键?

new*_*ser 21 foreign-key database-design best-practices

在数据库中定义外键还是在应用程序的代码部分中定义外键更好?

Con*_*lls 43

将外键放在数据库中。即使您在保存应用程序之前验证数据,FK 也是一个很好的 QA 备份。对于第一个近似值,应用程序总是有数据问题。将这样的控制排除在系统之外只会引发故障模式,在这种模式下,数据会被悄悄损坏。

没有什么比在数据仓库工作几年更能看到这一点的了。在应用程序开发人员认为他们可以在应用程序代码中强制执行数据完整性的错误之后,您将花时间收拾残局。花任何时间做这件事,你会得出结论,应用程序管理的数据完整性只不过是一种自负。

此外,查询优化器可以使用外键来推断有关表连接的内容,因此 FK 将产生更高效的查询计划。

外键还有很多其他好处。帮大家一个忙 - 将 FK 放在数据库中。


Tho*_*ger 15

参照完整性应该在尽可能低的级别上处理,这将是底层数据库。关系数据库管理系统经过优化可以处理这个问题。重新发明众所周知的轮子是没有意义的。

在应用程序代码中定义域逻辑来防止DML语句甚至导致RI异常是可以接受的,但这不应被视为数据库中外键关系的替代。


Aar*_*own 11

我将在这里完全期待这会被否决,因为这是一个以 DBA 为重点的小组。

我同意在大多数情况下使用严格的外键是最好的决定。但是,在某些情况下,外键引起的问题多于它们解决的问题。

当您处理非常高并发的环境(例如高流量 Web 应用程序)并使用完善的、健壮的 ORM 时,外键可能会导致锁定问题,从而使扩展和维护服务器变得困难。更新子表中的行时,父行也被锁定。在许多情况下,由于锁定争用,这会极大地限制并发。此外,有时您必须对单个表执行维护,例如您可能需要(有意地)至少暂时破坏参照完整性规则的归档过程。有了外键,这可能会非常困难,并且在某些 RDBMS 中,禁用外键约束将导致重建表,这是一个耗时的过程,可能需要大量停机。

理解我包括的警告,即您必须使用能够理解数据库外部引用完整性的强大框架。尽管如此,您最终还是会遇到一些参照完整性问题。然而,在很多情况下,孤立行或轻微的参照完整性违规并不是什么大问题。 我认为大多数 Web 应用程序都属于这一类。

话虽如此,但没有人一开始是 Facebook。首先在数据库中定义外键。监视器。如果您最终遇到问题,请了解您可能需要放弃其中一些限制以进行扩展。

结论:大多数数据库应该有外键。如果没有外键,高度并发的环境可能会更好。如果达到那个点,您可能需要考虑放弃这些限制。

我现在要去穿上我的阻燃服。

编辑 2012-03-23 7:00AM

在考虑外键的锁定后果时,我忽略了内部隐式生成的所有额外行查找的成本,增加了服务器负载。

最后,我的观点是外键不是免费的。在许多情况下,成本是值得的,但在某些情况下,成本会超过其收益。

编辑 2012-03-23 7:38AM

让我们具体一点。我在这个例子中选择了 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
Run Code Online (Sandbox Code Playgroud)

对于我的示例,相关约束是 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
Run Code Online (Sandbox Code Playgroud)

请注意,在插入子表时,我无法更新父行中不相关的字段。发生这种情况是因为 InnoDB 在 film.film_id = 508 的行上持有共享锁,这是由于 film_actor 的 FK 约束,因此对该行的 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
Run Code Online (Sandbox Code Playgroud)

考虑usersWeb 应用程序中的一个表,其中通常有几十个相关表。本质上,任何相关行上的任何操作都会阻止更新父行。当您有多个外键关系和大量并发时,这可能是一个具有挑战性的问题。

FK 约束也可以使表维护的变通方法具有挑战性。Percona 的 Peter Zaitsev 有一篇关于这个的博客文章,比我能更好地解释它:劫持 Innodb 外键


小智 6

在数据库中使用外键是一种很好的做法。它有助于-

  • 通过消除不需要的数据的可能性来保持数据完整性
  • 以提高性能。在自动索引字段的系统中,外键引用可以提高性能
  • 程序员编写更少的代码。喜欢,使用ON DELETE CASCADE