自学内容网 自学内容网

MySQL 在线 DDL 变更的一个异常问题

前言

业务执行一条 DDL 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. 模拟现场

创建测试表结构:

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;

制造测试数据,便于观测问题现象:

mysql_random_data_load -h127.0.0.1 -u'test' -p --max-threads=10 test ddl_test 2000000

PS:如何造数,可参考之前文章:MySQL 快速造数 mysql_random_data_load

  1. 手动插入一行记录,接下来打开两个会话窗口。
insert into ddl_test(user_id, channel_id, version,  user_info) value (19, 10, 2200, 'abc');
  1. Session 1:执行 Online DDL 变更。
alter table ddl_test engine=innodb, ALGORITHM=INPLACE, LOCK=NONE;
  1. Session 2:再次执行步骤 1 的写入,由于违反了 unq_index 约束会抛出异常:
root@mysql [test]>insert into ddl_test(user_id, channel_id, version,  user_info) value (19, 10, 2200, 'abc');
ERROR 1062 (23000): Duplicate entry '19-10-2200' for key 'unq_index'
  1. Session 1:DDL 执行到最后阶段,合并增量日志时,抛出异常,执行失败。
root@mysql [test]>alter table ddl_test engine=innodb, ALGORITHM=INPLACE, LOCK=NONE;
ERROR 1062 (23000): Duplicate entry '19-10-2200' for key 'unq_index'

这里会让人疑惑的是,在步骤 3 上面,插入并没有执行成功,为什么会导致 DDL 执行失败呢?

2. 原因推测

MySQL Online DDL 会记录增量日志,然后再最后阶段会把 DDL 期间的写入合并到表中,经过观察 DDL 总在执行一段时间后,报错,并不是重复写入失败后,DDL 立即失败的。所以推测是在合并阶段,增量写入,包含执行失败的回滚的事务,全部合并到表里,从而导致 DDL 执行失败。

3. 如何解决

了解到业务是一个并发写入的场景,会偶发导致重复写入的问题,定位到业务场景后,通过降低了并发,减少了重复写入。最后 DDL 执行成功。如果遇到该问题,建议先定位问题是由于哪些 SQL 造成的,然后和业务侧配合规避掉这个异常。个人觉得是一个 bug,并使用 8.4 版本进行了测试,发现依然执行失败。

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'

后记

后面计划有时间使用 GDB 调试一下 MySQL 找找代码里面的原因,或者如果各位知道原因可以分享在评论区。


原文地址:https://blog.csdn.net/qq_42768234/article/details/144353971

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!