从模拟数据到场景复现,剖析 MySQL Online DDL 异常报错之谜
本文从模拟数据到场景复现,深入浅出的分享了 MySQL 在线 DDL 变更误触报错的原因分析和解决方案,以减少或避免在Online DDL操作中误触发异常错误,提高数据库变更的成功率,希望能对遇到相似问题的客户起到一定的帮助。云掣专注于可观测运维,为您的数字化转型保驾护航~
前言
业务执行一条 alter table table_name engine=innodb 失败了很多次,一直无法执行成功,报错 ERROR 1062 (23000): Duplicate entry xxx for key 'xxx',在官方文档中也提到过,Online DDL 期间可能会出现 ERROR 1062 (23000): Duplicate entry 异常,但是没有详细说明,什么情况下会触发。业务执行 DDL 的时候,一直没有成功,意味着距离找到答案不远了。
When running an in-place online DDL operation, the thread that runs the ALTER TABLE statement applies an online log of DML operations that were run concurrently on the same table from other connection threads. When the DML operations are applied, it is possible to encounter a duplicate key entry error (ERROR 1062 (23000): Duplicate entry), even if the duplicate entry is only temporary and would be reverted by a later entry in the online log. This is similar to the idea of a foreign key constraint check in InnoDB in which constraints must hold during a transaction.
https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-limitations.html
1. 模拟现场
1.1 测试表结构
CREATE TABLE `ddl_test` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `channel_id` tinyint(4) NOT NULL, `version` bigint(20) NOT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `user_info` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `unq_index` (`user_id`,`channel_id`,`version`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
1.2 制造测试数据
mysql_random_data_load 是使用 Go 语言开发的 MySQL 随机造数工具,可从下方链接中直接下载编译完成的二进制程序。
下载地址:mysql_random_data_load GitHub
下载对应系统版本的文件,开箱即用。
# 解压后会得到一个二进制程序 tar -zxvf mysql_random_data_load_0.1.12_Linux_x86_64.tar.gz # 验证 ./mysql_random_data_load --help
造数命令模版:
mysql_random_data_load -h127.0.0.1 -u'用户' -p'密码' --max-threads=线程数 库名 表名 造数多少行
造数工具准备好了,现在需要制造一些测试数据,便于观测现象。
mysql_random_data_load -h127.0.0.1 -u'test' -p --max-threads=10 test ddl_test 2000000
1.3 场景复现
让人疑惑的是,Session 1 第二次写入并没有成功,为什么会导致 DDL 执行失败呢?
2. 原因分析
经过源码分析,发现 MySQL Online DDL 期间所有的写入都会记录到增利日志中,增量日志是通过聚簇索引(主键)来校验唯一性,只要主键不冲突后就直接写入到增量日志中,然后再依次写唯一索引、二级索引。
此时唯一索引由于违反约束,执行失败,抛出异常,增量日志这里也没有进行回滚,造成在 Online DDL 合并阶段,违反唯一索引的记录会被合并到表里,造成 DDL 执行失败。
3. 如何解决
了解到业务是一个并发写入的场景,会偶发导致重复写入的问题,定位到业务场景后,通过降低了并发,减少了重复写入。最后 DDL 执行成功。如果遇到该问题,建议先定位问题是由于哪些 SQL 造成的,然后和业务侧配合规避掉这个异常。
4. 误导报错
经过测试在 MySQL 5.7.17 版本复现该问题的时候,会出现一个误导报错。虽然是由 unq_index 约束造成的,在 DDL 会话里面,会直接报错主键的值,并且这个自增主键值不存在,因为已经被回滚了,回滚后自增主键不会重新分配了,会让用户一头雾水。
root@mysql [deom]>alter table ddl_test engine=innodb, ALGORITHM=INPLACE, LOCK=NONE; ERROR 1062 (23000): Duplicate entry '200006' for key 'PRIMARY'
后记
经过测试,该问题在最新的长期支持版本 MySQL 8.4 中依然存在。
云掣专注于可观测运维,致力解决企业上云难、用云难、管云难三大问题。基于云数据库提供7*24小时保障服务,提供开发支持、数据库体系规范、持续优化、数据库架构支持,保障企业数据库高效稳定运行。全面提升企业的运维效率和稳定性,助力企业完成云时代的数字化转型,满足客户在数据库管理和云迁移方面的多样化需求!