【问题标题】:MySQL inconsistently altering name of indexes associated with foreign keys on InnoDB tablesMySQL 不一致地更改与 InnoDB 表上的外键关联的索引名称
【发布时间】:2019-05-14 13:05:21
【问题描述】:

在删除和添加外键时,ALTER TABLE 语句的行为方式似乎不一致。有时关联的索引会被重命名,有时则不会。我已经确定了发生这种情况的情况:


方法 #1

一个简单的person 表,具有一个自动递增的主键id 和一个自身的外键列self_id。注意:两个单独的表的行为是相同的,我使用了一个表来简化示例。

CREATE TABLE `person` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `self_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `self_id_fk` (`self_id`),
  CONSTRAINT `self_id_fk` FOREIGN KEY (`self_id`) REFERENCES `person` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

接下来我通过删除现有的外键然后添加一个新的来重命名外键。这是在单个语句中完成的,但如果拆分为多个 ALTER TABLE 语句,行为是相同的。

ALTER TABLE `person`
  DROP FOREIGN KEY `self_id_fk`,
  ADD CONSTRAINT `a_new_fk_name` FOREIGN KEY (`self_id`) REFERENCES `person` (`id`);

此语句后的表格如下:

CREATE TABLE `person` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `self_id` int(11) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `self_id_fk` (`self_id`),
  CONSTRAINT `a_new_fk_name` FOREIGN KEY (`self_id`) REFERENCES `person` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

请注意,外键已重命名,但索引没有。


方法 #2

另一种方法是首先创建没有任何外键或索引的表:

CREATE TABLE `person` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `self_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

接下来添加外键约束:

ALTER TABLE `person` ADD CONSTRAINT `self_id_fk` FOREIGN KEY (`self_id`) REFERENCES `person` (`id`);

结果如下表:

CREATE TABLE `person` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `self_id` int(11) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `self_id_fk` (`self_id`),
  CONSTRAINT `self_id_fk` FOREIGN KEY (`self_id`) REFERENCES `person` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

请注意,由于行为described in the documentation,会自动创建索引

... 如果引用表自动创建索引 不存在

接下来我以与APPROACH #1相同的方式重命名外键:

ALTER TABLE `person`
  DROP FOREIGN KEY `self_id_fk`,
  ADD CONSTRAINT `a_new_fk_name` FOREIGN KEY (`self_id`) REFERENCES `person` (`id`);

但是这次外键和索引都被重命名了:

CREATE TABLE `person` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `self_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `a_new_fk_name` (`self_id`),
  CONSTRAINT `a_new_fk_name` FOREIGN KEY (`self_id`) REFERENCES `person` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

对发生的事情有任何解释吗?就好像 MySQL 正在跟踪哪些索引是“自动创建的”,然后在外键更改时重命名它们。在运行 ALTER TABLE 语句之前,两种方法的表 DDL 是相同的,因此 MySQL 必须跟踪一些“内部引擎状态”。

仅查看 DDL 无法预测运行 ALTER TABLE 语句时 MySQL 的行为方式。这意味着一旦一个简单的ALTER TABLE 语句运行,两个“架构相同”的数据库最终可能会出现架构不匹配的情况。

【问题讨论】:

  • 如果你使用 ADD FOREIGN KEY 让 MySQL 生成外键名称(例如 table_ibfk_1),事情会变得更加混乱 - 稍后添加命名外键似乎会导致隐式索引被重命名匹配新的外键,即使之前的外键仍然存在!

标签: mysql sql indexing foreign-keys innodb


【解决方案1】:

我注意到了同样的事情,但我从未见过任何解释这一点的官方文档。

我同意 InnoDB 似乎“知道”哪些索引是隐式创建的,哪些是显式创建的。但我不知道它在哪里跟踪这些信息。 InnoDB 公开了INFORMATION_SCHEMA 表中的大部分元数据,但它必须在内部数据字典中存储更多信息。

这是关于 MySQL 5.7 及更早版本中内部 DD 的唯一文档:https://dev.mysql.com/doc/refman/5.7/en/innodb-data-dictionary.html

我唯一的建议是,如果您需要索引名称是可预测的,则需要显式创建索引,然后创建外键约束。不要依赖外键隐式创建索引。

MySQL 8.0 完全重新设计了数据字典,因此您观察到的行为可能会再次发生变化。 https://dev.mysql.com/doc/refman/8.0/en/data-dictionary.html

【讨论】:

  • 您可以使用ALTER TABLE ADD INDEX 将隐式索引转换为显式索引。如果添加与现有隐式索引具有相同名称和列的显式索引,则不会出现“重复键名”错误。
猜你喜欢
  • 2015-05-12
  • 2022-01-02
  • 1970-01-01
  • 2018-10-25
  • 2011-09-17
  • 2011-11-11
  • 2018-10-02
  • 1970-01-01
相关资源
最近更新 更多