MySQL 高级特性(五):为什么不推荐你使用外键?
在早期的数据库表结构设计中,往往会把一张表引用另外一张表的字段(通常是 id)作为外键,借助 MySQL 自动维护外键,确实能够省掉很多开发工作,但是外键实际的代价不低,很多数据表设计规范已经明确禁止使用外键。本篇将介绍外键存在的缺陷。
目前,在 MySQL 内置存储引擎中,只剩下 InnoDB 还在支持外键,因此如果要使用外键可选择的存储引擎不多。
外键并不是没有代价的。事实上,外键通常会需要服务器地在更改数据的时候检查另一张表。尽管 InnoDB 使用了索引提高这个操作速度,但并没有让数据检查的影响消失。甚至还可能产生一个很大的没有筛选性的索引。例如,假设我们在大的数据表中有个 status 字段,然后约束 status 只能使用有效的值,然而这个值只有三个可选项。这会导致额外的索引大大增加数据表的存储空间大小,即便是这个列自身存储空间很少。尤其是当主键存储空间比较大时,而这个索引除了用于外键检查,通常没什么用。
当然,外键在某些情况也能够改善性能。如果我们需要保证两个关联表的数据必须保持一致的时候,使用 MySQL 服务器进行检测会比程序来检测效率更高。外键在那种级联删除或更新的场合也很有用。但是这类操作是逐行进行的,因此会比批量删除或批量操作更慢。
外键导致查询需要依赖其他数据表,这意味着 InnoDB 需要在父级表(或相关表)中检验相应的值。这也会锁定父级表的数据行,以保证在事务完成前该行不会被删除。这会导致意外的锁等待,甚至是死锁,这类问题很难被定位。
既然外键有如此多的缺陷,因此可以使用别的方式替代外键。一种方式是使用触发器替代外键。外键的目的是在类似级联更新的时候触发任务自动关联。对于那些仅仅是约束值的外键,例如之前谈到的 status 例子,可以通过一个明确的可用值列表的触发器重写(也可以使用 ENUM 枚举类型)。
另一种方式是使用程序来完成类似外键的约束。外键可能会显著增加负荷,这个虽然没法准确度量,但是实际确实发生过很多由于外键约束检查引起的性能问题,而且移除外键后性能能够得到显著提升。
结语:从众多互联网企业的数据库设计规范来看,外键是一再被重申要被禁用的。不单单是性能问题,而且互联网的业务多变,如果是表结构发生变动,很可能会导致外键关联的表出现意想不到的问题。因此,在非必要的情况下不要用外键,除非你只是为了验证外键的功能。