mysql 修改表唯一索引_mysql修改表结构出现唯一索引冲突

#########################

直接在主库上alter或者pt-osc操作都会报错

每次报错的value 都不一样, 添加ALGORITHM=COPY 即可搞定:

> alter TABLE ad_glc add column `number` bigint(20) not null DEFAULT 0 COMMENT 'xxx量';

ERROR1062 (23000): Duplicate entry '2020-12-02 00:00:00-12444' for key 'uk_billing'

MySQL add/drop字段时报主键冲突

问题现象

很多DBA朋友做ddl 变更比如添加、删除字段时,一定概率上会遇到如下报错:

Duplicate entry '7458421' for key 'PRIMARY'

错误提示是主键冲突,但是当我们去查询 id= 7458421 时,并无此记录。是不是很奇怪?遇到这种情况,一般有如下场景:

1 表具有一个或者多个唯一键。2 表比较大,执行DDL耗时超过数十秒。3 表的insert 操作比较频繁。

问题分析

首先我们通过一个思维导图了解一下MySQL online DDL 的过程,大家注意commit阶段,会把ddl 执行期间的记录的 log 重新应用到新的表上。

mysql 修改表唯一索引_mysql修改表结构出现唯一索引冲突_第1张图片

从官方文档中的描述所说 online ddl 期间,其他会话执行的dml操作造成唯一键冲突的sql会记录到 online log 中,在commit阶段等变更结束之后再应用这些sql会导致报错唯一键冲突。

When running an 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.

问题复现

构造一个2000w记录的表,其表结构如下

CREATE TABLE `ddl` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c1` int(10) NOT NULL DEFAULT '0', `c2` int(10) unsigned NOT NULL DEFAULT '0', `c3` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP

ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `c2` (`c2`), KEY `idx_c1` (`c1`)) ENGINE=InnoDB AUTO_INCREMENT=20000001 DEFAULT CHARSET=utf8mb4

mysql 修改表唯一索引_mysql修改表结构出现唯一索引冲突_第2张图片

举一反三 ,其实只要是会导致重复记录的sql语句,比如update,insert,insert into... on duplicate key,replace into 都会导致添加字段、删除字段的ddl变更失败。

如何解决呢 ,推荐使用 pt-osc或者 gh-ost 在线ddl变更工具

官方的讨论

官方定对于该问题是online ddl的限制,有兴趣的朋友可以阅读下面两个链接,了解官方和提交问题人员的讨论记录。

https://bugs.mysql.com/bug.php?id=76895

https://bugs.launchpad.net/percona-server/+bug/1445589

关联知识

innodb_online_alter_log_max_size 是MySQL 5.6版本引入。该参数限定了online ddl操作时使用的临时日志文件的最大大小。在创建索引或者对表进行alter操作时,该日志文件存储了DDL操作期间对表的 insert,update,delete的数据记录。临时日志文件每次以

innodb_sort_buffer_size 为单位进行扩展直至达到 innodb_online_alter_log_max_size设置的最大值。如果临时日志的大小超出规定限,则online ddl操作失败,

当前所有未提交的DML操作会回滚。该参数设置日志文件太大带来的负面影响是可能会导致DDL操作最后锁定表(Waiting for table metadata lock)的时间更长,因为要花费更长的时间应用日志到表上。所以涉及到dml比较频繁的表的ddl 尽量放到业务低峰操作。

基础材料:

centos7.5  mysql 5.7.24

online DDL是在mysql5.6版本后加入的特性,用于支持DDL执行期间DML语句的并行操作,提高数据库的吞吐量。

online DDL结构简图如下:

mysql 修改表唯一索引_mysql修改表结构出现唯一索引冲突_第3张图片

由上图可知online DDL大体可以分为3部分:

1、copy(ALGORITHM=COPY)这部分是offline的,在DDL执行期间其他DML不能并行,也是5.6版本前的DDL执行方法。其间生成临时表(server层的操作支持所有引擎),用于写入原表修改过的数据,同时在原表路径下会生成临时表的.frm和.ibd文件。在innodb中不支持使用inplace的操作都会自动使用copy方式执行,而MyISAM表只能使用copy方式。

2、inplace(ALGORITHM=INPLACE)所有操作在innodb引擎层完成,不需要经过临时表的中转。除上图两种特殊索引创建外,其他以inplace方式执行的操作都是online的,执行期间其他DML操作可以并行,其中又以是否重建表又分为两个部分rebuild和no-rebuild。

rebuild部分涉及表的重建,在原表路径下创建新的.frm和.ibd文件,消耗的IO会较多。期间(原表可以修改)会申请row log空间记录DDL执行期间的DML操作,这部分操作会在DDL提交阶段应用新的表空间中。

no-rebuild部分由于不涉及表的重建,除创建添加索引,会产生部分二级索引的写入操作外,其余操作均只修改元数据项,即只在原表路径下产生.frm文件,不会申请row log,不会消耗过多的IO,速度通常很快。

3、inplace but offline的几种特殊DDL操作,本身是按inplace方式执行,但是执行期间DML语句却不能并行。

注:如何区分DDL语句是使用了copy方式还是inplace方式,只需要查看语句执行完成输出结果中的 X rows affected,如果X为0则是inplace(online)方式,如果不为0则是copy(offline)方式。

online DDL可选参数示意图:

mysql 修改表唯一索引_mysql修改表结构出现唯一索引冲突_第4张图片

online DDL的两个子选项包括ALGORITHM和LOCK:

对于ALGORITHM参数使用default默认值即可,不需要强制指定该值,系统会自行判断,优先使用inplace,对于不支持的表或DDL操作使用copy。

LOCK参数绝大多数情况下也不需要显式指定值,默认值default已经是尽可能允许DML的并行操作了。

例句如下,参数间使用逗号隔开:

alter table innodb_test add test int,ALGORITHM=INPLACE,LOCK=DEFAULT;

inplace(rebuild)的整体执行过程如下:

准备阶段:

1、对表加元数据共享升级锁,并升级为排他锁。(此时DML不能并行)

2、在原表所在的路径下创建.frm和.ibd临时中转文件(no-rebuild除创建二级索引外只创建.frm文件,其中添加二级索引操作最为特殊,该操作属于no-rebuild不会生成.ibd,但实际上对.ibd文件却做了修改,该操作会在参数tmpdir指定路径下生成临时文件,用于存储索引排序结果,然后再合并到.ibd文件中)

3、申请row log空间,用于存放DDL执行阶段产生的DML操作。(no-rebuild不需要)

执行阶段:

1、释放排他锁,保留元数据共享升级锁(此时DML可以并行)。

2、扫描原表主键以及二级索引的所有数据页,生成 B+ 树,存储到临时文件中;

3、将所有对原表的DML操作记录在日志文件row log中

注:如果只修改元数据部分(no-rebuild)该阶段只是修改.frm文件,不需要其他操作,也不需要申请row log

提交阶段:

1、升级元数据共享升级锁,产生排他锁锁表(此时DML不能并行)。

2、重做row log中的内容。(no-rebuild不需要)

3、重命名原表文件,将临时文件改名为原表文件名,删除原表文件

4、提交事务,变更完成。

说明:在DDL期间产生的数据,会按照正常操作一样,写入原表,记redolog、undolog、binlog,并同步到从库去执行,只是额外会记录在row log中,并且写入row log的操作本身也会记录redolog,而在提交阶段才进行row log重做,此阶段会锁表,此时主库(新表空间+row log)和从库(表空间)数据是一致的,在主库DDL操作执行完成并提交,这个DDL才会写入binlog传到从库执行,在从库执行该DDL时,这个DDL对于从库本地来讲仍然是online的,也就是在从库本地直接写入数据是不会阻塞的,也会像主库一样产生row log。但是对于主库同步过来DML,此时会被阻塞,是offline的,DDL是排他锁的在复制线程中也是一样,所以不只会阻塞该表,而是后续所有从主库同步过来的操作(主要是在复制线程并行时会排他,同一时间只有他自己在执行)。所以大表的DDL操作,会造成同步延迟。

copy的整体执行过程如下:

1、锁表,期间DML不可并行执行

2、生成临时表以及临时表文件(.frm .ibd)

3、拷贝原表数据到临时表

4、重命名临时表及文件

5、删除原表及文件

6、提交事务,释放锁

online DDL的空间要求:

由于online DDL执行期间需要创建临时表空间文件用于存储数据,以及申请row log记录DML操作,所以在执行DDL前应该先确认空间上是否满足要求,否则由于空间不够很可能导致操作失败,而进行回滚。

1、row log空间:row log空间每次申请的大小由 innodb_sort_buffer_size决定,最大值由innodb_online_alter_log_max_size,该值默认为128M,支持动态修改。对于更新频繁的表来讲,如果预计在DDL期间对表的更新操作存储可能超过128M时,需要为本次操作增大该值。当然如果不涉及rebuild操作时,不需要考虑该值。如果提示DB_ONLINE_LOG_TOO_BIG错误,则是由

2、索引排序空间:如果DDL操作涉及二级索引的创建,会在MySQL临时目录产生临时排序文件,将中间的排序结果写入文件,最终将内容合并到最终表或索引中,然后自动删除临时排序文件。这个路径默认为mysql全局参数tmpdir指定(默认值为/tmp,如果手动指定了innodb_tmpdir参数的路径,则tmpdir会被覆盖),且不会在原始表的目录中创建临时排序文件。tmpdir需要保证能够容纳要创建的二级索引,临时排序文件最大可能需要的空间等于表中的数据量加上索引,否则执行将报错。(官方文档的说明,实际测试200万的表加索引,并未生成临时排序文件,这有点奇怪)

3、中间表空间:如果DDL操作涉及rebuild表,则会在原表所在目录创建临时表空间文件(以#sql开头),临时表空间大小需要等于原表大小,重建完成后会自动重命名临时表空间,删除原表空间。所以执行rebuild操作时需要保证原表所在路径下有足够空间

执行DDL语句需要额外注意的是:

如果操作失败,执行回滚操作时可能会影响服务器性能。

长时间运行的联机DDL操作可能导致复制滞后。在从服务器上运行之前,联机DDL操作必须在主服务器上完成运行。此外,在主服务器上同时处理的DML仅在从服务器上的DDL操作完成后才在从服务器上处理。

###################################

你可能感兴趣的:(mysql,修改表唯一索引)