作者:天勿雨,DBA 工程师。
爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。
本文约 1200 字,预计阅读需要 3 分钟。
1. 发生了什么?
某天开发火急火燎找来,说是给表加字段时,出现 ERROR 1062 (23000):Duplicate entry …… key ……
报错,怀疑是 MySQL 出了问题。我当场就掏出 50m 大刀指着他:”别什么事都赖数据库头上!“
当然这只是牛马打工人的幻想,活是要干的,态度也是要诚恳的。以我的数据库运维经验来看 SO EASY~,这不妥妥就是加了个带唯一约束的字段,字段里面有重复数据导致的报错么。我正准备大肆谴责开发,字打完一半才发现逻辑有些不通,
疑点:如果是新增字段,怎么会报数据重复(又不是给字段加唯一索引)?
保险起见,我默默删除了文字,重新编辑:”亲~数据库连接信息,执行的 SQL,具体报错提供下哦~“。待我拿到了具体的信息,复现了这个奇怪的报错,有图有真相。
生产环境能给大家看的就只有这么多了,但相信对各位 “彦祖” 来说足够看出问题了:
但对我来说,乍一看就只看到了 duplicate key,第一反应就是看该字段是否有重复值。排查后,发现这个值是唯一的……
众人看我陷入了思考,以为我已经有了解决方法,只是在看使用哪种更加简单有效,殊不知我已经把离职报告写好了。我一个刚入门的懂什么数据库啊?
玩笑归玩笑,言归正传,遇事不决,掏出新手的三板斧:CSDN,AI,Google。
2. 原因是什么?
摸索一番后,总算找到了原因,首先我们得了解下 Online DDL 的大致原理:
MySQL 在执行 DDL 期间,会将发生的 DML 存储到临时日志文件里,这个临时日志文件大小由配置参数innodb_online_alter_log_max_size[1] 指定。等 DDL 执行完后,再把 DML 应用到对应的表上。
随后我又找到了对应的 Bug 记录[2]。官方解释这并不算是 Bug,而是 Online DDL 的特性。
简单说:MySQL 在执行 DDL 时,如果有两条 DML,且插入了同样的数据,就可能会有
duplicate entry 报错。
3. 把问题复现一下
看来是我的无知误会了MySQL,虽然官网解释的很清楚,但咱们还是得来验证下。
3.1 生成如下表结构
必须得要有主键和唯一键。
mysql> show create table sbtest1\G
*************************** 1.row ***************************
Table: sbtest1
Create Table: CREATE TABLE`sbtest1` (
`id`int(11) NOT
NULL AUTO_INCREMENT,
`k`int(11) NOT NULL DEFAULT '0',
`c`char(120) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`pad`char(60) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUEKEY`pad` (`pad`),
KEY`k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=5000004 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
1 row in set (0.00 sec)
3.2 查看表数据
mysql> select * from sbtest1 limit 2;
+----+---------+-------------------------------------------------------------------------------------------------------------------------+-----+
| id | k | c | pad |
+----+---------+-------------------------------------------------------------------------------------------------------------------------+-----+
| 1 | 2507307 | 68487932199-96439406143-93774651418-41631865787-96406072701-20604855487-25459966574-28203206787-41238978918-19503783441 | h |
| 2 | 2512400 | 13241531885-45658403807-79170748828-69419634012-13605813761-77983377181-01582588137-21344716829-87370944992-02457486289 | g |
+----+---------+-------------------------------------------------------------------------------------------------------------------------+-----+
3.3 添加字段
mysql> alter table sbtest1 add column name varchar(20);
3.4 插入数据,制造唯一键冲突报错
mysql> insert into sbtest1(k,pad) values(1,'h');
ERROR 1062 (23000): Duplicate entry 'h' for key 'pad'
3.5 随后添加字段的 DDL 报错
mysql> alter table sbtest1 add column name varchar(20);
ERROR 1062 (23000): Duplicate entry 'h' for key 'pad'
3.6 发生疑惑
令人疑惑的是,如果主键冲突,却不会导致 DDL 报错。如果只存在唯一键而无主键,也不会导致 DDL 报错。
# 新增字段
mysql> alter table sbtest1 add column home varchar(20);
# 插入数据
mysql> insert into sbtest1(id,k) values(10000001,2) on duplicate key updateid=1;
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'
# 新增字段不会报错
mysql> alter table sbtest1 add column home varchar(20);
Query OK, 0 rows affected (1 min 2.57 sec)
Records: 0 Duplicates: 0 Warnings: 0
4. 总结
从上文大家可知,MySQL 执行 DDL 报 duplicate entry 的条件及原因,我这边再给大家总结:
条件
原因
MySQL Online DDL 的特性,在执行 DDL 时,MySQL 会把 DML 存储在日志中,等 DDL 执行完后再应用 DML,如果多条 DML 会产生数据冲突,在 DDL 完成后,应用 DML 时,理所当然会出现 duplicate entry 的错误了。
6. 最后的疑问
为什么一定得是主键 + 唯一键的表结构,唯一键冲突才会导致这个问题?
欢迎在文末留言,期待看到你的答案。
[1] innodb_online_alter_log_max_size: https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_online_alter_log_max_size
[2] bug#76895: https://bugs.mysql.com/bug.php?id=76895
✨ Github:https://github.com/actiontech/sqle
📚 文档:https://actiontech.github.io/sqle-docs/
💻 官网:https://opensource.actionsky.com/sqle/
👥 微信群:请添加小助手加入 ActionOpenSource
🔗 商业支持:https://www.actionsky.com/sqle
-