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

CDDT CST CDDT CSTTT This is the second part of the SA8653 automotive CDDT configuration guide. It describes the matching rules and modification methods between CDDT and device‑tree DDTS. It explains CDT’s underlying logic and configuration in TZ, QNX and Android. It details C 阅读详情

为什么这么设计(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 - 无状态服务与数据库

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

根据更新和删除时的行为不同,我们可以将外键分成 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 = <post.author_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 等传统数据库有哪些区别?
Amesim应用篇-电芯等效电路模型标定 为了使计算模型更加准确,在有电芯实验测试数据的情况下,依据现有的实验数据对Amesim中的电池等效电路模型进行标定。标定的目的是为了获得更加符合项目实际情况的电芯等效电路模型,标定完的电芯可以用于搭建PACK模型,也可以用于其他虚拟实验。本文以充电标定为例,进行介绍。放电标定与充电一致。 阅读详情

相关推荐

rk3326 MIPI休眠唤醒黑屏有背光问题

** 1.问题现象: ** **【开机起来】**开机起来可以正常显示 **【按power按键休眠】**第二次按下Power按键进入休眠,隔10s,再按Power按键唤醒。 **【出现结果】**屏幕黑屏有背光,并提示drm time out错 **【lcd信息】**lcd为480*800 2lane的mipi **【复现概率】**第二次按下休眠唤醒必然复现 【具体错误log如下】 //第一次休眠的log// [ 54.858511] dw-mipi-dsi ff450000.dsi: final DSI-

Ian22l的博客 3944

sqlserver设置外键遇到的问题(设置时对主键表和外键表的理解有误导致)

sqlserver设置外键的问题遇到的问题 使用sql设置category表的主键为clothes表的外键。 在设置的时候,因为搞不清主键表和外键表的意思,折腾了半小时。 要设置的外键,主表里必须拥有和从表的一样的字段列,字段类型等必须相等,否则会提示xxxx不匹配错误。 必须搞清楚主键表和外键表的意思!!! 右键双击要设置外键的字段,出现下列框,开始设置外键。 ...

Everydaxueli的博客 2600

Scipy优化踩坑实录:trust-constr和SLSQP约束定义到底差在哪?

本文深入解析了Scipy优化模块中trust-constr和SLSQP两种方法在约束定义上的差异。通过对比面向对象的trust-constr和字典式定义的SLSQP,揭示了它们各自的优势与适用场景,帮助开发者在有约束优化问题中选择合适的算法,提升计算效率与准确性。

weixin_42573113的博客 271

为什么数据库不应该使用外键,微信抢红包实战案例

蚂蚁面试比较重视基础,所以Java那些基本功一定要扎实。蚂蚁的工作环境还是挺赞的,因为我面的是稳定性保障部门,还有许多单独的小组,什么三年1班,很有青春的感觉。面试官基本水平都比较高,基本都P7以上,除了基础还问了不少架构设计方面的问题,收获还是挺大的。经历这次面试我还通过一些渠道发现了需要大厂真实面试主要有:蚂蚁金服、拼多多、阿里云、百度、唯品会、携程、丰巢科技、乐信、软通动力、OPPO、银盛支付、中国平安等初,中级,高级Java面试题集合,附带超详细答案,希望能帮助到大家。

2401_84003554的博客 953

java爬虫(爬取银行理财数据)

import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; imp

ROCK 2490

Ubuntu中snap简单介绍

通过Snap可以安装众多的软件包。需要注意的是,snap是一种全新的软件包管理方式,它类似一个容器拥有一个应用程序所有的文件和库,各个应用程序之间完全独立。所以使用snap包的好处就是它解决了应用程序之间的依赖问题,使应用程序之间更容易管理。但是由此带来的问题就是它占用更多的磁盘空间 介绍一些常用命令,有点类似docker 1、查看已经通过snap安装的snap包 snap list可以查看 2、可以搜索想要的snap包,例如tomcat snap search tomcat

icanflying的博客 4万+

数据库 创建外键失败 原因枚举

数据库外键创建 fk (foreign key) 创建失败原因 同名外键存在 外键两端的字段类型【不完全一致】 外键两端不是【主键】或没有【索引】 外键两端字段的内容与外键条件冲退(外键【更新】【删除】规则) SQL语法, 或数据库不支持

老专家的博客 1万+

Ubuntu20.04安装Ceres库

ceres真难安装。

little_white138的博客 8379

为什么不要使用外键以及批量导入1000万条数据和数据库优化方法

在数据库设计和管理中,外键是一种强大的工具,可以用于确保数据的完整性和一致性。然而,在某些情况下,使用外键可能并非总是明智的选择。

hymua的博客 762

【数据集】30 m地表温度LST数据集

【数据集】30 m地表温度LST数据集

WW、forever的博客 1128

基于simulink的智能驾驶领域--智能车道保持辅助系统仿真

LKAS通过摄像头等传感器检测车辆相对于车道线的位置,并自动调整转向,以帮助驾驶员保持在车道内行驶。将摄像头传感器模块的输出连接到车道线检测模块的输入端口,再将车道线检测模块的输出连接到转向控制器模块的输入端口,最后将转向控制器模块的输出连接到车辆动力学模型的转向输入端口,形成闭环控制系统。上升时间是指系统从初始转向角上升到目标转向角所需的时间,调节时间是指系统进入并保持在目标转向角一定范围内的所需时间。该控制器将根据摄像头传感器的数据调整车辆的转向角,以保持车辆在车道内行驶。

xiaoheshang_123的博客 162

为什么不建议使用数据库外键

为什么不建议使用数据库外键

m0_67907445的博客 1296

2021.07.05【R语言】丨预测lncRNA并绘制venn图

目录摘要环境与方法依赖工具预测工具绘制工具使用命令预测lncRNA统计noncoding_ID绘制Venn图结果展示总结 摘要 接到一个单样品测lncRNA数据的项目,正好拿来练练手,梳理一下lncRNA流程。这里记录预测lncRNA并生成venn图的过程 环境与方法 R version 3.6.3 (2020-02-29) 依赖工具 预测工具 CPC2 CNCI Pfam CPAT 绘制工具 R:VennDiagram 使用命令 预测lncRNA CPC2 #更换env conda activate py

2035

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

作者:Draveness来源:真没什么逻辑为什么这么设计(Why’s THE Design)是一系列关于计算机领域中程序设计决策的文章,我们在这个系列的每一篇文章中都会提出一个具体的问题并从不同的角度讨论这种设计的优缺点、对具体实现造成的影响。如果你有想要了解的问题,可以在文章下面留言。当我们想要持久化地存储数据时,使用关系型数据库往往都是最稳妥的选择,这不仅因为今天的关系型数据库种类非常丰富并且...

weixin_39628186的博客 142

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

数据库不推荐使用外键的9个理由!_为什么不建议使用外键

为了能够容纳旧的脏数据,架构师可以选择a)清理和转换遗留数据(昂贵的练习),或者b)放弃在数据库级别上强制执行参照完整性。数据仓库和分析数据库尤其如此,这些数据仓库和分析数据库不以交易方式(一次一行)处理数据,而是批量处理数据。数据库中缺少外键的另一个不太明显的负面影响是,不了解该模式的人很难找到正确的表并找出表关系。我的经验告诉我,很多数据库(大多数我曾经使用的)不包含外键时并不总是一件坏事。我个人(和许多其他经验丰富的数据库专家)建议在任何可能的地方使用它们(不会导致更多的问题)。为什么这是一个问题?

2301_82243558的博客 602

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

数据库外键为什么不建议使用

数据库外键(Foreign Key)是关系型数据库中用于维护数据完整性和表之间关联关系的一种约束机制。它通过在从表(子表)中定义一个或多个字段,引用主表(父表)的主键或唯一键,从而建立表之间的关联关系。

幽静净土的技术分享 1242

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

【数据库外键】为什么不推荐使用物理外键?

目录 一、外键使用场景 二、为什么不使用物理外键 1.参考阿里手册 2.物理外键的优点 3.物理外键的缺点 三、总结 一、外键使用场景 今天测试员测出来许多bug,是由于未解决外键关系照常的我问题. A表的id是B表的外键,业务上B表通过外键关联A表获取A表的name A表删除后,B表无法获取到name 通过加逻辑外键来解决了这个问题 二、为什么不使用物理外键 1.参考阿里手册 2.物理外键的优点 保证数据的完整性和一致性 级联操作方便 将数据完整性判断托付给...

corleone_4ever的博客 1890

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

本文转自真没什么逻辑作者 |Draveness为什么这么设计(Why’s THE Design)是一系列关于计算机领域中程序设计决策的文章,我们在这个系列的每一篇文章中都会提出一个具体的问题并从不同的角度讨论这种设计的优缺点、对具体实现造成的影响。如果你有想要了解的问题,可以在文章下面留言。当我们想要持久化地存储数据时,使用关系型数据库往往都是最稳妥的选择,这不仅因为今天的关系型数...

weixin_39860108的博客 202

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

当我们想要持久化地存储数据时,使用关系型数据库往往都是最稳妥的选择,这不仅因为今天的关系型数据库种类非常丰富并且稳定,还因为不同社区对关系型数据库的支持都非常完备。我们在前面的文章中曾经分析过 为什么 MySQL 的自增主键不单调也不连续,这篇文章我们来分析关系型数据库中另一个重要的概念 — 外键(Foreign Key)。 在关系型数据库中,外键也被称为关系键,它是关系型数据库中提供关系表之间连接的多个列[^1],这一组数据列是当前关系表中的外键,也必须是另一个关系表中的候选键(Candidate Ke

李姓门徒 1129

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

在关系型数据库中,外键也被称为关系键,它是关系型数据库中提供关系表之间连接的多个列,这一组数据列是当前关系表中的外键,也必须是另一个关系表中的候选键(Candidate Key),我们可以通过候选键在当前表中找到唯一的元素。在通常情况下,我们都会使用关系表中的主键作为其他表中的外键,这样才可以满足关系型数据库对外键的约束。 外键不仅仅是数据库表中的一个整数,它还提供了额外的一致性保证。因为数据库往往是整个系统的真理之源(Source of Truth),所以保证数据的一致性和正确性非常重要,关系型数据库虽

程序员麦冬的博客(公众号同名) 1262
上一篇: 程序员:我就在简历上写了熟悉kafka,结果面试官就逮着问,硬问
下一篇: 三面美团Java岗,HR现场直接发offer,他是横着走出来的。
Javaesandyou
博客等级 码龄5年 742粉丝 563原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值