问题重现

先从问题入手,重现下这个bug

这里我们关闭mysql,再启动mysql,然后再插入一条数据

我们看到插入了(2,2),而如果我没有重启,插入同样数据我们得到的应该是(4,2)。 上面的测试反映了mysqld重启后,InnoDB存储引擎的表自增id可能出现重复利用的情况。

自增id重复利用在某些场景下会出现问题。依然用上面的例子,假设t1有个历史表t1_history用来存t1表的历史数据,那么mysqld重启前,ti_history中可能已经有了(2,2)这条数据,而重启后我们又插入了(2,2),当新插入的(2,2)迁移到历史表时,会违反主键约束。

原因分析

InnoDB 自增列出现重复值的原因

建表时可以指定 AUTO_INCREMENT值,不指定时默认为1,这个值表示当前自增列的起始值大小,如果新插入的数据没有指定自增列的值,那么自增列的值即为这个起始值。对于InnoDB表,这个值没有持久到文件中。而是存在内存中(dict_table_struct.autoinc)。那么又问,既然这个值没有持久下来,为什么我们每次插入新的值后, show create table t1看到AUTO_INCREMENT值是跟随变化的。其实show create table t1是直接从dict_table_struct.autoinc取得的(ha_innobase::update_create_info)。

知道了AUTO_INCREMENT是实时存储内存中的。那么,mysqld 重启后,从哪里得到AUTO_INCREMENT呢? 内存值肯定是丢失了。实际上mysql采用执行类似select max(id)+1 from t1;方法来得到AUTO_INCREMENT。而这种方法就是造成自增id重复的原因。

MyISAM自增值

MyISAM也有这个问题吗?MyISAM是没有这个问题的。myisam会将这个值实时存储在.MYI文件中(mi_state_info_write)。mysqld重起后会从.MYI中读取AUTO_INCREMENT值(mi_state_info_read)。因此,MyISAM表重启是不会出现自增id重复的问题。

问题修复

MyISAM选择将AUTO_INCREMENT实时存储在.MYI文件头部中。实际上.MYI头部还会实时存其他信息,也就是说写AUTO_INCREMENT只是个顺带的操作,其性能损耗可以忽略。InnoDB 表如果要解决这个问题,有两种方法。1)将AUTO_INCREMENT最大值持久到frm文件中。2)将 AUTO_INCREMENT最大值持久到聚集索引根页trx_id所在的位置。第一种方法直接写文件性能消耗较大,这是一额外的操作,而不是一个顺带的操作。我们采用第二种方案。为什么选择存储在聚集索引根页页头trx_id,页头中存储trx_id,只对二级索引页和insert buf 页头有效(MVCC)。而聚集索引根页页头trx_id这个值是没有使用的,始终保持初始值0。正好这个位置8个字节可存放自增值的值。我们每次更新AUTO_INCREMENT值时,同时将这个值修改到聚集索引根页页头trx_id的位置。 这个写操作跟真正的数据写操作一样,遵守write-ahead log原则,只不过这里只需要redo log ,而不需要undo log。因为我们不需要回滚AUTO_INCREMENT的变化(即回滚后自增列值会保留,即使insert 回滚了,AUTO_INCREMENT值不会回滚)。

因此,AUTO_INCREMENT值存储在聚集索引根页trx_id所在的位置,实际上是对内存根页的修改和多了一条redo log(量很小),而这个redo log 的写入也是异步的,可以说是原有事务log的一个顺带操作。因此AUTO_INCREMENT值存储在聚集索引根页这个性能损耗是极小的。

修复后的性能对比,我们新增了全局参数innodb_autoinc_persistent 取值on/off; on 表示将AUTO_INCREMENT值实时存储在聚集索引根页。off则采用原有方式只存储在内存。

可以看出性能损耗在%1以下。

改进

新增参数innodb_autoinc_persistent_interval 用于控制持久化AUTO_INCREMENT值的频率。例如:innodb_autoinc_persistent_interval=100,auto_incrememt_increment=1时,即每100次insert会控制持久化一次AUTO_INCREMENT值。每次持久的值为:当前值+innodb_autoinc_persistent_interval。

测试结论

限制

1 innodb_autoinc_persistent=on, innodb_autoinc_persistent_interval=N>1时,自增N次后持久化到聚集索引根页,每次持久的值为当前AUTO_INCREMENT+(N-1)*innodb_autoextend_increment。重启后读取持久化的AUTO_INCREMENT值会偏大,造成一些浪费但不会重复。innodb_autoinc_persistent_interval=1 每次都持久化没有这个问题。

2 如果innodb_autoinc_persistent=on,频繁设置auto_increment_increment的可能会导致持久化到聚集索引根页的值不准确。因为innodb_autoinc_persistent_interval计算没有考虑auto_increment_increment变化的情况,参看dict_table_autoinc_update_if_greater。而设置auto_increment_increment的情况极少,可以忽略。

注意:如果我们使用需要开启innodb_autoinc_persistent,应该在参数文件中指定

如果这样指定set global innodb_autoinc_persistent=on;重启后将不会从聚集索引根页读取AUTO_INCREMENT最大值。

疑问:对于InnoDB表,重启通过select max(id)+1 from t1得到AUTO_INCREMENT值,如果id上有索引那么这个语句使用索引查找就很快。那么,这个可以解释mysql 为什么要求自增列必须包含在索引中的原因。 如果没有指定索引,则报如下错误,

ERROR 1075 (42000): Incorrect table definition; there can be only one auto column and it must be defined as a key 而myisam表竟然也有这个要求,感觉是多余的。

MySQL内核月报 2015.01-MySQL · 捉虫动态· InnoDB自增列重复值问题相关推荐

  1. MySQL内核月报 2014.09-MySQL· 捉虫动态·auto_increment

    背景: Innodb引擎使用B_tree结构保存表数据,这样就需要一个唯一键表示每一行记录(比如二级索引记录引用). Innodb表定义中处理主键的逻辑是: 1.如果表定义了主键,就使用主键唯一定位一 ...

  2. MySQL内核月报 2014.10-MySQL· 捉虫动态·binlog重放失败

    背景 在 MySQL 日常维护中,要回滚或者恢复数据,我们经常会用 binlog 来在数据库上重放,执行类似下面的语句: mysqlbinlog mysql-bin.000001 | mysql -h ...

  3. MySQL内核月报 2015.02-MySQL · 答疑释惑· InnoDB丢失自增值

    背景 在上一期的月报中,我们在InnoDB自增列重复值问题 中提到,InnoDB 自增列在重启后会丢失,因为MySQL没有持久化自增值,平时是存在内存表对象中的.如果实例重启的话,内存值丢失,其初始化 ...

  4. MySQL内核月报 2015.01-MySQL · 捉虫动态· replicate filter 和 GTID 一起使用的问题

    问题描述 当单个 MySQL 实例的数据增长到很多的时候,就会考虑通过库或者表级别的拆分,把当前实例的数据分散到多个实例上去,假设原实例为A,想把其中的5个库(db1/db2/db3/db4/db5) ...

  5. rds mysql 表被删了_MySQL · 捉虫动态 · 删除索引导致表无法打开

    问题背景 最近线上遇到一个问题,用户重启实例后发现有张表打不开了,经调研后发现是用户之前的霸蛮操作导致的,下面给出复现步骤: create table t1 (id int not null prim ...

  6. mysql数据库表删了重建error_数据库内核月报 - 2015 / 09-MySQL · 捉虫动态 · 建表过程中crash造成重建表失败-阿里云开发者社区...

    问题描述 主库的create table语句传到备库,备库SQL线程执行过程中报错: Error 'Can't create table 'XXX.XX' (errno: -1)' on query. ...

  7. MySQL · 捉虫动态 · 并行复制外键约束问题二

    背景 并行复制可以大大提高备库的 binlog 应用速度,内核月报也多次对并行复制特性进行介绍,感兴趣的朋友可以回顾下:5.6 并行复制实现分析.5.6 并行复制恢复实现 和 5.6并行复制事件分发机 ...

  8. mysql内核架构_热血江湖mysql内核技术之门派数据库表结构说明

    小编之前已经多次和大家说过了一个概念,不管你打算架设的是什么游戏什么版本都必须熟悉它的数据库整体结构.今天小编要说的是热血江湖私服游戏中最难的MYSQL内核技术,对于玩家来说MYSQL技术无疑是最难的 ...

  9. mysql auto position_MySQL内核月报 2015.01-MySQL · 捉虫动态· 设置 gtid_purged 破坏AUTO_POSITION复制协议-阿里云开发者社区...

    bug描述 Oracle 最新发布的版本 5.6.22 中有这样一个关于GTID的bugfix,在主备场景下,如果我们在主库上 SET GLOBAL GTID_PURGED = "some_ ...

最新文章

  1. 虚拟机下Linux安装图解之三:Linux发行版本之Red Hat 9 安装
  2. C++11中的Lambda表达式
  3. SSE,MSE,RMSE,R-square指标讲解
  4. TLS调试检测和反调试
  5. Winforn中设置ZedGraoh的GraphPane恢复到初始比例大小
  6. 展望2018,多媒体开发新趋势
  7. mysql占位符 防注入_php mysql中防注入的几个小问题,麻烦大大帮我解答谢谢
  8. JMeter扩展JMeter插件获取更多监听器
  9. JavaMVC之JSON
  10. poj3349找相同的雪花(哈希)
  11. RabbitMq之发布订阅模式
  12. 大数据在电力行业的应用前景有哪些?
  13. Hibernate的批量处理-批量更新
  14. ViewPager和Tabhost结合,可滑动的tabhost
  15. xshell教育版网盘下载
  16. Linux spi驱动框架之执行流程-nuc970-att7022
  17. 单片机STM8S测量电压电路_纸张计数测量显示装置+【2019年电赛F题国一获奖作品】...
  18. arduino编乐谱_基于Arduino的电子音乐发声器设计
  19. Qt5.9中在Pixmap图片设备上绘制线段实例
  20. 神经网络Loss损失函数总结

热门文章

  1. pycharm和python区别-python与pycharm有何区别
  2. python入门练习题-python入门练习题2
  3. python更新pip失败-新手求助, python 升级 pip 失败
  4. Git Bash命令行基础
  5. Windows和Ubuntu系统之间文件相互复制粘贴
  6. LeetCode Clone Graph(搜索问题)
  7. 网络编程学习笔记(TCP回射服务器程序修订版)
  8. 【php】(转载)分享一个好用的php违禁词 处理类
  9. shopping car 2.0
  10. webpack vue app.js自动注入页面.为app.js增加随机参数