社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  DATABASE

为 MySQL 加字段,我竟然遇史诗级 Bug?

脚本之家 • 昨天 • 16 次点击  
 脚本之家 设为“星标
第一时间收到文章更新
图片

作者:天勿雨,DBA 工程师。

爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。

本文约 1200 字,预计阅读需要 3 分钟。


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 TableCREATE TABLE`sbtest1` (
`id`int(11NOT  NULL AUTO_INCREMENT,
`k`int(11NOT NULL DEFAULT '0',
`c`char(120COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`pad`char(60COLLATE 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
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,padvalues(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,2on 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


✨ Github:https://github.com/actiontech/sqle

📚 文档:https://actiontech.github.io/sqle-docs/

💻 官网:https://opensource.actionsky.com/sqle/

👥 微信群:请添加小助手加入 ActionOpenSource

🔗 商业支持:https://www.actionsky.com/sqle

图片
  推荐阅读:
  1. 几道实用的 MySQL 面试题( eg:如何查找重复记录)
  2. 这真的不是八股!经典 MySQL 大数据量查询分页问题
  3. 得物面试:MySQL 深度分页如何优化?
  4. 故障分析 | MySQL 无法修改主键?原来是因为这个参数
  5. MySQL 正式引入 JavaScript 支持!

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/183303
 
16 次点击