MySQL自增ID耗尽实例讲解

显示定义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讲》

总结

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

(0)

相关推荐

  • mysql如何让自增id归0解决方案

    最近老是要为现在这个项目初始化数据,搞的很头疼,而且数据库的Id自增越来越大,要让自增重新从1开始:那么就用下面的方法吧: 方法一: 如果曾经的数据都不需要的话,可以直接清空所有数据,并将自增字段恢复从1开始计数 truncate table 表名 方法二: dbcc checkident ('table_name', reseed, new_reseed_value) 当前值设置为 new_reseed_value.如果自创建表后没有将行插入该表,则在执行 DBCC CHECKIDENT 后插

  • 利用mysql事务特性实现并发安全的自增ID示例

    项目中经常会用到自增id,比如uid,最简单的方法就是用直接用数据库提供的AUTO_INCREMENT,但是如果用户量非常大,几千万,几亿然后需要分表存储的时候呢,这种方案就搞不定了,所以最好有一个全局的自增ID的生成器,不管是否分表,都能从生成器中获取到全局自增的ID. 实现方法应该有很多,不过所有的方案都需要解决一个问题,就是保证在高并发的情景下,数据获取依然正确,每次获取的ID都不会重复. 这里我分享两种利用mysql的innodb的事务特性来实现的方案,一种是实现过了的,另一种没有试验过

  • Java获取最后插入MySQL记录的自增ID值的3种方法

    方法一: 复制代码 代码如下: String sql = "INSERT INTO users (username,password,email) VALUES (?,?,?);";PreparedStatement pstmt = (PreparedStatement) conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);//传入参数:Statement.RETURN_GENERATED_KEYSpstmt.setSt

  • 两种mysql对自增id重新从1排序的方法

    最近老是要为现在这个项目初始化数据,搞的很头疼,而且数据库的Id自增越来越大,要让自增重新从1开始:那么就用下面的方法吧: 方法一:如果曾经的数据都不需要的话,可以直接清空所有数据,并将自增字段恢复从1开始计数 truncate table 表名 方法二:dbcc checkident ('table_name', reseed, new_reseed_value) 当前值设置为 new_reseed_value.如果自创建表后没有将行插入该表,则在执行 DBCC CHECKIDENT 后插入的

  • 关于Mysql自增id的这些你可能还不知道

    导读: 在使用MySQL建表时,我们通常会创建一个自增字段(AUTO_INCREMENT),并以此字段作为主键.本篇文章将以问答的形式讲述关于自增id的一切. 注: 本文所讲的都是基于Innodb存储引擎. 下面话不多说了,来一起随着小编看看详细的介绍吧 1.MySQL为什么建议将自增列id设为主键? 如果我们定义了主键(PRIMARY KEY),那么InnoDB会选择主键作为聚集索引.如果没有显式定义主键,则InnoDB会选择第一个不包含有NULL值的唯一索引作为主键索引.如果也没有这样的唯一

  • 关于MySQL自增ID的一些小问题总结

    下面这几个小问题都是基于 InnoDB 存储引擎的. 1. ID最大的记录删除后,新插入的记录ID是什么 例如当前表中有ID为1,2,3三条记录,把3删除,新插入记录的ID从哪儿开始? 答案: 从4开始. 实验 创建表 tb0,ID自增: create table tb0(id int unsigned auto_increment primary key); 插入3条记录: insert into tb0 values(null); 删除ID为3的记录: delete from tb0 whe

  • mysql自增ID起始值修改方法

    在mysql中很多朋友都认为字段为AUTO_INCREMENT类型自增ID值是无法修改,其实这样理解是错误的,下面介绍mysql自增ID的起始值修改与设置方法.通常的设置自增字段的方法:创建表格时添加: 复制代码 代码如下: create table table1(id int auto_increment primary key,...) 创建表格后添加: 复制代码 代码如下: alter table table1 add id int auto_increment primary key 自

  • MySQL分表自增ID问题的解决方法

    当我们对MySQL进行分表操作后,将不能依赖MySQL的自动增量来产生唯一ID了,因为数据已经分散到多个表中.  应尽量避免使用自增IP来做为主键,为数据库分表操作带来极大的不便.  在postgreSQL.oracle.db2数据库中有一个特殊的特性---sequence. 任何时候数据库可以根据当前表中的记录数大小和步长来获取到该表下一条记录数.然而,MySQL是没有这种序列对象的.  可以通过下面的方法来实现sequence特性产生唯一ID:  1. 通过MySQL表生成ID  对于插入也

  • mysql自增id超大问题的排查与解决

    引言 小A正在balabala写代码呢,DBA小B突然发来了一条消息,"快看看你的用户特定信息表T,里面的主键,也就是自增id,都到16亿了,这才多久,在这样下去过不了多久主键就要超出范围了,插入就会失败,balabala......" 我记得没有这么多,最多1k多万,count了下,果然是1100万.原来运维是通过auto_increment那个值看的,就是说,表中有大量的删除插入操作,但是我大部分情况都是更新的,怎么会这样? 下面话不多说了,来一起看看详细的介绍吧 问题排查 这张表

  • MySQL自增ID耗尽实例讲解

    显示定义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

  • jQuery+Ajax+PHP+Mysql实现分页显示数据实例讲解

    本文使用jQuery,结合PHP和Mysql,通过实例讲解如何实现Ajax数据加载效果. HTML <div id="list"> <ul></ul> </div> <div id="pagecount"></div> 页面中,#list用来展示数据列表,包括本例要展示的商品图片和标题,#pagecount用来展示分页条,即本例中的上一页.下一页. 当然,别忘了,在head中预先载入jquery

  • ubutu 16.04环境下,PHP与mysql数据库,网页登录验证实例讲解

    正好最近的域名备案通过了,兴起就突然想做一个网页,虽然之前去备案域名也是有这个目的. 问过几个人,说用linux上用PHP搭建网站很简单,就试着做了一个,这里主要说一下登录验证相关的部分: 首相准备几个文件,主要是index.php.conn.php.data.php以及login.php; login.php 主要是登录过程中的数据对比部分:其中include ('conn.php')内容在下面有说. <?php if(!isset($_POST['submit'])){ exit('logi

  • 关于mysql自增id,你需要知道的

    导读:在使用MySQL建表时,我们通常会创建一个自增字段(AUTO_INCREMENT),并以此字段作为主键.本篇文章将以问答的形式讲述关于自增id的一切. 注: 本文所讲的都是基于Innodb存储引擎. 1.MySQL为什么建议将自增列id设为主键? 如果我们定义了主键(PRIMARY KEY),那么InnoDB会选择主键作为聚集索引.如果没有显式定义主键,则InnoDB会选择第一个不包含有NULL值的唯一索引作为主键索引.如果也没有这样的唯一索引,则InnoDB会选择内置6字节长的ROWID

  • MySQL外键约束的实例讲解

    MySQL的外键约束是用来在两个表之间建立链接的,其中一个表发生变化,另外一个表也发生变化.从这个特点来看,它主要是为了保证表数据的一致性和完整性的. 对于两个通过外键关联的表,相关联字段中主键所在的表是主表,也称之为父表,外键所在的表是从表,也称之为子表,定义外键的时候需要遵守几个规则: 1.父表必须已经存在于数据库中,或者是当前正在创建的表.如果是后一种情况,则父表与子表是同一个表,这样的表称为自参照表,这种结构称为自参照. 2.必须为父表定义主键. 3.主键不能包含空值,但允许在外键中出现

  • Python向MySQL批量插数据的实例讲解

    背景:最近测试web项目需要多条测试数据,sql中嫌要写多条,就看了看python如何向MySQL批量插数据(pymysql库) 1.向MySQL批量插数据 import pymysql #import datetime #day = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')#参数值插入时间 db = pymysql.connect(host='服务器IP', user='账号', passwd='密码', port=端口号) c

  • MySQL表自增id溢出的故障复盘解决

    问题:MySQL某个表自增id溢出导致某业务block 背景: tokudb引擎的一个大表tb1,存放业务上的机审日志,每天有大量的写入, 并且由于历史原因,这张表是int signed 类型的,最大只能存 2147483647行记录 . 处理过程: 增加DBLE中间件代理,然后做range分区,将新数据写到新加的的一个分片上. 同时业务上修改连接将这个表tb1的连接方式改走DBLE. 但是业务上改完代码后,发现还有残余的部分insert into tb1的写请求被转发到了老的表上,且有些表被错

  • MYSQL慢查询和日志实例讲解

    一.简介 开启慢查询日志,可以让MySQL记录下查询超过指定时间的语句,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能. 二.参数说明 slow_query_log 慢查询开启状态 slow_query_log_file 慢查询日志存放的位置(这个目录需要MySQL的运行帐号的可写权限,一般设置为MySQL的数据存放目录) long_query_time 查询超过多少秒才记录 三.设置步骤 1.查看慢查询相关参数 mysql> show variables like 'slow_quer

随机推荐