公司最近开发了一个新项目。在设计表的时候,有些字段需要对外展示,所以使用雪花算法生成的id作为主键。但是有同事对此提出异议,认为雪花算法生成的id不是顺序递增的,会影响MySQL的性能。经过交流,我发现有几位同事持有这种认知。估计有不少朋友对此有疑问。那么今天我们就来分析下使用雪花算法生成的id作为主键对MySQL性能有没有影响。没有效果?MySQL必须使用不断增加的主键来最大化性能?既然要分析不同主键的性能,首先要了解MySQL数据是如何存储的。相信对MySQL稍有了解的人大概都知道,MySQL的InnoDB引擎是使用B+树来存储数据的。为了数据安全,这些数据最终会持久化到磁盘。那么当我们查询或者修改数据的时候,如果每次都把所有的数据都从磁盘加载到内存中似乎不太现实,而且一次只读一条数据又太浪费IO了,那怎么办呢?于是这些设计MySQL的大师提出了页的概念,即在很多页上保存数据,内存和磁盘的交互是基于页的。默认情况下,一个页面的大小是16KB,也就是说每次至少要从磁盘加载16KB的数据到内存中。反之,每次至少有16KB的数据从内存持久化到磁盘。这样既利用了时间,也利用了空间,最大程度地保证了性能。当然,页的种类有很多种,比如存放表空间信息的页、undolog页、存放数据的数据页等。在本文中,我们只讨论数据页和目录页。下图是一个InnoDB数据页的结构,大家心中有个印象即可。UserRecords其实就是用来保存我们的数据的,我们来看看数据在页面中是如何保存的。需要特别注意的是,为了性能,这些记录按照主键的大小升序排列,最后形成一个单向链表。另外,每个数据页都会生成一个页目录,通过主键查找某条记录时,可以通过二分查找的方式快速找到需要的数据。上面我们提到,一个页面默认只有16KB,也就是说存储的数据是有限的,所以当你要存储大量的数据时,需要申请大量的数据页,如下图:上图,我们可以看到每个数据页都保存了很多条记录,相邻页之间的连接是通过双向链表保存的。需要注意的是,这些数据页不一定是物理空间中连续的地址。至此我们知道MySQL是通过数据页来存储数据的,但是随着表数据的增加,会带来一个明显的问题:页数过多难以管理。因此,InnoDB的高手们设计了目录页(目录页+数据页构成了一个索引树)。从名字就可以看出,目录页只是一个目录,并不存储具体的数据。它保存的数据其实很简单:主键和页码。从上图中我们可以看出,第30页是一个目录页(可以看成是树的根节点),第10页、第28页、第9页、第20页是真正存储数据的数据页。目录页中存储最小的主键id和每个数据页对应的页码,按照主键id排序。在数据页中,数据也是按照主键从小到大排序的,下一页的最小记录会比上一页的最大记录大。一般来说,这些页面的数据是增量的。注意!正在增加,但不需要顺序递增。因为对于二分查找法来说,只要数据是顺序递增的,就可以保证快速找到我们需要的数据。以查找id=8的记录为例。首先通过二分查找的方式在根节点找到记录5,对应的页码为28,然后找到28页,通过二分查找的方式找到主键为8的记录。现在回到我们的问题,雪花算法生成的id会不会影响MySQL的性能?雪花算法的一大特点是什么?大致递增。也就是说,只要是增量的,即使我们用JAVA的AtomicInteger或者redis的incrmentBy来生成主键id也是没有问题的。关于雪花算法我就不过多介绍了。想了解更多的朋友可以看看这篇文章。雪花算法简介。多说一句:虽然mysql的自增主键在应用的时候是在表级全局自增的,但是不一定最后保存在表中。举个简单的例子,批量保存10条数据,不知为何,事务操作回滚。当你插入另一条数据时,你会发现上次申请的10个id都浪费了,而表中的id是从11开始的。MySQL的数据结构和索引是一个庞大的系统,很难做到通过一篇简单的文章彻底解释它。如果您对本文有不同的看法,欢迎在评论区交流。
