MySQL只学有用的--你知道自增ID会用完吗?

MySQL里的自增ID是定义了初始值,然后不停地加步长。我们在创建这个字段的时候会给指定一个字节长度。这个字节长度就是这个ID的上限。比如:无符号整型(unsigned int)是4个字节,上限就是 232=12^{32}=1

既然有上限,那么就有可能用完? 下面我们就来聊一聊自增ID用完了怎么办?

下面我们会通过几种不同的自增ID,来分析一下它们的值达到上限以后的情况。

表定义自增值 ID

表定义的自增是比较常用的一种方式,通过就是一张表里面的ID主键,设置成一个自增长。
表定义的自增值达到上限后的逻辑是:再申请下一个ID时,得到的值保持不变。
我们可以通过下面这个语句序列验证:
请依次执行下面的语句,不要一次性都执行,这样你可以观察一下结果。

# create table t_test(id int unsigned auto_increment primary key ) auto_increment = 4294967295;

# insert into t_test VALUES(null);

# show create table t_test;

insert into t_test VALUES(null);

下面我们来解释一下这些SQL
第一行,表示创建一个表,id为主键int类型,unsigned代表无符合只能是正数,
auto_increment 代表为自增, primary key表示主键, auto_increment =num 表示步长为num。
第二行插入一条记录,为null是希望使用数据库自动生成的参数。
第三行查看表的结构。
第四条在次使用mysql 自动生成的参数插入一条数据。

最后的运行结果为

insert into t_test VALUES(null)
> 1062 - Duplicate entry '4294967295' for key 'PRIMARY'
> 时间: 0.022s

原因是第一个insert 语句插入数据成功后,这个表的AUTO_INCREMENT 没有改变(还是4294967295), 就导致了第二个insert 语句又拿到相同的自增ID值,再试图执行插入语句,报主键冲突错误。

如果是一个频繁插入删除数据的表,可以选择使用8个字节的bigint unsigned.

话外题: 为什么默认最大值是4294967295? 为什么要有unsigned? 为什么不是设置int(11)呢?

先回答第一个问题,最大值怎么计算,一个字节代表8位,计算机也是二进制语言,一位代表两个数,因此公式为 248=12^{4*8}=1

第二个问题,为什么要有unsigned?
因为我们mysql中的数字类型是有正负数的,而ID都是从零开始的,如果在有负数的情况下, int类型4个字节,能生成的ID数量只有4294967295除以2。整整少了一半,因此就需要设置上去掉负数的类型。

第三个问题: 为什么不是设置int(11)呢?
在SQL语句中int代表你要创建字段的类型,int代表整型,11代表字段的长度。

这个11代表显示宽度,整数列的显示宽度与mysql需要用多少个字符来显示该列数值,与该整数需要的存储空间的大小都没有关系,比如,不管设定了显示宽度是多少个字符,bigint都要占用8个字节。

int是整型,(11)是指显示字符的长度,但要加参数的,最大为255,比如它是记录行数的id,插入10笔资料,它就显示00000000001 ~~~00000000010,当字符的位数超过11,它也只显示11位,如果你没有加那个让它未满11位就前面加0的参数,它不会在前面加0

声明整型数据列时,我们可以为它指定个显示宽度M(1~255),如INT(5),指定显示宽度为5个字符,如果没有给它指定显示宽度,MySQL会为它指定一个默认值。显示宽度只用于显示,并不能限制取值范围和占用空间,如:INT(3)会占用4个字节的存储空间,并且允许的最大值也不会是999,而是 INT整型所允许的最大值。

InnoDB 系统自增row_id

如果你创建的InnoDB表没有指定主键,那么InnoDB会给你创建一个不可见的,长度为6个字节的row_id。InnoDB 维护了一个全局的dict_sys.row_id值,所以无主键的InnoDB表,每插入一行数据,都将当前的dict_sys.row_id值作为要插入数的row_id,然后把dict_sys.row_id的值加1。

实际上,在代码实现时row_id是一个长度为8字节的无符号长整型(bigint unsigned)。但是,InnoDB在设计时,给row_id留的只是6个字节的长度,这样写到数据表中只放了最后6个字节,所以row_id能写到数据表中的值,就有两上特征:

  1. row_id写入表中的值范围,是从0到2481 2^{48}-1
  2. 当dict_sys.row_id 到达上限时,如果再有插入数据的行为再来申请row_id,拿到以后再取最后6个字节的话就是0,取到0以后,再有插入数据的行为就会是1。这样就开始了新的一轮从0到最大值2481 2^{48}-1

这样会有一个后果,当开始新的一轮获取row_id之后,会将原来相同row_id的数据覆盖。

下面我们来一起验证一下:

  1. 我们创建一张表test_inc,不包含任何索引。
create table test_inc(id int) engine=innodb;
  1. 通过ps -ef|grep mysql得到对应的进程号,使用gdb来开始做下调试配置,切记!此处应该是自己的测试环境。
gdb -p 3132 -ex 'p dict_sys->row_id=1' -batch  
  1. 插入一些数据,使得rowid持续自增。
 insert into test_inc values(1),(2),(3);  
  1. 我们对rowid进行重置,调整为2^48
 gdb -p 3132 -ex 'p dict_sys->row_id=281474976710656' -batch  
  1. 继续写入一些数据,比如我们写入4,5,6三行数据
 insert into test_inc values(4),(5),(6);  
  1. 查看数据结果,发现1,2两行已经被覆盖了。
select *from test_inc;  
+------+ 
| id |  
+------+  
| 4 |  
| 5 |  
| 6 |  
| 3 |  
+------+  
4 rows in set (0.00 sec) 

从这个角度看,我们还是应该在InnoDB表中主动创建自增主键。因为相对于报主键冲突错误,我们无法接受数据丢失。
覆盖数据影响的是数据的可靠性,主键冲突,是插入失败影响的是可用性。而一般情况下,可靠性优先于可用性。

Xid,trx_id,tread_id

XID只需要不在同一个binlog文件中出现重复值即可。虽然理论上会出现重复值,但是概率极小,忽略不计。
trx_id 要想出现重复值会出现一个B需要在每秒50万的访问量下,连续跑17.8年。

业务ID选择

业务ID选择要分具体的业务场景,如果是小的传统项目,数据量不大的话,直接使用表自增就可以了。 如果int不够的话,设置成bigint就够了。

如果是像视频网站中 观看记录表,因为是长年累月的增加,建议使用分布式的ID生成器。 也方便以后分库分表。

这里不太推荐雪花算法, 因为他本身的长度太长了。对索引的使用不是很友好。

在这里推荐一个ID生成器,是我一个朋友写的。我也贡献了一点点代码。

https://github.com/barleyawn/zebra

借鉴

极客时间<MySQL实战45讲>
MySQL中int(11)最大长度是多少?
自增row_id验证

交个朋友好吗?

以上内容均为读书所得, 更多有趣有料的科技资讯请关注公众号。(交个朋友)
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章