Java进阶面霸|为什么数据库不应该使用外键( 三 )


Java进阶面霸|为什么数据库不应该使用外键
文章图片
图4-复杂的级联操作
虽然级联删除的出发点也是保证数据的完整性 , 但是在设计关系表之间的不同关系时 , 我们也需要注意级联删除引起的数据大规模删除的问题 。 如上图所示 , 当客户端想要在数据库中删除authos表中的数据时 , 如果我们同时在authors和posts中指定了级联删除的行为 , 那么数据库会同时删除所有关联的posts记录以及与posts表关联的comments数据 。
这种涉及多级的级联删除行为在数据量较小的数据库中不会导致问题 , 但是在数据量较大的数据库中删除关键数据可能会引起雪崩 , 一条记录的删除可能会被放大到几十倍甚至上百倍 , 这些对磁盘的随机读写会带来巨大的开销 , 是我们想要尽可能避免的情况 。 如果我们能够较好地设计各个表之间的关系并且慎用CASCADE行为 , 这对于保证数据库中数据的合法性有着很重要的意义 , 使用该特性可以避免数据库中出现过期的、不合法的数据 , 但是在使用时也要合理预估可能造成的最坏情况 。
手动实现数据库的级联删除操作是可行的 , 如果我们在一个事务中按照顺序删除所有的数据 , 确实可以保证数据的一致性 , 但是这与外键的级联删除功能没有太大的区别 , 反而会有更差的表现 。 如果我们能够接受在一个时间窗口内的数据不一致 , 就可以将一个大号的删除任务拆成多个子任务分批执行 , 降低对数据库影响的峰值 。
DELETEFROMpostsWHEREauthor_id=1LIMIT100;DELETEFROMpostsWHEREauthor_id=1LIMIT100;...DELETEFROMauthorsWHEREid=1;与数据库外键的CASCADE相比 , 这种方式会带来更大的额外开销 , 只是我们能降低对数据库性能的瞬时影响 。
总结外键提供的几种在更新和删除时的不同行为都可以帮助我们保证数据库中数据的一致性和引用合法性 , 但是外键的使用也需要数据库承担额外的开销 , 在大多数服务都可以水平扩容的今天 , 高并发场景中使用外键确实会影响服务的吞吐量上限 。 在数据库之外手动实现外键的功能是可能的 , 但是却会带来很多维护上的成本或者需要我们在数据一致性上做出一些妥协 。 我们可以从可用性、一致性几个方面分析使用外键、模拟外键以及不使用外键的差异:
不使用外键牺牲了数据库中数据的一致性 , 但是却能够减少数据库的负载;模拟外键将一部分工作移到了数据库之外 , 我们可能需要放弃一部分一致性以获得更高的可用性 , 但是为了这部分可用性 , 我们会付出更多的研发与维护成本 , 也增加了与数据库之间的网络通信次数;使用外键保证了数据库中数据的一致性 , 也将全部的计算任务全部交给了数据库;在大多数不需要高并发或者对一致性有较强要求的系统中 , 我们可以直接使用数据库提供的外键帮助我们对数据进行校验 , 但是在对一致性要求不高的、复杂的场景或者大规模的团队中 , 不使用外键也确实可以为数据库减负 , 而大团队也有更多的时间和精力去设计其他的方案 , 例如:分布式的关系型数据库 。
当我们考虑应不应该在数据库中使用外键时 , 需要关注的核心我们的数据库承担这部分计算任务后会不会影响系统的可用性 , 在使用时也不应该一刀切的决定用或者不用外键 , 应该根据具体的场景做决策 , 我们在这里介绍了两个使用外键时可能遇到的问题:
RESTRICT外键会在更新和删除关系表中的数据时对外键约束的合法性进行检查 , 保证外键不会引用到不存在的记录;CASCADE外键会在更新和删除关系表中的数据时触发对关联记录的更新和删除 , 在数据量较大的数据库中可能会有数量级的放大效果;我们在很多时候其实并不能选择是否使用外键 , 大多数公司的DBA都会对数据库系统的使用有比较明确的规定 , 但是我们要清楚做出使用外键和不使用外键这一抉择的原因 。 到最后 , 我们还是来看一些比较开放的相关问题 , 有兴趣的读者可以仔细思考一下下面的问题:


推荐阅读