引言

小A正在balabala写代码呢,DBA小B突然发来了一条消息,“快看看你的用户特定信息表T,里面的主键,也就是自增id,都到16亿了,这才多久,在这样下去过不了多久主键就要超出范围了,插入就会失败,balabala......”

我记得没有这么多,最多1k多万,count了下,果然是1100万。原来运维是通过 auto_increment那个值看的,就是说,表中有大量的删除插入操作,但是我大部分情况都是更新的,怎么会这样?

问题排查

这张表是一个简单的接口服务在使用,每天大数据会统计一大批信息,然后推送给小A,小A将信息更新到数据库中,如果是新数据就插入,旧数据就更新之前的数据,对外接口就只有查询了。

很快,小A就排查了一遍自己的代码,没有删除的地方,也没有主动插入、更新id的地方,怎么会这样呢?难道是小B的原因,也不太可能,DBA那边儿管理很多表,有问题的话早爆出来了,但问题在我这里哪里也没头绪。

小A又仔细观察了这1000多万已有的数据,将插入时间、id作为主要观察字段,很快,发现了个问题,每天第一条插入的数据总是比前一天多1000多万,有时候递增的多,有时候递增的少,小A又将矛头指向了DBA小B,将问题又给小B描述了一遍。

小B问了小A,“你是是不是用了 REPLACE INTO...语句”,这是怎么回事呢,原来 REPLACE INTO...会对主键有影响。

“REPLACE INTO ...”对主键的影响

假设有一张表 t1:

如果新建这张表,执行下面的语句,最后的数据记录如何呢?

原来, REPLACE INTO...每次插入的时候如果唯一索引对应的数据已经存在,会删除原数据,然后重新插入新的数据,这也就导致id会增大,但实际预期可能是更新那条数据。

小A说:“我知道replace是这样,所有既没有用它”,但还是又排查了一遍,确实不是自己的问题,没有使用 REPLACE INTO...。

小A又双叒叕仔细的排查了一遍,还是没发现问题,就让小B查下binlog日志,看看是不是有什么奇怪的地方,查了之后还是没发现问题,确实存在跳跃的情况,但并没有实质性的问题。

下图中 @1的值对应的是自增主键 id,用 (@2,@3)作为唯一索引:

后来过了很久,小B给小A指了个方向,小A开始怀疑自己的插入更新语句

INSERT...ON DUPLICATE KEY UPDATE...了,查了许久,果然是这里除了问题。

“INSERT ... ON DUPLICATE KEY UPDATE ...”对主键的影响

这个语句跟 REPLACE INTO...类似,不过他并不会变更该条记录的主键,还是上面 t1这张表,我们执行下面的语句,执行完结果是什么呢?

没错,跟小A预想的一样,主键并没有增加,而且 name字段已经更新为想要的了,但是执行结果有条提示,引起了小A的注意:

No errors; 2 rows affected, taking 10.7ms

明明更新了一条数据,为什么这里的影响记录条数是2呢?小A,又看了下目前表中的 auto_increment:

竟然是5`,这里本应该是4的。

也就是说,上面的语句,会跟 REPLACE INTO...类似的会将自增ID加1,但实际记录没有加,这是为什么呢?

查了资料之后,小A得知,原来,mysql主键自增有个参数 innodb_autoinc_lock_mode,他有三种可能只 0, 1, 2,mysql5.1之后加入的,默认值是 1,之前的版本可以看做都是 0。

可以使用下面的语句看当前是哪种模式:

  1. select @@innodb_autoinc_lock_mode;

小A使用的数据库默认值也是1,当做简单插入(可以确定插入行数)的时候,直接将auto_increment加1,而不会去锁表,这也就提高了性能。当插入的语句类似insert into select ...这种复杂语句的时候,提前不知道插入的行数,这个时候就要要锁表(一个名为AUTO_INC的特殊表锁)了,这样auto_increment才是准确的,等待语句结束的时候才释放锁。

还有一种称为Mixed-mode inserts的插入,比如INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d'),其中一部分明确指定了自增主键值,一部分未指定,还有我们这里讨论的INSERT ... ON DUPLICATE KEY UPDATE ...也属于这种,这个时候会分析语句,然后按尽可能多的情况去分配auto_incrementid,这个要怎么理解呢,我看下面这个例子:

此时数据表下一个自增id是7:

deletefrom t1 where id in (2,3,4);

此时数据表只剩1,5,6了,自增id还是7:

这里的自增id是多少呢?

上面的例子执行完之后表的下一个自增id是10,你理解对了吗,因为最后一条执行的是一个 Mixed-mode inserts语句,innoDB会分析语句,然后分配三个id,此时下一个id就是10了,但分配的三个id并不一定都使用。此处* @总是迟到[zongshichidao] * 多谢指出,看官方文档理解错了。

模式 0的话就是不管什么情况都是加上表锁,等语句执行完成的时候在释放,如果真的添加了记录,将 auto_increment加1。

至于模式 2,什么情况都不加 AUTO_INC锁,存在安全问题,当 binlog格式设置为 Statement模式的时候,从库同步的时候,执行结果可能跟主库不一致,问题很大。因为可能有一个复杂插入,还在执行呢,另外一个插入就来了,恢复的时候是一条条来执行的,就不能重现这种并发问题,导致记录id可能对不上。

至此,id跳跃的问题算是分析完了,由于 innodb_autoinc_lock_mode值是1, INSERT...ON DUPLICATE KEY UPDATE...是简单的语句,预先就可以计算出影响的行数,所以不管是否更新,这里都将 auto_increment加1(多行的话大于1)。

如果将 innodb_autoinc_lock_mode值改为 0,再次执行 INSERT...ON DUPLICATE KEY UPDATE...的话,你会发现 auto_increment并没有增加,因为这种模式直接加了 AUTO_INC锁,执行完语句的时候释放,发现没有增加行数的话,不会增加自增id的。

“INSERT ... ON DUPLICATE KEY UPDATE ...”影响的行数是1为什么返回2?

为什么会这样呢,按理说影响行数就是1啊,看看官方文档的说明:

With ON DUPLICATE KEY UPDATE, the affected-rows value per row is 1 if the row is inserted as a new row, 2 if an existing row is updated, and 0 if an existing row is set to its current values

官方明确说明了,插入影响1行,更新影响2行,0的话就是存在且更新前后值一样。是不是很不好理解?

其实,你要这样想就好了,这是为了区分到底是插入了还是更新了,返回1表示插入成功,2表示更新成功。

解决方案

将 innodb_autoinc_lock_mode设置为0肯定可以解决问题,但这样的话,插入的并发性可能会受很大影响,因此小A自己想着DBA也不会同意。经过考虑,目前准备了两种较为可能的解决方案:

修改业务逻辑

修改业务逻辑,将 INSERT...ON DUPLICATE KEY UPDATE...语句拆开,先去查询,然后去更新,这样就可以保证主键不会不受控制的增大,但增加了复杂性,原来的一次请求可能变为两次,先查询有没有,然后去更新。

删除表的自增主键

删除自增主键,让唯一索引来做主键,这样子基本不用做什么变动,只要确定目前的自增主键没有实际的用处即可,这样的话,插入删除的时候可能会影响效率,但对于查询多的情况来说,小A比较两种之后更愿意选择后者。

结语

其实 INSERT...ON DUPLICATE KEY UPDATE...这个影响行数是2的,小A很早就发现了,只是没有保持好奇心,不以为然罢了,没有深究其中的问题,这深究就起来会带出来一大串新知识,挺好,看来小A还是要对外界保持好奇心,保持敏感,这样才会有进步。

作者:燕南飞Liam

来自:https://segmentfault.com/a/1190000017268633


推荐阅读

1. GitHub 上有什么好玩的项目?

2. 这个牛逼哄哄的数据库开源了

3. SpringSecurity + JWT 实现单点登录

4. 100 道 Linux 常见面试题

MySQL 自增 ID 超大问题查询相关推荐

  1. 技术分享 | 关于 MySQL 自增 ID 的事儿

    作者:贲绍华 爱可生研发中心工程师,负责项目的需求与维护工作.其他身份:柯基铲屎官. 本文来源:原创投稿 *爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源. 当我们使用 M ...

  2. python获取MySQL自增ID序列

    数据库insert操作时,某个字段是自增序列如ID,但是想获取这个ID的值是多少,后者把这ID的值作为后续其他sql的参数,那要怎么做呢,其实很简单,可用last_insert_id 要获取MySQL ...

  3. 45 MySQL自增id

    45 MySQL自增id 表定义自增id 说到自增id,前面提到mysql的自增id不连续,当表定义的自增值达到上限后的逻辑是:再申请下一个id时,得到的值保持不变 create table t(id ...

  4. mysql自动增长id 溢出_MySQL表自增id溢出的故障复盘怎么解决 MySQL表自增id溢出的故障复盘解决方法...

    MySQL表自增id溢出的故障复盘如何解决?本篇文章小编给大家分享一下MySQL表自增id溢出的故障复盘解决方法,小编觉得挺不错的,现在分享给大家供大家参考,有需要的小伙伴们可以来看看. 问题:MyS ...

  5. 修改 MySQL 自增ID的起始值

    修改 MySQL 自增ID的起始值 alter table users AUTO_INCREMENT=10000; posted on 2019-04-15 17:45 流易 阅读(...) 评论(. ...

  6. mysql自增id用完了_MySQL表自增id用完了该怎么办?

    我们知道MySQL表可以定义一个自增长的id,如果我们的表没有指定主键字段,那MySQL会给我们的表创建一个不可见的,长度为6个自己的row_id,然后不停地往上加步长,虽然生活中自然数是没有上限的, ...

  7. 有关于mysql自增型需要返回id_关于mysql自增id,你需要知道的

    关于mysql自增id,你需要知道的,主键,重启,索引,类型,字段 关于mysql自增id,你需要知道的 易采站长站,站长之家为您整理了关于mysql自增id,你需要知道的的相关内容. 导读:在使用M ...

  8. mysql自增id用完了_MySQL 自增 ID 用完了怎么办?

    MySQL 自增 ID 用完了怎么办? 在MySQL中有很多类型的自增ID,每个自增ID都设置了初始值,然后按照一定的步长增加,只要定义了字节长度,那么就会有上限,如果达到上限再次添加,则会报主键冲突 ...

  9. mysql自增id跳跃增长不连续,auto_increment_increment的问题

    问题: 发现测试服务器上mysql自增id不连续,以8的倍数跳跃,像这样8,16,24,32... 查找网上资料大多说的都是起始id不是从1开始,或者删除后再插入id不连续... 我的情况是,id不连 ...

最新文章

  1. K近邻算法(KNN)原理小结
  2. 纯Rust编写的机器学习框架Neuronika,速度堪比PyTorch
  3. telnetd运行需要什么条件_申请日本研究生需要什么条件
  4. 解决matplotlib库在PyCharm和命令行都无法正常显示问题
  5. 深入研究 C++中的 STL Deque 容器
  6. 20155213 2016-2017-2《Java程序设计》课程总结
  7. 【Python】【数据库】
  8. 无法正常进入Windows也能开启Windows内核调试
  9. python实现逻辑回归算法
  10. Linux系统基于MobaXterm的下载及使用
  11. 无奇不有,20款国外便携式智能手机充电器
  12. Xcode8 解决注释以及VVDocumenter无法使用问题
  13. 微商怎么推广引流?学会玩豆瓣让精准流量源源不断
  14. 双十一来临,仓储物流快递安全保障解决方案
  15. SaaS模式、技术与案例详解——第12章 数据存储
  16. 产品周报第29期|创作中心优化:发文助手新增质量分检测功能,博文增加内容历史版本
  17. 将BMP 格式图片转换为 JPEG 格式【c语言】
  18. java 中free,FreeJava 的使用方法(三)FreeJava的使用,freejava使用方法
  19. android脚本 附近的人,前天微信上有个附近的人加我,我发现她是个脚本
  20. 独家 | 合成资产平台Synthetix:鲜为人知,却身处DeFi中心

热门文章

  1. python编码声明问题
  2. Playbook的写法讲解
  3. el-table合计行
  4. AI首席架构师6-AICA-从数据到知识-百度知识图谱技术及应用
  5. Android核心功能
  6. 22、R329刷机受阻和测试仿真环境demo
  7. 家族关系查询系统程序设计算法思路_家族关系查询系统
  8. 灵机一栋团队alpha冲刺 Ⅳ
  9. DirectX12_API流程入门篇
  10. 最优雅高效的 19 条工作习惯 · 职场亮剑