返回顶部
首页 > 资讯 > 数据库 >为什么数据库不应该使用外键
  • 549
分享到

为什么数据库不应该使用外键

摘要

为什么这么设计(Why’s THE Design)是一系列关于计算机领域中程序设计决策的文章,我们在这个系列的每一篇文章中都会提出一个具体的问题并从不同的角度讨论这种设计的优缺点、对具体实现造成的影响。如果你有想要了解的问题,可以在文章下面


	为什么数据库不应该使用外键
[数据库教程]

为什么这么设计(Why’s THE Design)是一系列关于计算机领域中程序设计决策的文章,我们在这个系列的每一篇文章中都会提出一个具体的问题并从不同的角度讨论这种设计的优缺点、对具体实现造成的影响。如果你有想要了解的问题,可以在文章下面留言。

当我们想要持久化地存储数据时,使用关系型数据库往往都是最稳妥的选择,这不仅因为今天的关系型数据库种类非常丰富并且稳定,还因为不同社区对关系型数据库的支持都非常完备。我们在前面的文章中曾经分析过 为什么 Mysql 的自增主键不单调也不连续,这篇文章我们来分析关系型数据库中另一个重要的概念 — 外键(Foreign Key)。

在关系型数据库中,外键也被称为关系键,它是关系型数据库中提供关系表之间连接的多个列1,这一组数据列是当前关系表中的外键,也必须是另一个关系表中的候选键(Candidate Key),我们可以通过候选键在当前表中找到唯一的元素2。在通常情况下,我们都会使用关系表中的主键作为其他表中的外键,这样才可以满足关系型数据库对外键的约束。

技术图片

图 1 - 关系型数据库与外键

外键不仅仅是数据库表中的一个整数,它还提供了额外的一致性保证。因为数据库往往是整个系统的真理之源(Source of Truth),所以保证数据的一致性和正确性非常重要,关系型数据库虽然提供了外键、触发器等特性保证一致性,但是在今天的生产环境中却很少被使用。

引用完整性(Referential Integrity)是数据的属性,如果数据拥有该属性,那么数据中所有的引用都是合法的,在关系型数据库的上下文中,这就意味着关系型数据库中引用另一个表中的值必须存在3。

ALTER TABLE posts
ADD CONSTRaiNT FOREIGN KEY (author_id)
REFERENCES authors(id);

上述 sql 语句可以向关系表中增加外键约束,该 SQL 语句的执行前提是 posts 表中存在 author_id 字段。从 SQL 语句中的 CONSTRAINT 关键字我们也能推测出外键不是一种数据类型,它是不同关系表之间的约束。

技术图片

图 2 - 无状态服务与数据库

不使用外键的原因其实很简单,mysqlpostgresql 等关系型数据库很难水平扩容,但是无状态的服务往往都可以很容易地扩容。由于外键等特性需要数据库执行额外的工作,而这些操作会占用数据库的计算资源,所以我们可以将大部分的需求都迁移到无状态的服务中完成以降低数据库的工作负载。

根据更新和删除时的行为不同,我们可以将外键分成 RESTRICT、CASCADE 和 SET NULL 等几种4,当我们为关系表中的字段增加外键约束时,需要指定外键的类型,最常见的也就是 RESTRICT 和 CASCADE 两种,其中 RESTRICT 为外键的默认类型,不同类型的外键会带来不同的额外开销,而这些额外开销就是我们不使用外键的理由:

  • 使用 RESTRICT 会在更新或者删除记录时对外键对应的记录是否存在进行一致性检查;
  • 使用 CASCADE 会在更新或者删除记录时触发级联更新或者删除操作;
    注意:MySQL 中的 NO ACTioN 和 RESTRICT 具有相同的语义5。

接下来我们会详细介绍关系型数据库如何处理上述两种不同类型的外键,而我们应该如何在应用中模拟这些功能。

一致性检查

当我们使用默认的外键类型 RESTRICT 时,在创建、修改或者删除记录时都会检查引用的合法性。想要在 MySQL 等数据库中触发外键的一致性检查其实非常容易,假设我们的数据库中包含 posts(id, author_id, content) 和 authors(id, name) 两张表,在执行如下所示的操作时都会触发数据库对外键的检查:

  • 向 posts 表中插入数据时,检查 author_id 是否在 authors 表中存在;
  • 修改 posts 表中的数据时,检查 author_id 是否在 authors 表中存在;
  • 删除 authors 表中的数据时,检查 posts 中是否存在引用当前记录的外键;
    作为专门用于管理数据的系统,数据库与应用服务相比能够更好地保证完整性,而上述的这些操作都是引入外键带来的额外工作,不过这也是数据库保证数据完整性的必要代价。上述的这些分析都是理论上的定性分析,我们其实可以简单的定量分析一下引入外键对性能的影响。

在这里我们在数据库中同时创建 authors、posts 和 foreign_key_posts 三种表,如下所示,其中 posts 和 foreign_key_posts 两个表中的列完全相同,只是 foreign_key_posts 表为 author_id 字段增加了 RESTRICT 类型的外键约束:

技术图片

图 3 - 外键性能测试关系图

我们先在 authors 表中插入一条记录,随后分别在 posts 和 foreign_key_posts 中插入多条新数据列引用该条记录,前者不会检查外键的合法性,而后者会做额外的检查。你可以在 这里 找到作者用来测试外键额外开销的 Go 语言代码6,经过多次基准测试,我们可以得到如下所示的结果:

BenchmarkBaseline-8             3770        309503 ns/op
BenchmarkForeignKey-8           3331        317162 ns/op

BenchmarkBaseline-8             3192        315506 ns/op
BenchmarkForeignKey-8           3381        315577 ns/op

BenchmarkBaseline-8             3298        312761 ns/op
BenchmarkForeignKey-8           3829        345342 ns/op

BenchmarkBaseline-8             3753        291642 ns/op
BenchmarkForeignKey-8           3948        325239 ns/op

作者执行了 4 次外键的基准测试,虽然 4 次测试的结果不是特别稳定,但是使用外键的用例在每次测试中都明显弱于不使用外键的用例,外键带来的额外开销分别为 ~2.47%、~0.02%、~10.41% 和 ~11.52%。这里的基准测试只是一个比较简单的定量分析,但是我们也可以从结果中看到大概的趋势 — 外键的完整性检查确实会带来额外的性能开销,而这些开销在高并发的服务中需要慎重考虑。

想要在应用程序中模拟数据库外键的功能其实比较容易,我们只需要遵循以下的几个准则:

  • 向表中插入数据或者修改表中的数据时,都应该执行额外的 SELECT 语句确保它引用的数据在数据库中存在;
  • 在删除数据之前需要执行额外的 SELECT 语句检查是否存在当前记录的引用;
    需要注意的是为了保证一致性,我们需要在事务中执行上述的查询和修改语句,这样才能完整模拟外键的功能;当我们向 posts 表中插入或者修改数据时,需要的处理相对比较简单,我们只需要执行有限的 SELECT 语句并按照如下所示的模式执行对应的操作就可以了:
BEGIN
SELECT * FROM authors WHERE id =  FOR UPDATE;
-- INSERT INTO posts ... / UPDATE posts ...
END

但是如果我们要删除 authors 表中的数据,就需要查询所有引用 authors 数据的表;如果有 10 个表都有指向 authors 表的外键,我们就需要在 10 个表中查询是否存在对应的记录,这个过程相对比较麻烦,不过也是为了实现完整性的必要代价,不过这种模拟外键方法其实远比使用外键更消耗资源,它不仅需要查询关联数据,还要通过网络发送更多的数据包。

级联操作

当我们在关系型数据库中创建外键约束时,如果使用如下所示的 SQL 语句指定更新或者删除记录时使用 CASCADE 行为,那么在客户端更新或者删除数据时就会触发级联操作:

ALTER TABLE posts
ADD CONSTRAINT FOREIGN KEY (author_id)
REFERENCES authors(id)
ON UPDATE CASCADE
ON DELETE CASCADE;
  • 当客户端更新 authors 表中记录的主键时,数据库会同时更新 posts 表中所有引用该记录的外键;
  • 当客户端删除 authors 表中的记录时,数据库会删除所有与 authors 表关联的记录;
    不过无论是执行更新还是删除操作,数据库都可以保证各个关系表之间引用的一致性和合法性不会出现引用到不存在记录的情况,与 RESTRICT 行为一样,所有外键的更新和删除行为都可以通过执行额外的检查和操作保证数据的一致。

技术图片

图 4 - 复杂的级联操作

虽然级联删除的出发点也是保证数据的完整性,但是在设计关系表之间的不同关系时,我们也需要注意级联删除引起的数据大规模删除的问题。如上图所示,当客户端想要在数据库中删除 authos 表中的数据时,如果我们同时在 authors 和 posts 中指定了级联删除的行为,那么数据库会同时删除所有关联的 posts 记录以及与 posts 表关联的 comments 数据。

这种涉及多级的级联删除行为在数据量较小的数据库中不会导致问题,但是在数据量较大的数据库中删除关键数据可能会引起雪崩,一条记录的删除可能会被放大到几十倍甚至上百倍,这些对磁盘的随机读写会带来巨大的开销,是我们想要尽可能避免的情况。如果我们能够较好地设计各个表之间的关系并且慎用 CASCADE 行为,这对于保证数据库中数据的合法性有着很重要的意义,使用该特性可以避免数据库中出现过期的、不合法的数据,但是在使用时也要合理预估可能造成的最坏情况。

手动实现数据库的级联删除操作是可行的,如果我们在一个事务中按照顺序删除所有的数据,确实可以保证数据的一致性,但是这与外键的级联删除功能没有太大的区别,反而会有更差的表现。如果我们能够接受在一个时间窗口内的数据不一致,就可以将一个大号的删除任务拆成多个子任务分批执行,降低对数据库影响的峰值。

DELETE FROM posts WHERE author_id = 1 LIMIT 100;
DELETE FROM posts WHERE author_id = 1 LIMIT 100;
...
DELETE FROM authors WHERE id = 1;

与数据库外键的 CASCADE 相比,这种方式会带来更大的额外开销,只是我们能降低对数据库性能的瞬时影响。

总结

外键提供的几种在更新和删除时的不同行为都可以帮助我们保证数据库中数据的一致性和引用合法性,但是外键的使用也需要数据库承担额外的开销,在大多数服务都可以水平扩容的今天,高并发场景中使用外键确实会影响服务的吞吐量上限。在数据库之外手动实现外键的功能是可能的,但是却会带来很多维护上的成本或者需要我们在数据一致性上做出一些妥协。我们可以从可用性、一致性几个方面分析使用外键、模拟外键以及不使用外键的差异:

  • 不使用外键牺牲了数据库中数据的一致性,但是却能够减少数据库的负载;
  • 模拟外键将一部分工作移到了数据库之外,我们可能需要放弃一部分一致性以获得更高的可用性,但是为了这部分可用性,我们会付出更多的研发与维护成本,也增加了与数据库之间的网络通信次数;
  • 使用外键保证了数据库中数据的一致性,也将全部的计算任务全部交给了数据库;

在大多数不需要高并发或者对一致性有较强要求的系统中,我们可以直接使用数据库提供的外键帮助我们对数据进行校验,但是在对一致性要求不高的、复杂的场景或者大规模的团队中,不使用外键也确实可以为数据库减负,而大团队也有更多的时间和精力去设计其他的方案,例如:分布式的关系型数据库。

当我们考虑应不应该在数据库中使用外键时,需要关注的核心我们的数据库承担这部分计算任务后会不会影响系统的可用性,在使用时也不应该一刀切的决定用或者不用外键,应该根据具体的场景做决策,我们在这里介绍了两个使用外键时可能遇到的问题:

  • RESTRICT 外键会在更新和删除关系表中的数据时对外键约束的合法性进行检查,保证外键不会引用到不存在的记录;
  • CASCADE 外键会在更新和删除关系表中的数据时触发对关联记录的更新和删除,在数据量较大的数据库中可能会有数量级的放大效果;

我们在很多时候其实并不能选择是否使用外键,大多数公司的 DBA 都会对数据库系统的使用有比较明确的规定,但是我们要清楚做出使用外键和不使用外键这一抉择的原因。到最后,我们还是来看一些比较开放的相关问题,有兴趣的读者可以仔细思考一下下面的问题:

  • 数据库中还有哪些特性是我们在生产环境中不会使用的?为什么?
  • 分布式的关系型数据库与 MySQL 等传统数据库有哪些区别?

如果对文章中的内容有疑问或者想要了解更多软件工程上一些设计决策背后的原因,可以在博客下面留言,作者会及时回复本文相关的疑问并选择其中合适的主题作为后续的内容。

推荐阅读

  • 为什么 MySQL 的自增主键不单调也不连续
  • 为什么 MySQL 使用 B+ 树
  • 『浅入浅出』MySQL 和 InnoDB
  1. Techopedia: Foreign Key https://www.techopedia.com/definition/7272/foreign-key ??

  2. Wikipedia: Foreign key Https://en.wikipedia.org/wiki/Foreign_key ??

  3. Wikipedia: Referential integrity https://en.wikipedia.org/wiki/Referential_integrity ??

  4. 1.20.5 FOREIGN KEY Constraints https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html ??

  5. MySQL RESTRICT and NO ACTION https://stackoverflow.com/questions/5809954/mysql-restrict-and-no-action ??

  6. Benchmark Foreign Key https://GISt.GitHub.com/draveness/8e714b61a1d20c836fd9ac8fa6553cdd ??

技术图片

为什么数据库不应该使用外键

原文地址:https://blog.51cto.com/14890690/2517742

您可能感兴趣的文档:

--结束END--

本文标题: 为什么数据库不应该使用外键

本文链接: https://lsjlt.com/news/7772.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 为什么数据库不应该使用外键
    为什么这么设计(Why’s THE Design)是一系列关于计算机领域中程序设计决策的文章,我们在这个系列的每一篇文章中都会提出一个具体的问题并从不同的角度讨论这种设计的优缺点、对具体实现造成的影响。如果你有想要了解的问题,可以在文章下面...
    99+
    2019-08-06
    为什么数据库不应该使用外键 数据库入门 数据库基础教程 数据库 mysql
  • 为什么不能用uuid作为数据库主键
    这篇文章主要介绍“为什么不能用uuid作为数据库主键”,在日常操作中,相信很多人在为什么不能用uuid作为数据库主键问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”为什么不能用...
    99+
    2024-04-02
  • 为什么不使用MySQL数据库
    小编给大家分享一下为什么不使用MySQL数据库,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!  1、MySQL(和PHP搭配之最...
    99+
    2024-04-02
  • Mysql关于数据库是否应该使用外键约束详解说明
    一、前言 对于【是否使用外键约束】这个话题已经是老生常谈的了。在学校中,老师交给我们的大多是需要我们建立外键约束,但进入了实际工作很多时候并不会使用外键,而是通过代码逻辑来控制。包括...
    99+
    2024-04-02
  • 数据库中为什么我们需要使用键
    这篇文章将为大家详细讲解有关数据库中为什么我们需要使用键,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。为什么我们需要使用键?在实际应用中,存储数据所需的表的数量很大,并且...
    99+
    2024-04-02
  • MySQL数据库中性别字段应该使用什么数据类型?
    在MySQL数据库中,性别字段通常可以使用ENUM类型来存储。ENUM是一种枚举类型,它允许我们在一组预定义的值中选择一个作为字段的值。在表示性别这样一个固定且有限的选项时,ENUM是...
    99+
    2024-03-14
    mysql 数据类型 性别
  • 数据库-三范式优化与不推荐使用外键
    反三范式其实是基于三范式所调整的,没有冗余的数据库未必是最好的数据库,完全按照第三范式做表的设计可能会降低查询效率(涉及多表查询,多表连接JOIN,临时表创建GROUP BY),有时候为了提高运行效率,就必须降低范式的标准...
    99+
    2016-12-01
    数据库-三范式优化与不推荐使用外键
  • 数据库管理的利器:主键与外键的应用
    ...
    99+
    2024-04-02
  • 如何在MySQL数据库中使用外键
    如何在MySQL数据库中使用外键 在关系型数据库中,外键是一种非常重要的概念,它能够帮助我们建立不同表之间的关联关系,并确保数据完整性。在MySQL数据库中,要使用外键,需要遵循一定的...
    99+
    2024-04-02
  • 数据库外键设置的步骤是什么
    设置数据库外键的步骤如下: 确定需要创建外键的表和外键所引用的表。 确保外键所引用的表中有一个主键或唯一约束。 在创建外键的表中,...
    99+
    2024-03-06
    数据库
  • 数据库中BDE Administrator为什么不可用
    BDE Administrator不可用的可能原因有以下几点: 未正确安装BDE组件:BDE Administrator是Bor...
    99+
    2023-10-27
    数据库
  • navicat设置外键为什么保存不了
    navicat 中设置外键保存不成功的原因可能是:表结构不匹配(外键列和参照列类型/长度不一致)循环引用(外键形成循环引用,如 a 表外键引用 b 表,而 b 表外键又引用 a 表)约束...
    99+
    2024-04-24
    navicat
  • 数据库navicat怎么定义外键
    在 navicat 中定义外键可确保数据完整性,操作步骤如下:编辑列,勾选 "外键"。选择父表和父列。设置更新和删除规则,如级联更新、禁止等。保存更改。 Navicat 中定义外键的方...
    99+
    2024-04-23
    navicat
  • sql数据库怎么设置外键
    sql 中外键是一种数据库约束,确保子表记录与主表相关记录关联。要设置外键,需要:1. 确定主表和子表;2. 选择相应列作为父列和子列;3. 使用 alter table 语句创建外键约...
    99+
    2024-05-30
  • mysql数据库外键怎么设置
    外键在 mysql 中用于在表之间建立关系约束,确保引用数据的完整性。步骤包括:1. 创建外键,指定外键列和所引用的父表和父键;2. 指定约束类型(限制、级联、置空)。设置外键可以维护数...
    99+
    2024-08-05
    mysql
  • sql数据库设置外键的步骤是什么
    在SQL数据库中,可以通过以下步骤来设置外键:1. 创建主表和从表。主表包含主键,从表包含外键。主表中的主键用于与从表中的外键进行关...
    99+
    2023-10-12
    sql数据库
  • 理解数据库中主键、外键以及索引是什么
    这篇文章给大家分享的是有关理解数据库中主键、外键以及索引是什么的内容。小编觉得挺实用的,因此分享给大家做个参考。一起跟随小编过来看看吧。数据库中的主键指的是能够唯一标识一条记录的属性或属性组,外键指的是另一...
    99+
    2024-04-02
  • 云服务器包括数据库吗为什么不包括外网
    云服务器不包括数据库,但是它提供了数据库服务,可以通过公共云平台访问并使用数据库。在使用云服务器之前,您必须安装相应的云服务提供商(即云数据库)来确保数据的安全性和完整性。 此外,您可以通过在公共云平台上添加一个数据库来存储您的数据,以便...
    99+
    2023-10-26
    不包括 服务器 数据库
  • 建Mysql数据库时为什么不适用utf8
    本篇文章给大家主要讲的是关于建Mysql数据库时为什么不适用utf8的内容,感兴趣的话就一起来看看这篇文章吧,相信看完建Mysql数据库时为什么不适用utf8对大家多少有点参考价值吧。 当然,现在...
    99+
    2024-04-02
  • navicat为什么连接不上数据库
    navicat无法连接数据库的常见原因有:数据库服务器未运行防火墙或网络问题用户名或密码错误数据库连接参数不正确navicat版本问题数据库服务器繁忙驱动程序问题数据库服务器配置错误 ...
    99+
    2024-04-06
    navicat 网络问题
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作