MySQL中有很多的自增id,对于不同的自增id,MySQL的处理策略是不同的,而且对于所有的策略,都是定义了一个初始值,然后不停的往上增加步长,虽然自然数是没有上限的,但是在计算机中,只要一个数有字节上限,就会有达到的一天,比如一个int就是 2的32次方-1

既然自增id有上限,那么就会用完,今天看下自增id的处理策略

1.表定义的自增id

简单来说,就是表结构定义的自增字段,一个表定义的自增值到达上限之后,再申请下一个id时候,得到的值保持不变的

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

insert into t values(null);

//成功插入一行 4294967295

show create table t;

/* CREATE TABLE `t` (

`id` int(10) unsigned NOT NULL AUTO_INCREMENT,

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=4294967295;

*/

insert into t values(null);

//Duplicate entry ‘4294967295’ for key ‘PRIMARY’

在插入第一个达到最大值的时候,其AUTO_INCREMENT没有改变,但是插入第二个时候又拿到了相同的自增id值,在试图插入语句,会爆出主键冲突错误

2的32次方-1 不是一个特别大的数,对于一个频繁插入删除数据的表来说,很可能被用完的,因此在使用主键id的时候可以创建为8字节的bigint unsigned

2.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字节的无符号整型,但是在InnoDB设计的时候,row_id留的只是一个6字节的长度,这样只有最后6个字节放了进去

所以

1.row_id写入表中值的范围,是从0到2的48次方-1

2.当dict_sys.row_id=2的48次方的时候,如果在有插入的行为申请row_id时候,下一个值就0

这个值会循环0到2的48次方-1

当然这个2的48次方本身是很大的,但是一个MySQL如果跑的够久,是可能出现的,如果出现了重复的row_id,就会让新的行覆盖到原来的行

可以利用gdb来修改系统的row_id来模拟覆盖的问题

图片

图片

可以看出新插入的行的row_id已经变为了0和1

于是覆盖了第一行的value

从这个角度的话,还是在InnoDB表中设置一个自增主键,在表自增id中到达上限后,在进行插入的话,是会报错的

因为,覆盖数据的情况,会更加的可怕

3.Xid

在redo log和binlog相配合的时候,有一个相同字段叫做Xid

Xid是怎么生成的呢?内部维护了一个global_query_id,每次执行的时候将其赋予给Query_id 然后进行加一

如果这个语句是第一条语句,那么还是会把Query_id赋值给这个事务的Xid

但是MySQL重启后生成一个新的binlog文件,保证了一个同一个binlog中Xid是唯一的

但是global_query_id还是可能达到上限的,所以有相同Xid的情况,但可能性不高

4.InnoDB trx_id

InnoDB这个 trx_id是为了维护事务可见性的,InnoDB内部维护了一个max_trx_id的全局变量,每次需要更新新的trx_id时候,就会获取这个值,然后进行加1

当一个事务读到一个数据的时候,判断这个数据是否可见,就通过这个trx_id

可以通过information_schema.innodb_trx表看到这个trx_id

图片

从sessionB来看innodb_trx表里查出这两个字段,两个字段trx_mysql_thread.id就是线程id,标定了session A所在线程

可以看出 T2时刻的trx_id是一个很大的值,T4是一个很小的值

对于T1时刻,只是一个读取语句,不涉及更新,对于T1时刻,trx_id值没有被赋予

T2时刻,才会真正的分配了trx_id,所以T4时刻,sessionB查到了trx_id值是1289

这就是只读语句不会进行赋予trx_id

而且很多时候表的内部事务,比如索引信息统计,可能不会按照1递增的

或者删除数据的时候,可能数据放在purge队列等待物理删除,所以事务id也会增加

那么T2时刻这个很大的值是怎么回事的呢

其实这个数字是系统临时计算的,其算法为,把这个事务的trx变量的指针的地址变为了整数,再加上2的48次方,

这样就能保证了

1.同一个事务中,内存指针是不会变的,于是能够保证trx_id是一样的

2.如果有并行的多个只读事务,那么每个事务的trx变量的指针地址是不同的,这样,不同并发只读事务,查出来的trx_id是不同的

为什么要加上2的48次方呢

为了就是保证只读事务和读写事务之间的区别,一眼能够认出来,但是一个MySQL执行的时候,如果足够长,那么会出现trx_id相同的情况,不过概率很低

只读事务部分配trx_id的话,可以减少事务中活跃的事务数组的大小,所以让MySQL只专注读写事务的trx_id

而且减少了trx_id的分配次数,避免了trx_id的申请的锁的冲突

但是 max_trx_id会持久化存储,于是一个MySQL跑的足够就,就可能出现max_trx_id到达2的48次方了,

如果到达了,会从0重新计数,就会出现一个脏读的问题

图片

图片

首先把max_trx_id修改为2的48次方-1

然后就会因为后来修改的事务的trx_id比之前的小,从一直出现了脏读的问题

因为事务一直持续增加,但是事务id从0开始增加,于是会持续出现脏读问题

这个问题,MySQL认为,每秒的TPS是50万,那么也会在17.8年后出现,所以暂时没必要担心

5.thread_id问题

线程id,是MySQL中最为常见的一种自增id,show processlist中的第一列,就是thread_id

这个逻辑好理解,就是系统粗暴拿了一个全局变量 thread_id_counter,每次新建一个线程,就会将thread_id_counter赋值给这个新连接的线程变量

这个变量的大小是4字节,如果达到了2的32次方-1,就会重置为0,然后继续增加

因为MySQL设置了一个唯一数组逻辑,这个线程分配thread_id的时候,逻辑代码是这样的

图片

总结一下,

1.表的自增主键id到了上限后,不会在改变,但是继续插入数据会报主键冲突的错误

2.row_id达到了上限之后,归0会重新递增,出现row_id,会覆盖之前的数据

3.Xid只需要保证不在同一个binlog中出现了重复值

4.InnoDB trx_id,出现用完的可能性很低

5.thread_id,分组策略,每次使用完成加一个前缀,很好

发表评论

邮箱地址不会被公开。 必填项已用*标注