1 innodb 自增列出现重复值的问题

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

use test;
drop table t1;
create table t1(id int auto_increment, a int, primary key (id)) engine=innodb;
insert into t1 values (1,2);insert into t1 values (null,2);
insert into t1 values (null,2);
select * from t1;
+----+------+
| id | a |
+----+------+
| 1 | 2 |
| 2 | 2 |
| 3 | 2 |
+----+------+
delete from t1 where id=2;
delete from t1 where id=3;
select * from t1;
+----+------+
| id | a |
+----+------+
| 1 | 2 |
+----+------+

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

insert into t1 values (null,2);
select * FROM T1;
+----+------+
| id | a |
+----+------+
| 1 | 2 |
+----+------+
| 2 | 2 |
+----+------+

我们看到插入了(2,2),而如果我没有重启,插入同样数据我们得到的应该是(4,2);

上面的测试反映了mysql重启后,innodb存储引擎的表自增id可能出现重复利用的情况。

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

2 innodb 自增列出现重复值的原因

mysql> show create table t1\G;
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=innodb AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

建表时可以指定 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重复的原因。

3 myisam也有这个问题吗

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

4 innodb 自增列出现重复问题修复

myisam选择将AUTO_INCREMENT实时存储在.MYI文件头部中。实际上.MYI头部还会实时存其他信息,也就是说写AUTO_INCREMENT只是个顺带的操作。其性能损耗可以忽略。InnoDB 表如果要解决这个问题,有两种方法。1)将auto_increment最大值持久到frm文件中。2)将 auto_increment最大值持久到聚集索引根页trx_id所在的位置。第一种方法直接写文件性能消耗较大,这是一额外的操作,而不是以个顺带的操作。如是我们采用第二种方案。为什么选择存储在聚集索引根页页头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值存储在聚集索引根页这个性能损耗是极小的。

5 修复后的性能对比

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

./bin/sysbench --test=sysbench/tests/db/insert.lua --mysql-port=4001 --mysql-user=root \--mysql-table-engine=innodb --mysql-db=sbtest --oltp-table-size=0 --oltp-tables-count=1 \--num-threads=100 --mysql-socket=/u01/zy/sysbench/build5/run/mysql.sock  --max-time=7200 --max-requests run
set global innodb_autoinc_persistent=off;
tps: 22199 rt:2.25ms
set global innodb_autoinc_persistent=on;
tps: 22003 rt:2.27ms

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

6 改进

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

测试结果如下

  innodb_autoinc_persistent=OFF

innodb_autoinc_persistent=ON

innodb_autoinc_persistent_interval=1

innodb_autoinc_persistent=ON

innodb_autoinc_persistent_interval=10

innodb_autoinc_persistent=ON

innodb_autoinc_persistent_interval=100

TPS 22199 22003

22069

22209

RT(ms)

2.25

2.27 2.26 2.25

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

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

两个疑问:

1 对于innodb和 myisam 存储引擎,.frm中的AUTO_INCREMENT是多余的。其他存储引擎没有研究,不知道有没有用处。

2 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表竟然也有这个要求,感觉是多余的。

附:

innodb_autoinc_lock_mode 这个参数主要解决自增列主备复制问题的,用于控制自增列值连续性的。与本文无关,详细可以参考这里

innodb 自增列重复值问题的更多相关文章

  1. SQLSERVER 自增列,值突然增大1000

    SQLSERVER 自增列,值突然增大1000https://blog.csdn.net/lichxi1002/article/details/40074247  

  2. sql server 自增列,值突然增大1000的情况

    sql server 自增列,值突然增大1000的情况   解决方法: 1 打开配置管理器2左面点击sql服务3右面 右键点击SQL Server(MSSQLSERVER) 4点击 启动参数5 在参数 ...

  3. 关于前一篇innodb自增列自己的一点补充

    上篇文章是我转载的,忘记注明了出处,在这里深感歉意.但是上篇文章中关于自增列预留ID的计算我当时怎么弄明白,后来自己想了想终于想通了,在这里详细解释一下. 我们以一次性插入10行为例,表格如下:  插 ...

  4. SQL 重置自增列的值 批量处理

    Declare @IdentityTable sysname, @IdentityColumn sysname, @TotalRows int, @i int, @Iden int, @Sql var ...

  5. sql server 修改表自增列的值

    Create PROCEDURE [dbo].[SP_UpdateIdentityId] ( ) , @beforeId INT , @afterId INT ) AS BEGIN IF @befor ...

  6. SQLServer如何在批量插入后,获取批量插入的自增列的值

    解决方法如下: Use the OUTPUT functionality to grab all the INSERTED Id back into a table. 使用output 功能获取所有插 ...

  7. mysql自增列导致主键重复问题分析。。。

    前几天开发童鞋反馈一个利用load data infile命令导入数据主键冲突的问题,分析后确定这个问题可能是mysql的一个bug,这里提出来给大家分享下.以免以后有童鞋遇到类似问题百思不得其解,难 ...

  8. MySQL -- Innodb是如何处理自增列的

    对于那些向带有自增列的表中插入行的语句,Innodb提供一种可配置的锁定机制,这种锁定机制可以显著提高SQL语句的可伸缩性和性能. Innodb中为了使用自增机制,自增列必须是索引的部份,从而可以使用 ...

  9. (转)mysql自增列导致主键重复问题分析

    mysql自增列导致主键重复问题分析...  原文:http://www.cnblogs.com/cchust/p/3914935.html 前几天开发童鞋反馈一个利用load data infile ...

随机推荐

  1. ExtJS 4.2 评分组件

    上一文章是扩展ExtJS自带的Date组件.在这里将创建一个评分组件. 目录 1. 介绍 2. 示例 3. 资源下载 1. 介绍 代码参考的是 Sencha Touch 2上的一个RatingStar ...

  2. sublime常用快捷键

    自己觉得比较实用的sublime快捷键: Ctrl + /  ---------------------注释 Ctrl + 滚动 --------------字体变大/缩小 Ctrl + N----- ...

  3. AI人工智能系列随笔

    初探 AI人工智能系列随笔:syntaxnet 初探(1)

  4. [原] KVM 虚拟化原理探究 —— 目录

    KVM 虚拟化原理探究 -- 目录 标签(空格分隔): KVM KVM 虚拟化原理探究(1)- overview KVM 虚拟化原理探究(2)- QEMU启动过程 KVM 虚拟化原理探究(3)- CP ...

  5. Atitit.软件开发的三层结构isv金字塔模型

    Atitit.软件开发的三层结构isv金字塔模型 第一层,Implements 层,着重与功能的实现.. 第二次,spec层,理论层,设计规范,接口,等.流程.方法论 顶层,val层,价值观层,原则, ...

  6. Windows下MySQL无法启动

    问题描述: 从网上下了5.7 的MySQL,在bin目录下执行 start mysqld ,弹出个cmd窗口一闪就没了,也看不清是什么报错.mysqld --install安装了服务,也启动不了.   ...

  7. Linux基础介绍【第四篇】

    Linux文件和目录的属性及权限 命令: [root@oldboy ~]# ls -lhi total 40K 24973 -rw-------. 1 root root 1.1K Dec 10 16 ...

  8. phpstorm 配置 xdebug调试工具

    前言   php是脚本型程序 每次出错都要手动exit断点程序不是很方便 哪里有需求哪里就有生产,Xdebug可以实现对php的断点调试.下面将我个人的安装经历分享给大家. 运行环境 windows ...

  9. FineReport如何用JDBC连接阿里云ADS数据库

    在使用FineReport连接阿里云的ADS(AnalyticDB)数据库,很多时候在测试连接时就失败了.此时,该如何连接ADS数据库呢? 我们只需要手动将连接ads数据库需要使用到的jar放置到%F ...

  10. 解决 Error: getaddrinfo EADDRINFO 错误

    安装npm失败,提示Error: getaddrinfo EADDRINFO,原因在于虚拟机未连接互联网,悲剧.