浅谈MySQL为何推荐使用自增主键

前言

在《高性能MySQL(第3版)》中,提及了这么一段话:

如果正在使用InnoDB表并且没有什么数据需要聚集,那么可以定义一个代理键(surrogate key)作为主键,这种主键的数据应该和应用无关,最简单的方法是使用 AUTO_INCREMENT 自增列。这样可以保证数据行是按顺序写入,对于根据主键做关联操作的性能也会更好。

为何书本的作者会在此推荐MySQL的在选用默认存储引擎InnoDB时使用自增主键?这可能得从InnoDB存储引擎底层的数据存储结构 B+树 开始说起。


1.B+树

B+树也是树结构大家族中的一员,但它的存在与其它的兄弟姐妹相对比却显得那么格格不入。如老大哥红黑树、二叉搜索树、二叉平衡树等都是 “高瘦“ 型的**,而B+树与它们相比显得有些 “矮胖” ,并且它的性格也十分古怪,只在叶子节点存储具体的数据信息,且叶子节点之间以链表的形式进行连接,非叶子节点只存储关键字的索引,不像传统的树结构每个节点都存储对应的具体数据信息。

虽然B+树在大家族中是一个“另类”的存在,但也正因为存在独特的性质,使得它与其它的兄弟姐妹相比,更适合作为数据库索引结构。

  1. 只有叶子节点存储具体的数据信息,减少了耗时的IO(磁盘)操作。
  2. 每个叶子节点都处于同一深度,使得查询每一条数据时的效率相当,查询效率稳定。
  3. 叶子节点之间使用链表进行连接,方便进行全表扫描。

需要注意的是: 为了方便范围查询的操作,InnoDB的B+树结构叶子节点之间是通过双链表进行连接的。详情可到MySQL官方文档查看:https://dev.mysql.com/doc/internals/en/innodb-fil-header.html

下图为一颗标准的B+树示例图:


2.页

在对InnoDB的存储结构B+树做了简单了解后,我们接着来了解 ”页” 的概念。

定义: 页是InnoDB存储引擎管理数据库的最小磁盘单位(默认情况下页的大小为 16kb )。

如下图: InnoDB其中一页数据包含四条信息分别为:data1、data2、data3、data4,且每条数据的所占空间大小都为 4kb 。在不引入页的思想时,查询数据data1后,此时执行一次IO操作;如果我还想继续查询数据data2,需要再执行一次IO操作。总共执行了两次IO操作,但当我们引入页的思想后,查询数据data1时,将会查询出整一页的内容(data1、data2、data3、data4),接着查询data2时,由于内存中已经存在data2的数据了,因此无需再进行IO操作,在内存中获取即可。

笔者个人观点: 页的设计也应该归属于“空间换取时间”思想的一种,牺牲一点内存空间来节省在将来有可能会发生的IO操作的时间。

当然,以上观点只是笔者自己对“页”作用的一种理解,若存在不合理之处或您有更好的理解欢迎在下方评论区提出~!


3.页分裂

页分裂的讲解,笔者仅在这里描述其基本原理,不做深入的探究。对该知识点有强烈兴趣的读者,推荐阅读此篇博客:https://www.cnblogs.com/better-farther-world2099/articles/14704530.html

如下图:共有三个“页”(page1、page2、page3),其中page1存储了关键字在1 ~ 5的数据,page2存储了关键字在21 ~ 25的数据,page3只存储了一个关键字为32的数据。

若向库中新增一条关键字为33的数据,因为底层数据存储结构是B+树,数据关键之间是顺序的,且33 > 32, 因此只需要在page3中关键字为32的数据后面新增该数据即可,此时并不会出现“页分裂”的情况。

但当我们需要新增一条关键字为15的数据时,发现可以插入到page1的尾部,但是page1的空间已经满了,无法容下新数据15,新数据无法插进去。这时我们就需要考虑对page1进行分裂操作。

InnoDB的做法是将需要分裂的页从中间开始分裂,前半截继续保留在原来的页面,后半截移动到新开辟的页面中,最后再将需要新添加的数据插入。

第一步:从中间开始分裂

第二步:新开辟页面,将后半截数据移动至新页面中

第三步:将新数据15插入


总结

写到这里,其实笔者从始至终都没有直接提及推荐MySQL使用自增主键的原因。但已经对B+树、页分裂等概念做了基本了解的你,相信已经拥有了能够独立推断其中缘由的能力。

MySQL默认的InnoDB存储引擎,为了提高数据的查询效率选用叶子节点为双向链表连接的B+树作为数据存储结构,使得每一条数据都按照关键字有序排列好,在进行查找操作时,只需通过关键字即可快速定位数据位置。

与此同时,InnoDB引擎以页作为磁盘和内存交互的基本单位,为了保证每一条数据都能够有序的存储,每一次进行插入操作时,都有可能触发“页分裂”操作,因而拉低插入操作的效率。就如同我们为了提高某个字段的查询效率而为其增加辅助索引一般,虽然提高了查询效率,但伴随而来的“副作用”则是数据插入效率的降低。

“在程序猿的世界里,鱼和熊掌很难做到兼得,为了某一目的,你总得牺牲些什么。”

使用自增主键能够最大程度的避免“页分裂”的发生,从而减少这种“牺牲”的出现,同时也是解决该问题最便捷、有效的方法之一。

当然,还是那句话,我们很难做到两全其美的事情,使用自增主键是最便捷的,但绝对不是最优的解决方案,其也会为我们带来一定的“副作用”。

对于高并发工作负载,在InnoDB中按主键顺序插入可能会造成明显的争用。主键的上界会成为“热点”。因为所有的插入都发生在这里,所以并发插入可能导致间隙锁竞争。

因此,对于自增主键的使用仅是推荐,我们还需要结合自身项目的需求权衡利弊来做出最终的选择,毕竟抛开实际业务而不谈的行为都是“耍流氓”。

  • 10
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云丶言

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值