【开发思考】约束是在数据库中定义还是在业务层中实现?

简介: 【开发思考】约束是在数据库中定义还是在业务层中实现?

[1] 现状


  首先,数据库的约束有:

  · 主键 primary key

  · 外键 foreign key

  · 非空 not null

  · 默认 default

  · 唯一 unique

  现在我们在谈论约束是在数据库中定义还是在业务层中实现的时候,大多说的都是外键。因为其他的约束我们还是经常在数据库里使用的。

  外键有三种删除/更新规则:


 1、no action 、restrict (这是默认规则):保证当父表主键与子表中的外键相关联时,不允许修改父表中的内容,当然,父表中的某一个主键元素没有与子表相关联时,可以关系与删除该主键元素。


 2、cascade:当父表中主键元素进行修改或者删除时,先判断是否有对应的外键,如果有,那么也修改和删除外键在子表中的数据,是为cascade


 3、set null: 当父表中的主键元素被修改或者删除时,先判断是否有对应的外键,如果有,那么将外键在子表中的数据置为空。

  目前业界的现状,我们可以从《阿里巴巴Java开源手册》中看到:

  【强制】不得使用外键与级联,一切外键概念必须在应用层解决。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。

 (13)禁止使用外键,如果有外键完整性约束,需要应用程序控制

 外键会导致表与表之间耦合,update与delete操作都会涉及相关联的表,十分影响sql 的性能,甚至会造成死锁。高并发情况下容易造成数据库性能,大数据高并发业务场景数据库使用以性能优先。当然,如果是一些内部数据量不大的系统,不涉及分布式环境,使用也是可以的。

[2] 为什么在业务层中实现外键?


  从业务上来看:

 根源上来说,关系数据库支持关系主要还是因为性能需求。在没有可以简单承载足够多数据和计算量的客户端或者服务器的时候,业务逻辑直接绑定在数据上是简单有效可靠的法子。所以简单说,如果你的目的是为了提高性能,那当然仅就关系约束而言外键约束无论如何是不慢于外部逻辑的。


 但随着业务变得复杂,数据量暴增,事实并不会这么简单。


 从DevOps的角度看,业务逻辑与数据解耦不仅是推荐的,更是应该的。因为一旦耦合起来就意味着你终究会遇到一种情况,就是你的业务逻辑和数据必须同时修改。对常见的升级、灰度等操作都是非常大的麻烦。

  从数据库性能扩展上来看:

 一种最常见的问题莫过于,数据库性能扩展常用水平拆分来缩减单表单库的大小,这已经形成了很多成熟的方法论Shard (database architecture),最常见的莫过于基于shard key来做分库分表,很多第三方魔改mysql都已经实现了自动化的基于shard key 来分库分表,自动扩容,还能在此基础上实现主备切换等等很多自动化运维操作。 shared disk 来搞的垂直拆分实际中因为需要硬件支持且对性能提升也不大比较少有实现。


 然而想要基于shard key来做操作,很多形式的跨表sql 或者跨表约束肯定是会受限制的。因为如果本来你眼中属于同一个库的多表操作和约束,底层实际却是跨库去操作的,甚至是跨物理机去操作的,那肯定性能就会经常莫名的下跌。


  所以一般支持水平扩展性能的设计,哪怕是手工进行水平扩展的,一般都不会允许太多的多表关联操作和约束,甚至表内join都需要尽量避免(免得给proxy产生集中冲击)。

  一些个人理解:

  互联网开发就是糙猛快,像Redis这种几乎不提供任何ACID保证的存储都能用得飞起,还要什么外键约束?

  外键不比主键,很多时候没有外键约束并不会造成严重错误,而业务逻辑自己去保证外键约束也不难。

  生产环境很多时候MySQL单库单表是扛不住的,而分库分表的情况下外键很难玩得动,更不论分布式系统为了提高扩展性和吞吐量,向来对一致性要求比较宽松。

[3] 再思考一层:那数据的ACID怎么办?不满足范式怎么办?


  业界不使用外键与范式发生了冲突的这种现象,我们称之为反范式

 反范式其实是基于范式所调整的,没有冗余的数据库未必是最好的数据库,完全按照范式做表的设计可能会降低查询效率(涉及多表查询,多表连接JOIN,临时表创建GROUP BY),有时候为了提高运行效率,就必须降低范式的标准,适量保留冗余数据。


 在概念数据模型设计时遵守范式,降低范式标准的工作放在物理数据模型时考虑。


 适当的合并一些表的字段(减少表的数量),产生一些字段冗余,降低了查询时的关联,有时候可以提高查询效率。


 因为在数据库操作中,DQL的比例是要远大于DML的,反范式优化一定要适度,并且是在原本满足但范式的基础上做调整的。


 在真正理解范式之前,不要想着反范式。否则很容易陷入“迷信”的怪圈,自己并不知道为什么要那么做,只是人云亦云。如果应用场景发生了变化,那么自己迷信的内容也许就不适用了。


 所以原则上请先按照范式来设计,等有一定的经验以后,自然能根据项目情况预估哪些地方可能会成为性能瓶颈,这样也就可以凭经验来使用反范式设计避开这些瓶颈。

相关文章
|
6天前
|
SQL 关系型数据库 API
HarmonyOs开发:关系型数据库封装之增删改查
每个方法都预留了多种调用方式,比如使用callback异步回调或者使用Promise异步回调,亦或者同步执行,大家在使用的过程中,可以根据自身业务需要进行选择性调用,也分别暴露了成功和失败的方法,可以针对性的判断在执行的过程中是否执行成功。
61 13
|
5天前
|
存储 前端开发 关系型数据库
鸿蒙开发:实现键值数据库存储
对于数据量比较的小的,我们直接选择轻量级的用户首选项方式即可,而对于数据量比较大的情况下,直接可以使用数据库,而对于相对来说,比较大的数据,我们就可以使用键值型数据库方式
|
11天前
|
存储 JSON 测试技术
【HarmonyOS Next开发】云开发-云数据库(二)
实现了云侧和端侧的云数据库创建、更新、修改等操作。这篇文章实现调用云函数对云数据库进行增删改查。
33 9
|
2月前
|
存储 SQL API
探索后端开发:构建高效API与数据库交互
【10月更文挑战第36天】在数字化时代,后端开发是连接用户界面和数据存储的桥梁。本文深入探讨如何设计高效的API以及如何实现API与数据库之间的无缝交互,确保数据的一致性和高性能。我们将从基础概念出发,逐步深入到实战技巧,为读者提供一个清晰的后端开发路线图。
|
2月前
|
存储 缓存 NoSQL
2款使用.NET开发的数据库系统
2款使用.NET开发的数据库系统
|
2月前
|
存储 SQL 数据库
深入浅出后端开发之数据库优化实战
【10月更文挑战第35天】在软件开发的世界里,数据库性能直接关系到应用的响应速度和用户体验。本文将带你了解如何通过合理的索引设计、查询优化以及恰当的数据存储策略来提升数据库性能。我们将一起探索这些技巧背后的原理,并通过实际案例感受优化带来的显著效果。
56 4
|
2月前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
72 2
|
2月前
|
监控 Java 数据库连接
在Java开发中,数据库连接管理是关键问题之一
在Java开发中,数据库连接管理是关键问题之一。本文介绍了连接池技术如何通过预创建和管理数据库连接,提高数据库操作的性能和稳定性,减少资源消耗,并简化连接管理。通过示例代码展示了HikariCP连接池的实际应用。
23 1
|
3月前
|
SQL JavaScript 关系型数据库
node博客小项目:接口开发、连接mysql数据库
【10月更文挑战第14天】node博客小项目:接口开发、连接mysql数据库
|
3月前
|
Rust 前端开发 关系型数据库
Tauri 开发实践 — Tauri 集成本地数据库
本文介绍了在 Tauri 框架中集成本地数据库的几种方案,包括直接绑定 SQLite、使用第三方数据库库和使用 tauri-plugin-sql-api 插件。最终选择了 tauri-plugin-sql-api,因为它集成简单、支持多种数据库类型,并且与 Tauri 框架深度整合,提升了开发效率和安全性。文章详细介绍了如何安装和使用该插件,以及如何编写核心代码实现数据库操作。
286 2