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

news/2025/1/22 8:49:45/

一、引言

当我们需要持久化地存储数据时,关系型数据库通常是首选。它不仅种类丰富、稳定,而且得到了广泛社区的支持。本文将探讨关系型数据库中的一个重要概念——外键(Foreign Key)。


二、外键的作用

在关系型数据库中,外键也被称为关系键。它是一组数据列,用于在不同关系表之间建立连接。这组数据列在当前表中是外键,而在另一个表中必须是候选键(Candidate Key)。通过候选键,我们可以在当前表中找到唯一的元素。通常,我们会使用关系表中的主键作为其他表中的外键,以满足关系型数据库外键的约束。

图 1 - 关系型数据库外键

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

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

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

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

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

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

  • 使用 RESTRICT 会在更新或删除记录时对外键对应的记录是否存在进行一致性检查。

  • 使用 CASCADE 会在更新或删除记录时触发级联更新或删除操作。

需要注意的是,MySQL 中的 NO ACTIONRESTRICT 具有相同的语义。

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


三、一致性检查

当我们使用默认的外键类型 RESTRICT 时,在创建、修改或删除记录时都会检查引用的合法性。假设我们的数据库中包含 posts(id, author_id, content)authors(id, name) 两张表,在执行以下操作时都会触发数据库外键的检查:

  • posts 表中插入数据时,检查 author_id 是否在 authors 表中存在。

  • 修改 posts 表中的数据时,检查 author_id 是否在 authors 表中存在。

  • 删除 authors 表中的数据时,检查 posts 中是否存在引用当前记录的外键

作为专门用于管理数据的系统,数据库与应用服务相比能够更好地保证完整性。上述操作是引入外键带来的额外工作,但也是数据库保证数据完整性的必要代价。这些分析是理论上的定性分析,我们可以通过简单的定量分析来研究引入外键对性能的影响。

在这里,我们在数据库中同时创建 authorspostsforeign_key_posts 三种表。postsforeign_key_posts 两个表中的列完全相同,只是 foreign_key_posts 表为 author_id 字段增加了 RESTRICT 类型的外键约束。

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

我们先在 authors 表中插入一条记录,随后分别在 postsforeign_key_posts 中插入多条新数据列引用该条记录。posts 表不会检查外键的合法性,而 foreign_key_posts 表会进行额外的检查。通过多次基准测试,我们可以得到如下结果:

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

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

  • 向表中插入数据或修改表中的数据时,执行额外的 SELECT 语句确保引用的数据在数据库中存在。

  • 删除数据之前,执行额外的 SELECT 语句检查是否存在当前记录的引用。

需要注意的是,为了保证一致性,我们需要在事务中执行上述查询和修改语句,这样才能完整模拟外键的功能。当我们向 posts 表中插入或修改数据时,处理相对简单。我们只需要执行有限的 SELECT 语句并按照以下模式执行对应的操作即可:

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


四、级联操作

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

  • 当客户端更新 authors 表中记录的主键时,数据库会同时更新 posts 表中所有引用该记录的外键

  • 当客户端删除 authors 表中的记录时,数据库会删除所有与 authors 表关联的记录。

数据库可以保证各个关系表之间引用的一致性和合法性,不会出现引用到不存在记录的情况。与 RESTRICT 行为一样,所有外键的更新和删除行为都可以通过执行额外的检查和操作保证数据的一致性。

图 4 - 复杂的级联操作

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

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

五、外键数据库性能的影响

1. 插入和更新操作延迟

外键约束会增加数据库在插入和更新操作时的负担。每次向包含外键的表中插入或更新数据时,数据库都需要检查外键约束,确保引用的记录在父表中存在。这会增加额外的查询开销,尤其是在外键引用的表较大时,这种检查可能会显著延迟操作。

2. 删除操作复杂性

删除被外键引用的记录时,数据库需要检查是否存在依赖记录,可能导致级联删除或拒绝删除,增加了操作的复杂性。

3. 锁竞争

外键约束可能导致更多的锁竞争,尤其是在高并发环境下,多个事务同时修改相关表时。这可能会导致性能瓶颈。

4. 索引开销

为了提高外键约束的检查效率,通常需要对被引用的字段创建索引,这会增加存储开销和维护成本。

5. 查询性能

虽然外键本身不会直接影响查询性能,但它们可能会通过触发器和复杂的查询逻辑间接影响。在某些情况下,外键可以优化连接操作(JOIN),因为数据库可以根据外键约束来选择最佳的连接方法,从而提高连接操作的性能。

6. 性能影响案例分析

以电商平台订单系统为例,订单表中的用户ID(user_id)作为外键指向用户表的主键。在高并发场景下,大量订单的插入和更新操作会导致频繁的外键检查,从而影响性能。

7. 优化策略

为了最大限度地减少外键对性能的影响,可以采取以下策略:

  • 合理设计表结构:适度反范式化可以减少外键的使用;表分区技术可以减少单表数据量,降低外键检查的开销。

  • 索引优化:为外键列和引用列创建适当的索引,减少回表操作。

  • 查询优化:使用索引字段和外键字段进行查询,避免全表扫描。

  • 事务和锁机制优化:尽量使用行锁而非表锁,减少锁竞争。

  • 数据库配置调整:适当增加缓冲池大小,提高数据缓存效率。

  • 应用层逻辑:在某些场景下,可以在应用层进行数据一致性检查,而非依赖数据库外键约束。

六、总结

外键数据库设计中是一个双刃剑。它可以在数据库层面确保数据的完整性和一致性,但也会增加数据库的负担,影响性能和开发灵活性。在实际应用中,需要根据具体的业务需求和性能要求来决定是否使用外键。对于数据完整性要求高的核心业务表,使用外键可以确保数据的正确性;而对于性能要求高的非核心业务表,可以在应用层通过逻辑控制来确保数据一致性,从而提高系统的整体性能。


http://www.ppmy.cn/news/1565180.html

相关文章

02内存结构篇(D4_JVM内存分配机制)

目录 一、对象的创建 1. 类加载检查 2. 分配内存 3. 初始化零值 4. 设置对象头 32位对象头 64位对象头 5. 执行方法 二、对象大小与指针压缩 三、对象内存分配 1. 对象内存分配流程图 2. 对象栈上分配 3.3 对象在Eden区分配 3.4 大对象直接进入老年代 3.5 长期存…

md中的特殊占位文件路径的替换

结构 :::readFile /xx/xx.vue :::将/xx/xx.vue进行替换出来 const extractContentAll (str, prefix, suffix) > {const pattern new RegExp(${prefix}(.*?)${suffix}, "sg");const match str.match(pattern);return match ? match : null;};const extractC…

QD Laser携“Lantana”激光器参展SPIE光子学西部展2025,聚焦紧凑型设计

据悉,QD Laser公司将在2025年SPIE光子学西部展览会上展出其最新产品——世界最小一体化紧凑型可见光激光器“Lantana”。该展会将于1月28日至30日在旧金山的Moscone中心举行。 在展会期间,QD Laser公司将现场展示这款超小型、轻便设备—— “Lantana”。…

mysql精简单机版,免登录,可复制,不启动服务与本机mysql无冲突

突然有了个需要在本地使用的mysql需求,要求不用安装,随拷随用,不影响其他mysql服务,占用空间小.基于这种需求做了个精简版的mysql 首先下载mysql的zip安装包 > windows 64位 > https://repo.huaweicloud.com/mysql/Downloads/MySQL-5.7/mysql-5.7.36-winx64…

记录一次 centos 启动失败

文章目录 现场1分析1现场2分析2搜索实际解决过程 现场1 一次断电,导致 之前能正常启动的centos 7.7 起不来了有部分log , 关键信息如下 [1.332724] XFS(sda3): Internal error xfs ... at line xxx of fs/xfs/xfs_trans.c [1.332724] XFS(sda3): Corruption of in-memory data…

无人机飞手考证难度增加,实操、地面站教学技术详解

随着无人机技术的快速发展和广泛应用,无人机飞手考证的难度确实在不断增加。这主要体现在对飞手的实操技能和地面站操作技术的要求上。以下是对无人机飞手考证中实操和地面站教学技术的详细解析: 一、实操教学技术详解 1. 无人机基础知识学习&#xff1…

C++17 新的求值顺序规则:小白友好版指南

嘿,C 小白们!今天,我们要聊一个听起来有点枯燥,但实际上超重要的话题——C17 中的求值顺序规则。别急,我会用最通俗易懂的方式,带你一步步搞懂这个知识点,让你在编程路上少走弯路,写…

学习华为熵减模型:激发组织活力(系列之三)

目录 为什么学习华为? 学习华为什么? 一、势:顺势而为,在风口上猪都会飞起来。 二、道:就是认识和利用规律层面,文化和制度创新就是企业经营之道。 三、法:就是一套价值管理的变革方法论。…