技术社群的这篇文章《为 MySQL 加字段,我竟然遇史诗级 Bug?》给我们讲解了MySQL增加字段碰到的问题,这种日常运维的操作,有些细节还是要注意,防范一些风险。

1. 发生了什么?

某天开发火急火燎找来,说是给表加字段时,出现 ERROR 1062 (23000):Duplicate entry …… key …… 报错,怀疑是 MySQL 出了问题。我当场就掏出 50m 大刀指着他:”别什么事都赖数据库头上!“

当然这只是牛马打工人的幻想,活是要干的,态度也是要诚恳的。以我的数据库运维经验来看 SO EASY~,这不妥妥就是加了个带唯一约束的字段,字段里面有重复数据导致的报错么。我正准备大肆谴责开发,字打完一半才发现逻辑有些不通,

疑点:如果是新增字段,怎么会报数据重复(又不是给字段加唯一索引)?

保险起见,我默默删除了文字,重新编辑:”亲~数据库连接信息,执行的 SQL,具体报错提供下哦~“。待我拿到了具体的信息,复现了这个奇怪的报错,有图有真相。

生产环境能给大家看的就只有这么多了,但相信对各位 “彦祖” 来说足够看出问题了:

  1. 这就是个普通的加字段操作
  2. 加的字段和报重复值的字段不一样

但对我来说,乍一看就只看到了 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 的条件及原因,我这边再给大家总结:

条件

  1. 表结构里面必须得是主键 + 唯一键。
  2. 在执行 DDL 的时候,插入和唯一键冲突的值。

原因

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

如果您认为这篇文章有些帮助,还请不吝点下文章末尾的"点赞"