一、背景
生产环境遇到一个 MySQL 写入报错的问题,业务写入数据时报主键冲突。经过调查,这套 MySQL 集群版本为 Percona 5.7.19,在报主键冲突前,做过主从切换,报主键冲突的SQL语句为 replace into,表的主键是自增列,调查该表的 auto_increment,发现一个很奇怪的问题,该表的 auto_increment 竟然比表数据的 max(id) 还小,难怪会出现主键冲突了。
那么为什么会出现 auto_increment 比 max(id) 还小?搜索一下,发现果然是 MySQL 的一个 Bug,见链接:
https://bugs.mysql.com/bug.php?id=83030
简单来说,replace into 语句通过唯一键相同修改记录时,主库上该表的 auto_increment 会变化,而从库上该表的 auto_increment 值不变。所以在从库上会出现 auto_increment 值比 max(id) 还小的情况,做主从切换之后,写入数据,就可能会发生主键冲突。
二、复现方法
另外也搜索到一篇文章,描述了同样的 Bug 和 复现方法,见链接:
https://developer.aliyun.com/article/57855
拷贝上述链接原文如下:
2.1 复现条件
- MySQL 5.7
- ROW模式
表结构:
CREATE TABLE `test` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`col_1` varchar(100) DEFAULT NULL,
`col_2` varchar(100) DEFAULT NULL,
`col_3` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `col_1` (`col_1`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
2.2 复现步骤
初始化数据:
mater:lc> REPLACE INTO test (col_1,col_2,col_3) values('a','a','a');
Query OK, 1 row affected (0.00 sec) --注意,这里是影响了1条记录
master:lc> REPLACE INTO test (col_1,col_2,col_3) values('b','b','b');
Query OK, 1 row affected (0.00 sec) --注意,这里是影响了1条记录
master:lc> REPLACE INTO test (col_1,col_2,col_3) values('c','c','c');
Query OK, 1 row affected (0.00 sec) --注意,这里是影响了1条记录
查询主库表结构,发现 AUTO_INCREMENT=4,如下:
master > show create table test
| test | CREATE TABLE `test` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`col_1` varchar(100) DEFAULT NULL,
`col_2` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `col_1` (`col_1`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 |
查询从库表结构,发现 AUTO_INCREMENT=4,如下:
slave > show create table test
| test | CREATE TABLE `test` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`col_1` varchar(100) DEFAULT NULL,
`col_2` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `col_1` (`col_1`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 |
以上都很正常,下面就是见证奇迹的时刻,在主库执行下面的SQL:
mater:lc> REPLACE INTO test (col_1,col_2) values('c','cc');
Query OK, 2 rows affected (0.00 sec) --注意,这里是影响了两条记录
然后再看主、从库表结构的 AUTO_INCREMENT 值,发现主库 AUTO_INCREMENT=5,从库 AUTO_INCREMENT=4,主从库数据完全一致,如下:
mater:lc> show create table test
| test | CREATE TABLE `test` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`col_1` varchar(100) DEFAULT NULL,
`col_2` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `col_1` (`col_1`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 |
master:lc> select * from test
+----+-------+-------+
| id | col_1 | col_2 |
+----+-------+-------+
| 1 | a | a |
| 2 | b | b |
| 4 | c | cc |
+----+-------+-------+
3 rows in set (0.00 sec)
slave:lc> show create table test
| test | CREATE TABLE `test` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`col_1` varchar(100) DEFAULT NULL,
`col_2` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `col_1` (`col_1`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 |
slave:lc> select * from test
+----+-------+-------+
| id | col_1 | col_2 |
+----+-------+-------+
| 1 | a | a |
| 2 | b | b |
| 4 | c | cc |
+----+-------+-------+
3 rows in set (0.00 sec)
在不同的MySQL版本上测试该 Bug,结果如下:
- 5.7.30 未修复
- 8.0.20 已修复
这个 Bug 很容易复现,很久之前就已经被提出来,直到现在都没有在 5.7 版本进行修复。