作者:腾讯数据库技术

来源:http://r6e.cn/df8b

今年,这种情况,有时候不找好下家还真不敢跳,这不,前段时间刚跳到新东家,刚办入职那天,就遇上事了,真的是吓出一身冷汗(老大一直盯着我,说要快速解决这个问题),差点被(背)开(锅)了....

情况如何?且听我下面慢慢道来!!!希望对大家有所帮助与借鉴。

问题描述

线上有个重要Mysql客户的表在从5.6升级到5.7后,master上插入过程中出现"Duplicate key"的错误,而且是在主备及RO实例上都出现。

以其中一个表为例,迁移前通过“show create table” 命令查看的auto increment id为1758609, 迁移后变成了1758598,实际对迁移生成的新表的自增列用max求最大值为1758609。

用户采用的是Innodb引擎,而且据运维同学介绍,之前碰到过类似问题,重启即可恢复正常。

内核问题排查

由于用户反馈在5.6上访问正常,切换到5.7后就报错。因此,首先得怀疑是5.7内核出了问题,因此第一反应是从官方bug list中搜索一下是否有类似问题存在,避免重复造车。经过搜索,发现官方有1个类似的bug,这里简单介绍一下该bug。

背景知识1

Innodb引擎中的auto increment 相关参数及数据结构。

主要参数包括:innodb_autoinc_lock_mode用于控制获取自增值的加锁方式,auto_increment_increment, auto_increment_offset用于控制自增列的递增的间隔和起始偏移。

主要涉及的结构体包括:数据字典结构体,保存整个表的当前auto increment值以及保护锁;事务结构体,保存事务内部处理的行数;handler结构体,保存事务内部多行的循环迭代信息。

背景知识2

mysql及Innodb引擎中对autoincrement访问及修改的流程

(1) 数据字典结构体(dict_table_t)换入换出时对autoincrement值的保存和恢复。换出时将autoincrement保存在全局的的映射表中,然后淘汰内存中的dict_table_t。换入时通过查找全局映射表恢复到dict_table_t结构体中。相关的函数为dict_table_add_to_cache及dict_table_remove_from_cache_low。

(2) row_import, table truncate过程更新autoincrement。

(3) handler首次open的时候,会查询当前表中最大自增列的值,并用最大列的值加1来初始化表的data_dict_t结构体中的autoinc的值。

(4) insert流程。相关对autoinc修改的堆栈如下:

ha_innobase::write_row:write_row的第三步中调用handler句柄中的update_auto_increment函数更新auto increment的值。
handler::update_auto_increment: 调用Innodb接口获取一个自增值,并根据当前的auto_increment相关变量的值调整获取的自增值;同时设置当前handler要处理的下一个自增列的值。
ha_innobase::get_auto_increment:获取dict_tabel中的当前auto increment值,并根据全局参数更新下一个auto increment的值到数据字典中
ha_innobase::dict_table_autoinc_initialize:更新auto increment的值,如果指定的值比当前的值大,则更新。
handler::set_next_insert_id:设置当前事务中下一个要处理的行的自增列的值。

(5) update_row。对于”INSERT INTO t (c1,c2) VALUES(x,y) ON DUPLICATE KEY UPDATE”语句,无论唯一索引列所指向的行是否存在,都需要推进auto increment的值。相关代码如下:

if (error == DB_SUCCESS&& table->next_number_field&& new_row == table->record[0]&& thd_sql_command(m_user_thd) == SQLCOM_INSERT&& trx->duplicates)  {ulonglong    auto_inc;……auto_inc = table->next_number_field->val_int();auto_inc = innobase_next_autoinc(auto_inc, 1, increment, offset, col_max_value);error = innobase_set_max_autoinc(auto_inc);……
}

从我们的实际业务流程来看,我们的错误只可能涉及insert及update流程。

BUG 76872 / 88321: "InnoDB AUTO_INCREMENT produces same value twice"

(1) bug概述:当autoinc_lock_mode大于0,且auto_increment_increment大于1时,系统刚重启后多线程同时对表进行insert操作会产生“duplicate key”的错误。

(2) 原因分析:重启后innodb会把autoincrement的值设置为max(id) + 1。

此时,首次插入时,write_row流程会调用handler::update_auto_increment来设置autoinc相关的信息。首先通过ha_innobase::get_auto_increment获取当前的autoincrement的值(即max(id) + 1),并根据autoincrement相关参数修改下一个autoincrement的值为next_id。

当auto_increment_increment大于1时,max(id) + 1 会不大于next_id。handler::update_auto_increment获取到引擎层返回的值后为了防止有可能某些引擎计算自增值时没有考虑到当前auto increment参数,会重新根据参数计算一遍当前行的自增值,由于Innodb内部是考虑了全局参数的,因此handle层对Innodb返回的自增id算出的自增值也为next_id,即将会插入一条自增id为next_id的行。

handler层会在write_row结束的时候根据当前行的值next_id设置下一个autoincrement值。如果在write_row尚未设置表的下一个autoincrement期间,有另外一个线程也在进行插入流程,那么它获取到的自增值将也是next_id。这样就产生了重复。

(3) 解决办法:引擎内部获取自增列时考虑全局autoincrement参数,这样重启后第一个插入线程获取的自增值就不是max(id) + 1,而是next_id,然后根据next_id设置下一个autoincrement的值。由于这个过程是加锁保护的,其他线程再获取autoincrement的时候就不会获取到重复的值。

通过上述分析,这个bug仅在autoinc_lock_mode > 0 并且auto_increment_increment > 1的情况下会发生。实际线上业务对这两个参数都设置为1,因此,可以排除这个bug造成线上问题的可能性。

现场分析及复现验证

既然官方bug未能解决我们的问题,那就得自食其力,从错误现象开始分析了。

(1) 分析max id及autoincrement的规律 由于用户的表设置了ON UPDATE CURRENT_TIMESTAMP列,因此可以把所有的出错的表的max id、autoincrement及最近更新的几条记录抓取出来,看看是否有什么规律。抓取的信息如下:

乍看起来,这个错误还是很有规律的,update time这一列是最后插入或者修改的时间,结合auto increment及max id的值,现象很像是最后一批事务只更新了行的自增id,没有更新auto increment的值。

联想到【官方文档】中对auto increment用法的介绍,update操作是可以只更新自增id但不触发auto increment推进的。按照这个思路,我尝试复现了用户的现场。复现方法如下:

同时在binlog中,我们也看到有update自增列的操作。如图:

不过,由于binlog是ROW格式,我们也无法判断这是内核出问题导致了自增列的变化还是用户自己更新所致。因此我们联系了客户进行确认,结果用户很确定没有进行更新自增列的操作。

那么这些自增列到底是怎么来的呢?

(2) 分析用户的表及sql语句 继续分析,发现用户总共有三种类型的表

hz_notice_stat_sharding
hz_notice_group_stat_sharding
hz_freeze_balance_sharding

这三种表都有自增主键。

但是前面两种都出现了autoinc错误,唯独hz_freeze_balance_sharding表没有出错。难道是用户对这两种表的访问方式不一样?抓取用户的sql语句,果然,前两种表用的都是replace into操作,最后一种表用的是update操作。难道是replace into语句导致的问题?搜索官方bug, 又发现了一个疑似bug。

bug #87861: “Replace into causes master/slave have different auto_increment offset values”
原因:

(1) Mysql对于replace into实际是通过delete + insert语句实现,但是在ROW binlog格式下,会向binlog记录update类型日志。Insert语句会同步更新autoincrement,update则不会。

(2) replace into在Master上按照delete+insert方式操作, autoincrement就是正常的。基于ROW格式复制到slave后,slave机上按照update操作回放,只更新行中自增键的值,不会更新autoincrement。

因此在slave机上就会出现max(id)大于autoincrement的情况。此时在ROW模式下对于insert操作binlog记录了所有的列的值,在slave上回放时并不会重新分配自增id,因此不会报错。但是如果slave切master,遇到Insert操作就会出现”

Duplicate key”的错误。

(3) 由于用户是从5.6迁移到5.7,然后直接在5.7上进行插入操作,相当于是slave切主,因此会报错。

解决方案

业务侧的可能解决方案:

  • (1) binlog改为mixed或者statement格式。

  • (2) 用Insert on duplicate key update代替replace into。

内核侧可能解决方案:

  • (1) 在ROW格式下如果遇到replace into语句,则记录statement格式的logevent,将原始语句记录到binlog。

  • (2) 在ROW格式下将replace into语句的logevent记录为一个delete event和一个insert event。

心得

  • (1) autoincrement的autoinc_lock_mode及auto_increment_increment这两个参数变化容易导致出现重复的key,使用过程中要尽量避免动态的去修改。

  • (2) 在碰到线上的问题时,首先应该做好现场分析,明确故障发生的场景、用户的SQL语句、故障发生的范围等信息,同时要对涉及实例的配置信息、binlog甚至实例数据等做好备份以防过期丢失。

只有这样才能在找官方bug时精准的匹配场景,如果官方没有相关bug,也能通过已有线索独立分析。

猜你喜欢

1、2019 年 9 月全国程序员工资统计,你是什么水平?

2、如何才能成为优秀的架构师?

3、从零开始搭建创业公司后台技术栈

4、程序员一般可以从什么平台接私活?

5、37岁程序员被裁,120天没找到工作,无奈去小公司,结果懵了...

6、滴滴业务中台构建实践,首次曝光

7、不认命,从10年流水线工人,到谷歌上班的程序媛,一位湖南妹子的励志故事

8、15张图看懂瞎忙和高效的区别!

好险!一入职,就遇到MySQL这么大Bug!差点背锅走人~相关推荐

  1. 顶级AI科学家裴健入职京东 携手共探大数据与智慧供应链领域

    内容来源:ATYUN AI平台 今日,京东集团宣布,加拿大西蒙弗雷泽大学计算科学学院教授.统计与精算学系教授裴健博士入职京东,任京东集团副总裁. 裴健博士将负责大数据平台与产品研发部,整合京东集团整体 ...

  2. 刚入职场,应该去大公司还是创业型公司?

    马上要毕业了,拿到了大公司和创业公司的 offer,不知道应该去哪儿.我觉得在大公司各种流程体系比较完善,平台也大,但发挥空间有限:创业公司会有更多发展可能,但平台会小一些.应该去大公司还是创业型公司 ...

  3. 职场指南,如何当一个不背锅的人?

    大家好. 今天和大家探讨一个很有意思的问题,很多刚入行的同学往往会有这样的发现. 好像程序员这行做多错多,有些人整天划水摸鱼反而一点问题没有,而有些人却干着最苦最累的活,拿着普通的绩效,动不动还要为各 ...

  4. 如何通过一个SDK轻松搞定人脸识别,拯救初入职场的程序猿

    摘要:看一个SDK如何拯救初入职场的程序猿小Hi- [职场初体验] 时间过得真快,距离上次给小Hi安排"人脸识别"的开发任务(话接上期:[快速玩转华为云开发]小Hi拍了拍你,基于华 ...

  5. 知识点滴 - 新员工入职培训 New Employee Orientation (NEO)

    新员工指导NEO的介绍 新员工指导,通常由人力资源部门的会议牵头,一般包含以下方面的信息. 安全 工作环境 新的工作描述 福利和福利资格 员工的新经理和同事 公司文化 公司历史 组织结构图 其他与新员 ...

  6. 一入职!就遇到MySQL亿级大表优化....

    作者丨jia-xin 出处: https://www.cnblogs.com/YangJiaXin/p/10828244.html "前段时间刚入职一家公司,就遇到了 MySQL 亿级大表优 ...

  7. mysql中同一天入职怎么表示_ORACLE入职考试题及答案

    Oracle基础知识入职考试题 考试题说明: 这些是oracle基本知识,是后面入职培训的基础,你需要熟练掌握,否则后面的入职培训你可能听不懂,而培训只会一次,培训学不好你就会影响你以后的工作,以后的 ...

  8. mysql中日期相减_解放双手!用这3个日期函数解决入职、工龄等天数的计算

    人事部门员工的入职天数.财务部门合同的到期剩余天数等等,可以通过Excel表格来实现自动计算功能. 今天小编分享三个与日期有关的函数 一.EDATE函数 很多公司新员工入职会有3个月试用期,根据入职日 ...

  9. 数据库SQL实战-查找所有员工自入职以来的薪水涨幅情况(mysql)

    1. 查找所有员工自入职以来的薪水涨幅情况 1.1 题目描述 有一个员工表employees简况如下: 有一个薪水表salaries简况如下: 请你查找所有员工自入职以来的薪水涨幅情况,给出员工编号e ...

  10. mysql社区版安装教程,成功入职阿里

    前言 难道程序员的职业生命线是青春饭?答案是的. 35岁考虑转行,然后35岁又成了一个新人,而外国可以做到60岁,啥也不说了,可能是觉得中年大叔油腻,不及小鲜肉便宜,唉,可叹市场更新换代太快,快到我们 ...

最新文章

  1. MongoDB sharding迁移那些事(一)
  2. sql 优化之关于null 和数据类型
  3. JQuery:deferred对象的方法
  4. 一些通过SAP ABAP代码审查得出的ABAP编程代码优化建议
  5. she is so css什么意思,输入she is so什么意思 微信she is so什么梗
  6. php 实时更新内容_PHP+Redis 有序集合实现 24 小时排行榜实时更新
  7. 服务器虚拟化平台 可信云认证,100%满足规范,华为云Stack首批通过可信云虚拟化云平台最高等级认证...
  8. Linux基础——搭建自己的云计算, 多电脑共享你云端文件
  9. springSession框架来实现sso单点登陆
  10. Prometheus 架构 - 每天5分钟玩转 Docker 容器技术(83)
  11. SM2246EN+闪迪15131
  12. Google BigQuery带你走进大数据
  13. 信息收集之 绕过CDN获取真实IP地址
  14. phpcms v9摆脱手机门户,轻松搭建wap手机站
  15. java 手动触发gc_java触发full gc的几种情况整理
  16. 草地与石头模型边缘混合
  17. 2009-2010年中国十大平面设计公司排名
  18. Jmeter安装手记
  19. 企业信息化改革怎么做?
  20. 观点丨李飞飞:我们怎么教计算机理解图片

热门文章

  1. Flutter 2019 产品路线图正式公布
  2. 互联网创业的准备——数据库:硬盘iops、mysql
  3. 886C. Petya and Catacombs#墓室探险(set集合)
  4. Selecting Foreground or Background Colors
  5. SQL Server 数据库做读写分离
  6. 乔布斯:你须寻得所爱(转)
  7. 「代码随想录」322. 零钱兑换 【动态规划】力扣详解!
  8. InDesign 教程,如在使用的不同类型的框架?
  9. 如何修复最常见的 macOS 11 Big Sur 问题?
  10. iOS 审核之 Performance - 2.5.2 被拒 dlopen(), dlsym(), respondsToSelector:, performSelector