vlambda博客
学习文章列表

Mysql篇:为什么推荐自增列作为主键

简介

主键是一个索引,MySQL数据表使用InnoDB作为存储引擎的时候,数据结构就是使用B+树,而表的所有数据存储在主键索引上,也就是通常所说的聚簇索引。

每个表都需要有个聚簇索引树,而除了聚簇索引的普通索引存储的只是主键的引用,所以,查询的时候对于普通索引需要进行回表才能取到具体数据。


概述

MySQL数据表使用InnoDB作为存储引擎,它对聚簇索引处理如下:

  1. 如果我们定义了主键(PRIMARY KEY),那么InnoDB会选择主键作为聚集索引;

  2. 如果没有显式定义主键,则InnoDB会选择第一个不包含有NULL值的唯一索引作为主键索引;

  3. 如果也没有这样的唯一索引,则InnoDB会选择内置6字节长的ROWID作为隐含的聚集索引(ROWID随着行记录的写入而主键递增,这个ROWID不像ORACLE的ROWID那样可引用,是隐含的)。

缺少主键的表,InnoDB会内置一列用于聚簇索引来组织数据。而没有建立主键的话就没法通过主键来进行索引,查询的时候都是全表扫描,小数据量没问题,大数据量就会出现性能问题。


主键生成策略

下面是一些主键的生成策略:

  1. Mysql自增长主键策略:这种最简单,不需要程序特别处理 ,但是这种方法对项目移植到其它数据库上改动会比较大,oracle、db2采用Sequence,Mysql、sqlServer又采用自增长,通用性不好 ;

  2. 时间戳相关生成策略:这种实现简单,与数据库无关,移植性较好 ,但长度太长,最少也得20位; 比如雪花算法,可以生成20位有序的的ID;

  3. UUID、GUID:这种方式也很简单,代码方便,全球唯一,与数据库无关,移植性较好,但没有排序,无法保证趋势递增。UUID往往是使用字符串存储,查询的效率比较低;

  4. 取主键最大值+1为新的主键:这种主键长度可控,移植性较好 ,但并发可能会造成主键冲突,对并发也不太好控制;

  5. 单独建一个存放主键的表:这种实现简单,移植性较好,但需要考虑并发问题,整个系统主键生成都依赖该表,性能影响可能较大。



自增列作为主键的好处

这么多种主键生成策略,那为什么会推荐自增策略(时间戳相关生成策略其实是分布式的自增)呢?

  1. 数据记录本身被存于主索引(B+Tree)的叶子节点上。这就要求同一个叶子节点内(大小为一个内存页或磁盘页)的各条数据记录按主键顺序存放,因此每当有一条新的记录插入时,MySQL会根据其主键将其插入适当的节点和位置,如果页面达到装载因子(InnoDB默认为15/16),则开辟一个新的页(节点);

  2. 如果表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页;

  3. 如果使用非自增主键(如果身份证号或学号等),由于每次插入主键的值近似于随机,因此每次新记录都要被插到现有索引页的中间某个位置,此时MySQL不得不为了将新记录插到合适位置而移动数据,甚至目标页面可能已经被回写到磁盘上而从缓存中清掉,此时又要从磁盘上读回来,这增加了很多开销,同时频繁的移动、分页操作造成了大量的碎片,得到了不够紧凑的索引结构,后续不得不通过OPTIMIZE TABLE来重建表并优化填充页面。



总结

    主键尽量采用自增方式,InnoDB 表实际是一索引组织表,顺序存储可以提高存取效率,充分利用磁盘空间。 如果没有主键或唯一索引,update/delete 是通过所有字段来定位操作的行,相当于每行就是一次全表扫描。

    使用自增主键是最便捷的,但绝对不是最优的解决方案,对于高并发工作负载,在InnoDB中按主键顺序插入可能会造成明显的争用。主键的上界会成为“热点”。因为所有的插入都发生在这里,所以并发插入可能导致间隙锁竞争。



这里就先说这么多了,可以点击左下方的【阅读原文】查看完整的关于更多关于Mysql面试题的介绍


欢迎在评论区留下你的观点,一起讨论提高。

如果今天的文章让你有新的启发,或者在学习能力的提升上有新的认识,欢迎转发分享给更多人。


欢迎各位读者加入JAVA技术交流群,群号:227682204