MySQL里的自增ID是定义了初始值,然后不停地加步长。我们在创建这个字段的时候会给指定一个字节长度。这个字节长度就是这个ID的上限。比如:无符号整型(unsigned int)是4个字节,上限就是 2 32 = 1 2^{32}=1 232=1
既然有上限,那么就有可能用完? 下面我们就来聊一聊自增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.
先回答第一个问题,最大值怎么计算,一个字节代表8位,计算机也是二进制语言,一位代表两个数,因此公式为 2 4 ∗ 8 = 1 2^{4*8}=1 24∗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表没有指定主键,那么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能写到数据表中的值,就有两上特征:
这样会有一个后果,当开始新的一轮获取row_id之后,会将原来相同row_id的数据覆盖。
下面我们来一起验证一下:
create table test_inc(id int) engine=innodb;
gdb -p 3132 -ex 'p dict_sys->row_id=1' -batch
insert into test_inc values(1),(2),(3);
gdb -p 3132 -ex 'p dict_sys->row_id=281474976710656' -batch
insert into test_inc values(4),(5),(6);
select *from test_inc;
+------+
| id |
+------+
| 4 |
| 5 |
| 6 |
| 3 |
+------+
4 rows in set (0.00 sec)
从这个角度看,我们还是应该在InnoDB表中主动创建自增主键。因为相对于报主键冲突错误,我们无法接受数据丢失。
覆盖数据影响的是数据的可靠性,主键冲突,是插入失败影响的是可用性。而一般情况下,可靠性优先于可用性。
XID只需要不在同一个binlog文件中出现重复值即可。虽然理论上会出现重复值,但是概率极小,忽略不计。
trx_id 要想出现重复值会出现一个B需要在每秒50万的访问量下,连续跑17.8年。
业务ID选择要分具体的业务场景,如果是小的传统项目,数据量不大的话,直接使用表自增就可以了。 如果int不够的话,设置成bigint就够了。
如果是像视频网站中 观看记录表,因为是长年累月的增加,建议使用分布式的ID生成器。 也方便以后分库分表。
这里不太推荐雪花算法, 因为他本身的长度太长了。对索引的使用不是很友好。
在这里推荐一个ID生成器,是我一个朋友写的。我也贡献了一点点代码。
https://github.com/barleyawn/zebra
极客时间
MySQL中int(11)最大长度是多少?
自增row_id验证