欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

MySQL自增ID耗尽实例讲解

程序员文章站 2023-11-10 16:15:28
显示定义id 表定义的自增值id达到上限后,在申请下一个id时,得到的值保持不变 -- (2^32-1) = 4,294,967,295 -- 建议使用...

显示定义id

表定义的自增值id达到上限后,在申请下一个id时,得到的值保持不变

-- (2^32-1) = 4,294,967,295
-- 建议使用 bigint unsigned
create table t (id int unsigned auto_increment primary key) auto_increment=4294967295;
insert into t values (null);

-- auto_increment没有改变
mysql> show create table t;
+-------+------------------------------------------------------+
| table | create table           |
+-------+------------------------------------------------------+
| t  | create table `t` (
 `id` int(10) unsigned not null auto_increment,
 primary key (`id`)
) engine=innodb auto_increment=4294967295 default charset=utf8 |
+-------+------------------------------------------------------+

mysql> insert into t values (null);
error 1062 (23000): duplicate entry '4294967295' for key 'primary'

innodb row_id

1、如果创建的innodb表没有指定主键,那么innodb会创建一个不可见的,长度为6 bytes的row_id

2、innodb维护一个全局的dict_sys.row_id值,所有无主键的innodb表,每插入一行数据

  • 都将当前的dict_sys.row_id值作为要插入数据的row_id,然后把dict_sys.row_id的值+1

3、代码实现上,row_id是一个8 bytes的bigint unsigned

  • 但innodb设计时,给row_id只保留了6 bytes的空间,写到数据表时只会存放最后的6 bytes
  • row_id的取值范围:0 ~ 2^48-1
  • 达到上限后,下一个值就是0

4、在innodb里面,申请到row_id=n后,就将这行数据写入表中

  • 如果表中已经有row_id=n的行,新写入的行就会覆盖原有的行

5、推荐显示创建自增主键

  • 表自增id达到上限后,再插入数据时会报主键冲突的错误,影响的是可用性
  • 而覆盖数据,意味着数据丢失,影响的是可靠性
  • 一般来说,可靠性优于可用性

xid

1、redolog和binlog相配合的时候,有一个共同的字段xid,对应一个事务

2、生成逻辑

  • mysql内部维护一个全局变量global_query_id
  • 每次执行语句的时候将global_query_id赋值给query_id,然后global_query_id+1
  • 如果当前语句是这个事务执行的第一条语句,把query_id赋值给这个事务的xid

3、global_query_id是一个纯内存变量,重启之后清零

  • 因此,在同一个数据库实例中,不同事务的xid也有可能是相同的
  • mysql重启之后,会重新生成新的binlog
    • 保证:同一个binlog文件里,xid是唯一的
  • global_query_id达到上限后,就会继续从0开始计数
    • 因此理论上,同一个binlog还是会出现相同的xid,只是概率极低

4、global_query_id是8 bytes,上限为2^64-1

  • 执行一个事务,假设xid是a
  • 接下来执行2^64次查询语句,让global_query_id回到a
  • 再启动一个事务,这个事务的xid也是a

innodb trx_id

1、xid是由server层维护的

2、innodb内部使用的是trx_id,为的是能够在innodb事务和server层之间做关联

3、innodb内部维护一个max_trx_id的全局变量

  • 每次需要申请一个新的trx_id,就获得max_trx_id的当前值,然后max_trx_id+1

4、innodb数据可见性的核心思想

  • 每一行数据都记录了更新它的trx_id
  • 当一个事务读到一行数据的时候,判断数据可见性的方法
    • 事务的一致性视图和这行数据的trx_id做对比

5、对于正在执行的事务,可以通过information_schema.innodb_trx看到事务的trx_id

操作序列

时刻 session a session b
t1 begin;
select * from t limit 1;
t2 use information_schema;
select trx_id,trx_mysql_thread_id from innodb_trx;
t3 insert into t values (null);
t4 select trx_id,trx_mysql_thread_id from innodb_trx;
-- t2时刻
mysql> select trx_id,trx_mysql_thread_id from innodb_trx;
+-----------------+---------------------+
| trx_id   | trx_mysql_thread_id |
+-----------------+---------------------+
| 281479812572992 |     30 |
+-----------------+---------------------+

-- t4时刻
mysql> select trx_id,trx_mysql_thread_id from innodb_trx;
+-----------------+---------------------+
| trx_id   | trx_mysql_thread_id |
+-----------------+---------------------+
| 7417540   |     30 |
+-----------------+---------------------+

mysql> show processlist;
+----+-----------------+-----------+--------------------+---------+--------+------------------------+------------------+
| id | user   | host  | db     | command | time | state     | info    |
+----+-----------------+-----------+--------------------+---------+--------+------------------------+------------------+
| 4 | event_scheduler | localhost | null    | daemon | 344051 | waiting on empty queue | null    |
| 30 | root   | localhost | test    | sleep | 274 |      | null    |
| 31 | root   | localhost | information_schema | query |  0 | starting    | show processlist |
+----+-----------------+-----------+--------------------+---------+--------+------------------------+------------------+

1、trx_mysql_thread_id=30就是线程id,即session a所在的线程

2、t1时刻,trx_id的值其实为0,而很大的值只是为了显示用的(区别于普通的读写事务)

3、t2时刻,trx_id是一个很大的数字,因为在t1时刻,session a并未涉及更新操作,是一个只读事务

  • 对于只读事务,innodb不会分配trx_id

4、session a在t3时刻执行insert语句时,innodb才真正分配trx_id

只读事务

1、在上面的t2时刻,很大的trx_id是由系统临时计算出来的

  • 把当前事务的trx变量的指针地址转成整数,再加上2^48

2、同一个只读事务在执行期间,它的指针地址是不会变的

  • 不论是在innodb_trx还是innodb_locks表里,同一个只读事务查出来的trx_id都是一样的

3、如果有多个并行的只读事务,每个事务的trx变量的指针地址肯定是不同的

  • 不同的并发只读事务,查出来的trx_id是不同的

4、加上2^48的目的:保证只读事务显示的trx_id值比较大,用于区别普通的读写事务

5、trx_id与row_id的逻辑类似,定义长度为8 bytes

  • 在理论上,可能会出现一个读写事务与一个只读事务显示的trx_id相同的情况
  • 但概率极低,并且没有什么实质危害

6、只读事务不分配trx_id的好处

  • 可以减少事务视图里面活跃数组的大小
    • 当前正在运行的只读事务,是不影响数据的可见性判断
    • 因此,在创建事务的一致性视图时,只需要拷贝读写事务的trx_id
  • 可以减少trx_id的申请次数
    • 在innodb里,即使只执行一条普通的select语句,在执行过程中,也要对应一个只读事务
    • 如果普通查询语句不申请trx_id,就可以大大减少并发事务申请trx_id的锁冲突
    • 由于只读事务不分配trx_id,trx_id的增加速度会变慢

7、max_trx_id会持久化存储,重启不会重置为0,只有到达2^48-1的上限后,才会重置为0

thread_id

1、show processlist的第一列就是thread_id

2、系统保存了一个环境变量thread_id_counter

  • 每新建一个连接,就将thread_id_counter赋值给这个新连接的线程变量

3、thread_id_counter定义为4 bytes,因此达到2^32-1后就会重置为0

  • 但不会在show processlist里面看到两个相同的thread_id
  • 因为mysql设计了一个唯一数组的逻辑,给新线程分配thread_id,逻辑代码如下
do {
  new_id= thread_id_counter++;
} while (!thread_ids.insert_unique(new_id).second);

参考资料

《mysql实战45讲》

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。