アカリの部屋

关于是否应该使用MySQL外键的思考

缘起

今天去听了公司一个关于数据库优化的分享,基本是吹水,最后当我问及“在您的经验当中用什么依据来判断是否使用外键?”的时候,讲师只是从是否需求一致性作答,我似乎没听到我想要的。后来经过反思,在这里总结一下自己对外键的看法。

问题

课本告诉我们,在数据库中,外键的作用,是给两个相互关联的表加上约束,它会阻止执行一些破坏两个行关联一致性的操作。
可见,这个工具在DB一层保证了数据的物理一致性。
问题在于,外键检查会拖慢非查询性能,如果数据表很大,且插入频繁,那么使用外键就是不合理的。
当弃用外键的时候,显然会带来并发问题,即事务B可以删除事务A写入的依赖。

消除物理依赖

如果弃用外键,就需要把保证一致性的逻辑放到代码里来做,而不是推给数据库机制。
为了达到这个目的,从根本上讲,所有数据都不应该被物理删除,而应该用标记字段假删除。
那么既然依赖从根本上不能被物理破坏,就可以使用ORM的事务(工作单元模式)来保证一致性了。

消除逻辑依赖

既然用不着外键来保证物理一致性,那么它就是一个形式上的工具了。
进一步思考,考虑外键本身的意图,它在业务上相当于面向对象里的聚合,是一种让对象化为自身一部分的手段。
然而,聚合也是依赖的一种,面向对象设计要制造恰当的依赖,干掉有问题的依赖。
如果不采取把数据存在另一个表里的方式(聚合),而是直接存在自身里(加属性),这样就从根本上消除依赖(聚合)了。

从其他角度思考

从程序上来看,多一个外键表意味着我必须多学一些ORM的语法机制,增加了成本,也增加了将来引入代码坑、框架坑的几率。
从变化上来看,互联网行业需求变化快,要求快速响应,然而在有外键约束的环境下,对着SQL反复做逻辑自测都是一件极端麻烦的事情,哪怕没有约束,多一个外键表,都麻烦。
从查询上来看,不论是MySQL大表还是上到Hive/SparkSQL级的数据,在大量数据上搞join都不是什么好事。
从长远上来看,数据量是会增长的,如果使用了外键表,那么一旦有一天外键表要分库分表的时候,必然会导致一系列的大动干戈,就会抱怨当初的设计了,这个问题是最最蛋疼的!

结论

1.通过制度干预规避问题,优于通过机制设防线,后者往往会带来更大的约束和成本。
2.彻底弃用外键,所有数据禁止删除,使用假删除,通过代码保证事务性。
3.无视范式规范,能扩列不加表,能冗余不join,能星型不雪花,向Parquet和HBase的理念看齐。

那什么时候一定要用外键呢?

吐个槽,就是譬如某些政府外包团队用.Net开发的内网办公系统,这类系统变化速度没那么快,而且大多数研发人员都是水平不怎么好的应届生纯码农,根本想不到上面说的这坨东西,只是对着需求写面条代码而已,恐怕公司都不重视技术积累,技术学得明白的人基本不去这种公司,那么天知道这帮人会写出什么样的代码,所以还是用外键这最终手段比较踏实,至少你数据错了不是DB的锅。我要做的是优化频繁插入性能,然而对这种系统来说根本就没有海量数据,性能啥的都是饭后甜点而已。