为什么 MySQL 会标记不存在的重复键?

Rya*_*yan 5 mysql innodb duplication unique-constraint mysql-5.7

我正在auto-increment向大表中添加一个字段。

该字段已填充(大约 1-500M)并且新插入的内容正在正确地手动递增。为了验证,我运行了一个查询以显示任何重复项:

mysql> SELECT new_id, COUNT(*) AS count FROM my_table GROUP BY new_id HAVING count > 1;
#      No Results
Run Code Online (Sandbox Code Playgroud)

当我手动查看最新记录时,我会看到看起来像自动递增的BIGINT(20)字段。NULL该列中不存在 's 。

然而,当我尝试将索引更改为 a UNIQUE INDEX(从非唯一索引,但具有唯一值)时,它会触发DUPLICATE KEY错误。并且重复键始终为MAX(new_id)+1,因此有问题的重复项的aSELECT始终不返回任何结果。

这是我正在运行的内容:

mysql> SELECT MAX(new_id) FROM my_table;
+-------------+
| MAX(new_id) |
|   512345678 |
+-------------+
1 row in set (0.00 sec)

mysql> ALTER TABLE my_table
        DROP INDEX `idx_new_id`,
        ADD UNIQUE INDEX `idx_new_id ` USING BTREE (`new_id `);

ERROR 1062 (23000): Duplicate entry '512345679' for key 'idx_new_id'
Run Code Online (Sandbox Code Playgroud)

但为什么?这个记录根本不存在!MAX少1比这和因为查询之前并没有改变。所以我证明...

mysql> SELECT MAX(new_id) FROM my_table;

+-------------+
| MAX(new_id) |
|   512345678 |
+-------------+
1 row in set (0.00 sec)

mysql> SELECT * FROM my_table WHERE new_id = 512345679;
# No results
Run Code Online (Sandbox Code Playgroud)

512345679如果记录512345679甚至不存在,为什么 MySQL 会标记为重复?

我至今未果复制问题从头开始。(数据库小提琴

你能指出我接下来要看的地方吗?

最终,我打算将主键从另一个字段更改为这个字段。但是我试图通过预先填充唯一 ID(已经完成)来防止停机。如果我在这一步成功,我会将这个new_id字段切换到PRIMARY KEYAUTOINCREMENT它。


这是SHOW CREATEfor my_table

CREATE TABLE `my_table` (
  `new_id` bigint(20) NOT NULL,
  `id` bigint(20) NOT NULL,
  `username` varchar(80) NOT NULL,
  `name` varchar(100) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_name` (`name`),
  KEY `idx_username` (`username `),
  KEY `idx_new_id` (`new_id`) USING BTREE,
  KEY `idx_id` (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Run Code Online (Sandbox Code Playgroud)

Rol*_*DBA 1

我尝试在 MacOS 的 MySQL 5.7.12 中运行你的代码。我似乎无法复制这个,但我发现了一些有趣的东西。

SQL-Fiddle 有 MySQL 5.7.17。MySQL 5.7.13 中出现错误。这些版本之间是否修复了有关删除和添加二级索引的问题?我相信是的。请注意5.7.14 的发行说明,小标题下的要点 21Bugs Fixed

InnoDB:在回滚删除并添加辅助索引的 ALTER TABLE 操作期间引发断言。(错误#22005726)

问:什么是二级索引?根据MySQL 文档,Clustered and Secondary Indexes标题为“二级索引如何与聚集索引相关”:

除聚集索引外的所有索引都称为二级索引。在 InnoDB 中,辅助索引中的每条记录都包含该行的主键列,以及为辅助索引指定的列。InnoDB 使用此主键值来搜索聚集索引中的行。

这表明aUNIQUE INDEX只是一个二级索引。二级索引会将主键的副本附加到每个索引条目。

我的断言是,当涉及到同时在同一列上删除和创建索引时,5.7.13 中引入了一个 5.7.12 中不存在的错误。

回头看看你的ALTER TABLE命令

mysql> ALTER TABLE my_table
        DROP INDEX `idx_new_id`,
        ADD UNIQUE INDEX `idx_new_id` USING BTREE (`new_id `);
Run Code Online (Sandbox Code Playgroud)

您删除了具有特定索引名称 ( idx_new_id) 和特定列列表(仅一列new_id)的索引,然后添加一个具有相同名称和相同列列表的新索引,但将其设为索引UNIQUE。最终效果是索引从未真正被删除,并且UNIQUE属性只是附加到已经存在的非唯一索引。然后,在幕后,mysqld 尝试将数据加载到已填充的表和仍填充的索引中,从而创建重复键错误。请注意,我只是断言这一点。我必须查看 5.7.13 的源代码,看看这是否确实发生。

尽管如此,5.7.12 和 5.7.14 无法重现此问题。这表明这个奇怪的情况在制作 5.7.14 时已得到解决。

根据我的所有猜测,以下是我的建议:

建议

建议#1:升级到 5.7.14 或更高版本

建议#2:更改ALTER TABLE为使用不同的索引名称

mysql> ALTER TABLE my_table
       DROP INDEX `idx_new_id`,
       ADD UNIQUE INDEX `uniq_idx_new_id` USING BTREE (`new_id`);
Run Code Online (Sandbox Code Playgroud)

建议#3:执行 2 个ALTER TABLE命令

mysql> ALTER TABLE my_table DROP INDEX `idx_new_id`;
mysql> ALTER TABLE my_table ADD UNIQUE INDEX `uniq_idx_new_id` USING BTREE (`new_id`);
Run Code Online (Sandbox Code Playgroud)

试一试 !!!