网站如何做市场推广,怎样建立网站,wordpress 书站,wordpress 主动提交问题描述近期#xff0c;线上有个重要Mysql客户的表在从5.6升级到5.7后#xff0c;master上插入过程中出现Duplicate key的错误#xff0c;而且是在主备及RO实例上都出现。以其中一个表为例#xff0c;迁移前通过“show create table” 命令查看的auto increme…问题描述近期线上有个重要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。背景知识1Innodb引擎中的auto increment 相关参数及数据结构主要参数包括innodb_autoinc_lock_mode用于控制获取自增值的加锁方式auto_increment_increment auto_increment_offset用于控制自增列的递增的间隔和起始偏移。主要涉及的结构体包括数据字典结构体保存整个表的当前auto increment值以及保护锁事务结构体保存事务内部处理的行数handler结构体保存事务内部多行的循环迭代信息。背景知识2mysql及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_rowwrite_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语句会同步更新autoincrementupdate则不会。(2) replace into在Master上按照deleteinsert方式操作 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也能通过已有线索独立分析。