MySQL непоследовательно изменяет имя индексов, связанных с внешними ключами в таблицах InnoDB - PullRequest
1 голос
/ 14 мая 2019

Кажется, я столкнулся с несоответствием поведения оператора 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;

Обратите внимание, что индекс создается автоматически из-за поведения , описанного в документации

... для ссылочной таблицы автоматически создается индекс, если он не существует

Далее я переименую внешний ключ так же, как 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, невозможно предсказать, как MySQL будет вести себя при выполнении оператора ALTER TABLE. Это означает, что две «идентичные схеме» базы данных могут получить несоответствующую схему после выполнения простого оператора ALTER TABLE.

1 Ответ

2 голосов
/ 14 мая 2019

Я заметил то же самое, но я никогда не видел официальной документации, объясняющей это.

Я согласен, кажется, что InnoDB «знает», какие индексы были созданы неявно, а какие - явно. Но я не знаю, где он отслеживает эту информацию. InnoDB предоставляет большую часть метаданных в таблицах INFORMATION_SCHEMA, но он должен хранить больше информации во внутреннем словаре данных.

Это единственная документация о внутреннем DD в MySQL 5.7 и более ранних версиях: 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

...