MySQL 中 AUTO_INCREMENT 的“坑” --重复值问题
MySQL · 捉虫动态· InnoDB自增列重复值问题
问题重现
先从问题入手,重现下这个 bug
use test;
drop table if exists 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)。 上面的测试反映了MySQLd重启后,InnoDB存储引擎的表自增id可能出现重复利用的情况。
自增id重复利用在某些场景下会出现问题。依然用上面的例子,假设t1有个历史表t1_history用来存t1表的历史数据,那么MySQLd重启前,ti_history中可能已经有了(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自增值
对于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则采用原有方式只存储在内存。
./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以下。
改进
新增参数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=ON, innodb_autoinc_persistent_interval=1时性能损耗在%1以下。
innodb_autoinc_persistent=ON, innodb_autoinc_persistent_interval=100时性能损耗可以忽略。
限制
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
每次都持久化没有这个问题。- 如果
innodb_autoinc_persistent=on
,频繁设置auto_increment_increment
的可能会导致持久化到聚集索引根页的值不准确。因为innodb_autoinc_persistent_interval
计算没有考虑auto_increment_incremen
t变化的情况,参看dict_table_autoinc_update_if_greater
。而设置auto_increment_increment
的情况极少,可以忽略。
注意:如果我们使用需要开启innodb_autoinc_persistent,应该在参数文件中指定
innodb_autoinc_persistent= on
如果这样指定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 中 AUTO_INCREMENT 的“坑” --重复值问题相关推荐
- MySQL中 auto_increment如何修改初始值和步长【亲测】
1.如何查看auto_increment的初始值和步长 打开黑窗口,登录管理员账号和密码后,执行以下命令: show variables like 'auto_inc%'; -- 查看当前数据库的自增 ...
- MySQL 中 AUTO_INCREMENT 的“坑”--id不连续
背景 最近在玩 MySQL 双主复制架构,表里的主键使用自增ID,为了避免两台主库生成的主键冲突,遂两台主库分别配置如下: server 1 的 my.cnf : auto_increment_inc ...
- mysql栏的范围外值,MySQL中各种字段的取值范围-数据库专栏,MySQL
mysql中各种字段的取值范围过节回来,网站更新的第一篇文章. 看来我有必要在最近找到一位志同道合的同学一起来维护站点才行了----------------------tinyint -128 – 1 ...
- MYSQL中TIMESTAMP类型的默认值
MYSQL中TIMESTAMP类型的默认值 MYSQL中TIMESTAMP类型可以设定默认值,就像其他类型一样. 1.自动UPDATE 和INSERT 到当前的时间: 表: ---------- ...
- 判断数组中是否有存在重复值
面试题: 已知有一长度为100的无序随机整型数组,且数值范围是[1,100],写一算法,判断数组中是否有存在重复值,要求,不得嵌套循环,不得使用递归. 方法一: 1public bool C ...
- java同名变量在list中添加两次_去除集合中自定义对象的重复值(对象的成员变量值都相同)...
package cn.itcast_04; import java.util.ArrayList; import java.util.Iterator; /* * 需求:去除集合中自定义对象的重复值( ...
- mysql alter auto increment_修改mysql中Auto_increment值的例子
要求: 修改mysql中某张表的下一条记录的Auto_increment值. 操作方法: 查看db.table表的下一条记录auto_increment的值: show table status fr ...
- excel函数去重_Python中实现Excel的重复值提取
本文作者:王碧琪,中南财经政法大学金融学院 本文编辑:任 哲 技术总编:张馨月 爬虫俱乐部云端课程 爬虫俱乐部于2020年暑期在线上举办的Stata与Python编程技术训练营和Stata数据分析 ...
- MySQL中查询和删除重复行
关于MySQL中的重复行 1.重复行的定义 2.单字段重复 2.1.查询重复记录 2.2.删除多余行 3.多字段重复 3.1.查询重复记录 3.2.删除多余行 4.小结 1.重复行的定义 在不同的业务 ...
最新文章
- 后台开发经典书籍--Redis深度历险:核心原理和应用实践
- 计算机中丢失 MSVCR100.dll
- python 温度 符号_Python通过小实例入门学习---1.0(温度转换)
- [密码学基础][每个信息安全博士生应该知道的52件事][Bristol52]41所有的侧信道分析都是能量分析吗
- 优酷视频手机上能发现投屏设备,但投屏失败?
- activiti报错ProcessEngines.getDefaultProcessEngine()为null
- mysql 策略_MySQL 密码策略
- 实用工具【SqlPrompt】 【Subline】 【XMind】 【PhotoShop】 【TakeColor】 【Q+】本次只讨论SqlPrompt...
- arduino无线下载
- Ghost还原的时候,显示A:GHOSTERR.TXT或CRC32错误的解决方案
- MapGis二次开发问题记录
- mysql数据恢复或数据找回方法
- matlab对多项式求导的命令,matlab多项式求导
- 2019年java全栈工程师学习大全
- HDU1175 连连看 模拟搜索
- java 实时弹幕_[Java记录]实时抓取斗鱼弹幕
- BZOJ3420: Poi2013 Triumphal arch
- 按键精灵抓取不到的问题
- WIN10输入法改为WIN7习惯,默认输入英文,程序员专用
- 《我拼图贼6》游戏反馈栏
热门文章
- kill掉占用端口程序
- 转:【总结】浏览器CSS Hacks汇总,浏览器兼容方式CSS Hacks
- HNOI2015 实验比较
- 熟悉又陌生 彪悍徐茂栋的双面人生
- mysql 8.0.18安装,mysql 8.0.18 安装配置方法图文教程
- liunx java font_Linux下JDK中文字体乱码 | 学步园
- 适应各个浏览器的iframe高度自动调整
- Java熔断框架有哪些_降级熔断框架 Hystrix 源码解析:滑动窗口统计
- 在java面向对象编程中_谈一谈你对封装_继承_多态概念的理解_Java新职篇:面向对象编程的3个原则是什么?...
- mysql storm_flume+kafka+storm+mysql架构设计