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

导语

有人可能在面试的时候被问到过,你觉得为什么不建议数据库通过构建外键做约束?你可以回答说,在《阿里Java开发手册》中是这样说的:不得使用外键与级联,一切外键概念必须在应用层解决。而且这个规约是强制的。但是我想这样的答案,可能并不能让面试官满意。当然,可能也有人说:在每次对表数据进行操作时,需要考虑外键的因素,太过于繁琐,但是这依然不是问题的重点。

那么该如何回答这样的一个问题呢?下面我们就来进行简单的说明。

外键的作用(原理)与优点

其实外键也不是一无是处,外键主要是用于完整性的约束的检查。这句怎么解释呢?

  • 首先已经通过外键把数据间的关系构建起来,减少了代码的构建工作
  • 严格保证数据的完整性

不过这些理由并不是推荐使用外键的理由,可能有人说,那你说说建外键的缺点呗?这里先不说它的缺点,我们来以一个简单的例子来进行体现。

在Mysql的InnoDB存储引擎中,在一个外键列中,如果你没有显示的给此列加索引的话,InnoDB存储引擎会自己给这一列加上一个索引,数据库这么处理的原因,是因为这样可以避免表锁。如果InnoDB存储引擎不自动添加索引,在进行数据操作时可能会出现死锁,Oracle中就存在这样的问题(不知道新版本的Oracle是否改善了这个问题),这其实是一个并发问题。

简单的说了下,在InnoDB中建外键是,InnoDB自己进行的一些优化。那么,现在来说下在有外键的情况下会出现什么样的状况。

外键测试
时间 会话A 会话B
1 begin  
2 delete from student where id = 7;  
3   begin
4  

insert into grade selete * from student id in (6,7,8);

#由于其中有外键,执行时会被阻塞

(waiting)

在这个例子中,可以看到当在两个会话都没有进行事务的commit或rollback时,会话B的操作会被阻塞,因为id等于7的student表中已经加了一个X锁(排它锁),此时会话B中又需要对student表中id等于7的这条数据行加了一个S锁(共享锁),这样Insert操作便会被阻塞。

此时加入要读student表的数据的话,同时使用的还是一致性非锁定读,但是这样会话B会读到student等于7的数据,便会认为这条数据是可操作性的,但当会话A的事务提交后,这条数据便不存在了,这样就会导致数据不一致的情况。

从上面的例子中,我们可以看出来大概有这样几个问题:性能问题、并发问题(数据一致性),当然你除了这些问题,还有数据库的扩展问题。对于这几个问题,我们来进行一些简单的说明。

性能问题

以上面的例子为例,当程序每次往grade表中插入数据的时候,就必须去到student表中去校验这所要插入的数据是否存在,但是当把这个工作交给代码层去操作时,这些校验就可以有我们自己去做了,况且并不是所有的情况都需要去校验有‘外键’关联的表数据。

并发问题

在上面的例子中,我们说到了,当我们在开启会话A的时候,就已经在会话A中加了X锁,当在会话B中操作字表的数据时,又会在student表中id等于7的这条数据上加了一个S锁,如果会话A一直不提交会出现什么样的情况?这也就是一种资源竞争,如果会话A的事务提交了,又会导致数据的不一致。因此这也影响数据的插入性能。

扩展问题

在有外键的数据库中是不适做合分布式、高并发集群的,首先在水平分库的时候,外键是无法生效的。所以把这样的关系约束交给应用层去维护的话,会给后面的工作省去一些麻烦的。还有我们上面也说了Mysql与Oracle在外键上的差别,加入你把带有外键的MySQL数据库迁移到了Oracle中,那么会有什么情况呢?

 

总结

笔者写这篇文章,是因为最近在项目中发现有小伙伴在大量的使用外键,第一在维护表的时候,工作量很大,第二在性能上明显有损耗。因此想着写一篇文章,毕竟笔者使用外键还是当年刚学编程的时候。

发布了43 篇原创文章 · 获赞 9 · 访问量 6196

猜你喜欢

转载自blog.csdn.net/zfy163520/article/details/104009835