愿我们都好事发生

要使用业务唯一ID做主键吗?

2024.07.04

注意:

  1. 本篇文章需阅读者掌握MySQL索引基本原理,可提前阅读 MySQL系列 | 浅尝MySQL索引
  2. 本篇文章涉及的V2帖子,没有特殊能力者,是访问不通的
  3. 本人无特殊能力去看V2帖子,是不小心在某个地方看到,已全然忘记出处,是一位遵纪守法的好公民

看到V2一篇帖子中的提问:https://go.lion.im/sk7qf ,促使我写下了这篇文章。

这位楼主的问题看似简单,但挺值得思考的。

这个问题我曾经想当然的认为,答案就应该是“需要使用自增ID列作为主键”,直到曾经一次滴滴的面试,被问到几乎同样的问题,才进行了深度的思考。

很多时候我们在设计数据库表的时候,明明已经有了一个逻辑上的唯一ID,却依然新建了一个自增的ID作为物理主键,在业务上却毫无用处,仿佛就像人要吃饭睡觉一样自然,why?

非自增主键会产生的问题

我们先来看看,为什么我们需要一个自增的主键?

性能问题

MySQL InnoDB 引擎的主键索引是一棵B+树结构,为了维护索引数据的有序性,会对新增数据进行比较,如果大于最大值,则只需要在最后记录后面插入一个新记录,如果小于,那么会去查找应该插入的具体位置。

页分裂

由于数据页的大小默认是16KB,如果恰巧要插入数据的数据页已满,就需要在此位置申请一个新的数据页,然后迁移部分数据过去,这个过程称为页分裂。

所以,如果我们的主键是自增的,那么每次新增数据,都会直接在最大记录后插入,非常高效。

空间问题

页空洞

如果主键是非自增的,除了要在插入过程中,经过多次比较、页分裂导致数据挪动等额外的耗时操作外,还会产生页空洞,致使空间利用率下降。

辅助索引

当我们为了高效多维度查询,对一张表创建了多个辅助索引时,辅助索引的叶子节点存储的是主键值。

如果我们使用业务上的唯一ID来做主键,由于一般业务上的唯一ID为了保证唯一性,生成的ID可能较长,那么所有辅助索引的叶子节点数据都会变大。
例如,用18位身份证号做主键,刨除其他额外信息,需要18个字节,而如果使用整型自增,则只需要4字节。

综上来看,无论是从性能上,还是空间占用上,我们确实应该使用自增ID列做主键,而不应该使用业务唯一ID来做主键。

那么,所有场景都该如此吗?不,当然不是。

业务唯一ID适合作为主键的场景

什么场景下,我们可以使用业务唯一ID来做主键呢?

有限场景一

  • 业务唯一ID必须是自增趋势的
  • 只会有一个主键索引

如果同时满足以上两点,那么使用业务唯一ID作为主键是最佳的。

因为如果此时,再去建立自增ID列,那么必然要基于业务唯一ID建立辅助索引,我们知道,由于自增ID是不会在业务中被使用的,所以我们的查询,都要基于业务唯一ID来进行,这样将会在每次查询时,造成回表。

所以,使用业务唯一ID作为主键既避免了回表操作,又保证了主键的递增趋势,也不需要创建辅助索引,一举三得。

有限场景二

  • 读多写少
  • 数据量小

假设你的业务唯一ID不是自增趋势,是否可以使用它做主键呢?
答案是可以。

但要满足以上两个条件,第一是,这张表,必须是读多写少的,例如我曾在一家在线教育公司任职,课程产品表,就是一个读多写极少,且数据量很小的表,即使课程唯一ID无序,也完全可以作为主键,对性能几乎无影响,新增频率极低。


总结

其实当我们的软硬件资源足够充沛,并可预见的能持续保持时,我们可以减少一些开发、设计细节上的斟酌和处理,但是,思考却不能敷衍。

不同的外部条件下,我们的行为可能会做出调整或取舍,但思考必须是细致的、全面的、尽力的,为作品,也为自己的成长。

公众号: 今天404