[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的,反范式优化一定要适度,并且是在原本满足但范式的基础上做调整的。
在真正理解范式之前,不要想着反范式。否则很容易陷入“迷信”的怪圈,自己并不知道为什么要那么做,只是人云亦云。如果应用场景发生了变化,那么自己迷信的内容也许就不适用了。
所以原则上请先按照范式来设计,等有一定的经验以后,自然能根据项目情况预估哪些地方可能会成为性能瓶颈,这样也就可以凭经验来使用反范式设计避开这些瓶颈。