Ошибки внешних ключей: errno 150, MySQL error 1005 и другие
Дата: 2.12.2016
Автор: Василий Лукьянчиков , vl (at) sqlinfo (dot) ru
Функционирование внешних ключей в MySQL имеет много нюансов и ограничений из-за чего существует немало возможностей получить ошибку при работе с ними. Одна из проблем состоит в том, что сообщения об ошибках содержат мало полезной информации и не указывают причину возникновения ошибки. В данной статье дается объяснение как получить дополнительную информацию об ошибке и приведен полный список причин возникновения ошибок внешних ключей. Каждая причина снабжена уникальным буквенно-цифровым кодом (А4, Б1, ..), использующимся в сводной таблице в конце статьи, которая поможет вам быстро диагностировать проблему.
Внешний ключ - это поле (или набор полей) в таблице, называемой дочерней, которое ссылается на поле (или набор полей) в таблице, называемой родительской. Дочерняя и родительская таблицы могут совпадать, т.е. таблица будет ссылаться на саму себя. Внешние ключи позволяют связать записи в двух таблицах по определенным полям так, что при обновлении поля в родительской автоматически происходит изменение записи в дочерней таблице.
В MySQL внешние ключи не реализованы на уровне сервера, их поддержка зависит от используемого хранилища данных. Содержание статьи справедливо для InnoDB (в том числе и для XtraDB).
Как получить больше данных об ошибке
После получения ошибки выполните SHOW ENGINE INNODB STATUS и смотрите содержимое секции LATEST FOREIGN KEY ERROR. Этот способ имеет следующие недостатки:
требует привилегии SUPER
содержит информацию о последней ошибке, связанной с внешними ключами, из-за чего нужно выполнять SHOW ENGINE INNODB STATUS сразу после возникновения ошибки, что не всегда удобно/возможно
используются внутренние имена таблиц (например, 'test.#sql-d88_b'), что затрудняет диагностику
порой содержит мало полезной информации или таковая вообще отсутствует.
Альтернатива: использовать MariaDB версий больше 5.5.45 и 10.0.21, в которых сообщения об ошибках значительно улучшены и указывают причину возникновения ошибки.
Errno 150
Если в сообщении об ошибке содержится errno 150 (или errno 121), значит парсер MySQL не смог распознать ошибку и передал команду (create/alter) на выполнение в InnoDB. В этом разделе перечислены ситуации, приводящие к ошибкам, содержащим errno 150.
А1. Нет индекса в родительской таблице. Набор полей, на которые ссылается дочерняя таблица, должен быть проиндексирован (или являться левой частью другого индекса). Порядок полей в индексе должен быть таким же как в определении внешнего ключа. Сюда же относится случай отсутствия нужной колонки в родительской таблице (нет колонки, нет и индекса).
Неочевидный момент: на колонке родительской таблицы есть индекс - полнотекстовый (fulltext). Но внешний ключ всё равно не создается и сервер ругается на отсутствие индекса. Это происходит потому, что индекс должен быть обычным (btree).
Другой неочевидный момент: на колонке родительской таблицы есть индекс - префиксный. Но внешний ключ всё равно не создается и сервер ругается на отсутствие индекса. Это происходит потому, что индекс должен быть определен на всей длине колонки.
Строго говоря, поля в дочерней таблице тоже должны быть проиндексированы, но если нет подходящего индекса, MySQL автоматически его создаст при добавлении внешнего ключа (в совсем уж древних версиях требовалось предварительное создание индекса).
create table t1 (a int, b int, index(a))engine=innodb;
SHOWENGINEINNODB STATUS; ------------------------
LATEST FOREIGNKEY ERROR ------------------------ 2016-11-1606:37:39 0x14c1c Error inforeignkeyconstraint of table test/t2: foreignkey(a)references t1(b))engine=innodb:
Cannot find an indexin the referenced tablewhere the
referenced columns appear as the firstcolumns, orcolumntypes in the tableand the referenced tabledonotmatch for constraint.
Note that the internal storage type of ENUMandSET changed in
tables created with >= InnoDB-4.1.12, and such columnsin old tables
cannot be referenced by such columnsin new tables.
Please refer to http://dev.mysql.com/doc/refman/5.7/en/innodb-foreign-key-constr
aints.html for correct foreignkey definition. ------------
-- при использовании оператора ALTER ошибка и секция -- LATEST FOREIGN KEY ERROR будут содержать внутреннее имя таблицы test.#sql-a64_1
create table t2 (a int)engine=innodb; alter table t2 addforeignkey(a)references t1(a), addforeignkey(a)references t1(b);
ERROR 1005(HY000): Cannot create table'test.#sql-a64_1'(errno: 150)
-- в новых версиях парсер MySQL определяет некорректность -- конструкции и возвращает другую ошибку (без errno 150)
drop table t2; create table t2 (a int, foreignkey(a)references t1(a), foreignkey(a)references t1(b))engine=innodb;
ERROR 1215(HY000): Cannot addforeignkeyconstraint
Обратите внимание, если внешний ключ уже существует и в результате изменений (alter table) возникает ситуация отсутствия индекса в родительской таблице, то код ошибки будет 1025:
create table t1 (a int, b int, index(a))engine=innodb; create table t2 (a int, foreignkey(a)references t1(a))engine=innodb;
alter table t1 drop a;
ERROR 1025(HY000): Error onrename of '.\test\#sql-d6c_5'to'.\test\t1'(errno: 150)
showengineinnodb status; ------------------------
LATEST FOREIGNKEY ERROR ------------------------ 1612207:14:25 Error inforeignkeyconstraint of table test/t2:
there is no indexin referenced table which would contain
the columnsas the firstcolumns, or the datatypesin the
referenced tabledonotmatch the ones intable. Constraint:
, CONSTRAINT"t2_ibfk_1"FOREIGNKEY("a")REFERENCES"t1"("a")
The indexin the foreignkeyintableis"a"
See http://dev.mysql.com/doc/refman/5.5/en/innodb-foreign-key-constraints.html
for correct foreignkey definition. InnoDB: Renaming table `test`.`#sql-d6c_5` to `test`.`t1` failed! --------
А2. Родительская таблица не найдена в словаре данных InnoDB. Это означает, что родительская таблица должна существовать и быть постоянной InnoDB таблицей. Не временной InnoDB таблицей, так как информация о временных таблицах не сохраняется в словаре данных InnoDB. И уж тем более не представлением.
mysql> create table t1 (a int, index(a))engine=myisam;
-- в старых версиях будет ошибка вида
ERROR 1005(HY000): Cannott create table'test.t2'(errno: 150)
showengineinnodb status; ------------------------
LATEST FOREIGNKEY ERROR ------------------------ 2016-11-1716:30:09 0x364c Error inforeignkeyconstraint of table world/t2: foreignkey(a)references t1(a))engine=innodb:
Cannot resolve table name close to: (a))engine=innodb ------------
А3. Синтаксическая ошибка. Внешние ключи реализованы на уровне хранилища, и в старых версиях парсер сервера MySQL не распознавал синтаксические ошибки внешних ключей, из-за чего их было трудно идентифицировать.
Например, в определении внешнего ключа количество столбцов дочерней таблицы не совпадает с количеством столбцов родительской таблицы:
-- понять, что причина в синтаксической ошибке -- можно только из:
showengineinnodb status; ------------------------
LATEST FOREIGNKEY ERROR ------------------------ 16060522:28:23 Error inforeignkeyconstraint of table test/#sql-d88_b: foreignkey(id,b)references t1(id):
Syntax error close to:
-- в новых версиях парсер распознает синтаксическую ошибку -- и сообщает об этом:
ERROR 1239(42000): Incorrect foreignkey definition for 'foreign key without name': Key reference andtable reference don't match
Другой пример: попробуем создать внешний ключ на поле типа text:
create table t1 (a text , index(a(50)))engine=innodb;
create table t2 (a text, foreignkey(a)references t1(a))engine=innodb;
ERROR 1170(42000): BLOB/TEXTcolumn'a' used inkey specification without a keylength
-- MySQL автоматически пытается создать индекс на колонке `a`, и -- сообщает, что нельзя создать индекс по всей длине поля типа text. -- Хорошо, укажем префикс и получим errno 150:
-- понять, что произошла ошибка синтаксиса можно: -- или через show engine innodb status; -- или внимательно сравнить разрешенный синтаксис в документации -- с написанной командой.
А4. Несовпадение типов данных. Столбцы дочерней таблицы, входящие в определение внешнего ключа, должны иметь такие же типы данных, что и столбцы родительской таблицы, на которые они ссылаются, вплоть до атрибутов: знак и кодировка/сопоставление.
-- например, если у одной колонки мы определим -- атрибут unsigned, а у другой нет, то: create table t1 (a intunsigned, index(a))engine=innodb;
-- в старых версиях будет ошибка вида
ERROR 1005(HY000): Cannott create table'test.t2'(errno: 150)
showengineinnodb status; ------------------------
LATEST FOREIGNKEY ERROR ------------------------ 2016-11-2603:00:47 0x10894 Error inforeignkeyconstraint of table world/t2: foreignkey(a)references t1(a))engine=innodb:
Cannot find an indexin the referenced tablewhere the
referenced columns appear as the firstcolumns, orcolumntypes in the tableand the referenced tabledonotmatch for constraint.
Note that the internal storage type of ENUMandSET changed in
tables created with >= InnoDB-4.1.12, and such columnsin old tables
cannot be referenced by such columnsin new tables.
Please refer to http://dev.mysql.com/doc/refman/5.7/en/innodb-foreign-key-constr
aints.html for correct foreignkey definition. ------------
Если несоответствие типов данных возникает во время изменения таблицы при уже существующем внешнем ключе, то ошибка будет иметь вид:
create table t1 (a int, index(a))engine=innodb; create table t2 (a int, foreignkey(a)references t1(a))engine=innodb;
MariaDB [test]> alter table t1 modify a intunsigned;
ERROR 1025(HY000): Error onrename of '.\test\#sql-d6c_6'to'.\test\t1'(errno: 150)
А5. Некорректно задано действие внешнего ключа. Если в определении внешнего ключа указано ON UPDATE SET NULL и/или ON DELETE SET NULL, то соответствующие столбцы дочерней таблицы не должны быть определены как NOT NULL.
create table t1 (a intnot null, index(a))engine=innodb;
-- в старых версиях будет:
ERROR 1005(HY000): Cannot create table'test.t2'(errno: 150)
showengineinnodb status; ------------------------
LATEST FOREIGNKEY ERROR ------------------------ 2016-11-2606:24:42 0x10894 Error inforeignkeyconstraint of table world/t2: foreignkey(a)references t1(a)ondeletesetnull)engine=innodb:
You have defined a SETNULL condition though some of the columns are defined asNOT NULL. ------------
Если коллизия возникает при уже существующем внешнем ключе, то:
create table t1 (a intnot null, index(a))engine=innodb; create table t2 (a int, foreignkey(a)references t1(a)ondeletesetnull)engine=innodb;
alter table t2 modify a intnot null;
ERROR 1025(HY000): Error onrename of '.\test\#sql-d6c_6'to'.\test\t2'(errno: 150)
А6. Дочерняя таблица является временной InnoDB таблицей. Внешние ключи можно создавать только в постоянной, несекционированной InnoDB таблице.
showengineinnodb status; ------------------------
LATEST FOREIGNKEY ERROR ------------------------ 1611304:22:26 Error inforeignkeyconstraint of table temp/#sql318_4_1: foreignkey(a)references t1(a))engine=innodb:
Cannot resolve table name close to: (a))engine=innodb --------
-- в новых версиях ошибка будет иметь вид:
ERROR 1215(HY000): Cannot addforeignkeyconstraint
А7. Родительская таблица является секционированной таблицей. На данный момент (MySQL 5.7 и MariaDB 10.1) внешние ключи не поддерживаются для секционированных таблиц (partitioned tables). Иными словами, ни родительская, ни дочерняя таблица не должны иметь секции. В случае, когда внешний ключ ссылается на секционированную таблицу диагностика ошибки затруднена ошибкой вывода show engine innodb status:
create table t1 (a int, index(a)) partition by range (a) (partition p0 values less than (10),
partition p1 values less than (20),
partition p2 values less than maxvalue);
showengineinnodb status; ------------------------
LATEST FOREIGNKEY ERROR ------------------------ 16122319:38:14 Error inforeignkeyconstraint of table test/t2: foreignkey(a)references t1(a))engine=innodb:
Cannot resolve table name close to: (a))engine=innodb -------- -- сообщение указывает на то, что родительская таблица -- не найдена в словаре данных innodb (bug: 84331)
Если разбивать на секции родительскую таблицу после создания внешнего ключа, то
create table t1 (a int, index(a))engine=innodb; create table t2 (a int, foreignkey(a)references t1(a))engine=innodb;
alter table t1 PARTITION BYHASH(a) PARTITIONS 8;
ERROR 1217(23000): Cannot deleteorupdate a parent row: a foreignkeyconstraint fails
showengineinnodb status; -- не содержит секцию LATEST FOREIGN KEY ERROR
Errno 121
Такой результат возникает только в одном случае.
Б1. Неуникальное имя ограничения. Обратите внимание: речь не о имени внешнего ключа. Если при создании внешнего ключа вы указываете не обязательное ключевое слово CONSTRAINT, то идущий после него идентификатор должен быть уникальным в пределах базы данных.
create table t1 (a int, index(a))engine=innodb;
create table t2 (a int, CONSTRAINT q1 foreignkey(a)references t1(a))engine=innodb;
create table t3 (a int, CONSTRAINT q1 foreignkey(a)references t1(a))engine=innodb;
ERROR 1005(HY000): Cannot create table'test.t3'(errno: 121)
-- в 5.7 будет другая ошибка
ERROR 1022(23000): Cannot write; duplicate keyintable't3'
showengineinnodb status; ------------------------
LATEST FOREIGNKEY ERROR ------------------------ 1611303:31:11 Error inforeignkeyconstraint creation for table `test`.`t3`.
A foreignkeyconstraint of name `test`.`q1`
already exists. (Note that internally InnoDB adds 'databasename' in front of the user-defined constraint name.)
Note that InnoDBFOREIGNKEY system tables store constraint names as case-insensitive, with the
MySQL standard latin1_swedish_ci collation. If you create tables ordatabases whose names differ only in
the character case, then collisions inconstraint
names can occur. Workaround: name your constraints
explicitly withunique names. --------
Нет ошибок
Внешний ключ не создается, и нет никаких ошибок. Это может происходить по следующим причинам:
В1. Дочерняя таблица не является InnoDB таблицей. В этом случае для совместимости с другими субд парсер MySQL просто проигнорирует конструкцию внешнего ключа.
В2. Не соответствует синтаксису MySQL. Стандарт SQL разрешает указывать внешний ключ сразу при объявлении колонки с помощью конструкции REFERENCES (например, ... a int references t1(a), ...), однако MySQL игнорирует такую форму записи. Единственный способ создать в нем внешний ключ - это использовать отдельный блок FOREIGN KEY:
В этой части собраны ошибки, которые возникают из-за нарушения ссылочной целостности, т.е. наличие в дочерней таблице записей, которым нет соответствия в родительской таблице.
Г1. Удаление родительской таблицы. Нельзя удалить родительскую таблицу при наличии внешнего ключа.
create table t1 (a int, index(a))engine=innodb; create table t2 (a int, foreignkey(a)references t1(a))engine=innodb;
drop table t1;
ERROR 1217(23000): Cannot deleteorupdate a parent row: a foreignkeyconstraint fails
Удаление следует понимать в расширенном варианте как удаление из множества InnoDB таблиц. Например, если мы сменим (alter table) движок родительской таблицы на MyISAM, то с точки зрения ограничения внешнего ключа родительская таблица перестанет существовать (т.к. она должна быть постоянной innodb таблицей):
alter table t1 engine=myisam;
ERROR 1217(23000): Cannot deleteorupdate a parent row: a foreignkeyconstraint fails
Сначала нужно удалить внешний ключ (или всю дочернюю таблицу, что удалит в том числе и внешний ключ). Если вы не знаете какие таблицы являются дочерними для заданной таблицы, то это можно определить через запрос к information_schema:
select table_name from information_schema.key_column_usage where table_schema = "test"and references_table_name = "t1";
Г2. Изменение данных в родительской таблице. Если в определении внешнего ключа не задано действие при update/delete, то такие операции над родительской таблицей могут привести к несогласованности данных, т.е. появлению в дочерней таблице записей не имеющих соответствия в родительской таблице.
create table t1 (a int, index(a))engine=innodb; create table t2 (a int, foreignkey(a)references t1(a))engine=innodb;
insertinto t1 values(1); insertinto t2 values(1);
update t1 set a=2;
ERROR 1451(23000): Cannot deleteorupdate a parent row: a foreignkeyconstraint fails (`test`.`t2`, CONSTRAINT `t2_ibfk_1` FOREIGNKEY(`a`)REFERENCES `t1`(`a`))
Г3. Изменение данных в дочерней таблице. Если insert/update записи в дочерней таблицы приводит к несогласованности данных, то
create table t1 (a int, index(a))engine=innodb; create table t2 (a int, foreignkey(a)references t1(a))engine=innodb;
insertinto t2 values(15);
ERROR 1452(23000): Cannot addorupdate a child row: a foreignkeyconstraint fails (`test`.`t2`, CONSTRAINT `t2_ibfk_1` FOREIGNKEY(`a`)REFERENCES `t1` (`a`))
Г4. Добавление внешнего ключа на не пустую таблицу. При попытке добавить внешний ключ на таблицу, в которой есть записи, не удовлетворяющие условию внешнего ключа (т.е. не имеющие соответствия в родительской таблице), будет ошибка:
create table t1 (a int, index(a))engine=innodb; create table t2 (a int, index(a))engine=innodb;
insertinto t2 values(2);
alter table t2 addforeignkey(a)references t1(a);
ERROR 1452(23000): Cannot addorupdate a child row: a foreignkeyconstraint fails (`test`.`#sql-3f0_4`, CONSTRAINT `#sql-3f0_4_ibfk_1` FOREIGN KEY (`a`) REFERENCES `t1` (`a`))
Г5. Не уникальный ключ в родительской таблице. По стандарту SQL набор полей, на которые ссылается внешний ключ, должен быть уникальным. Однако, реализация внешних ключей в InnoDB позволяет иметь несколько "родителей". Из-за этого возникает трудно диагностируемая ошибка:
create table t1 (a int, index(a))engine=innodb; create table t2 (a int, index(a))engine=innodb;
deletefrom t1 where a=1limit1;
ERROR 1451(23000): Cannot deleteorupdate a parent row: a foreignkeyconstraint fails (`test`.`t2`, CONSTRAINT `t2_ibfk_1` FOREIGNKEY(`a`)REFERENCES `t1`(`a`))
Сводная таблица
По вертикали расположены коды ошибок MySQL, которые возникают при работе с внешними ключами ("нет ошибок" соответствует ситуации, когда сервер не генерирует ошибку, но и не создает внешний ключ). По горизонтали - идентификаторы причин, которые могут привести к ошибке. Плюсы на пересечении указывают какие причины приводят к той или иной ошибке.
А1
А2
А3
А4
А5
А6
А7
Б1
В1
В2
Г1
Г2
Г3
Г4
Г5
MySQL error 1005
+
+
+
+
+
+
+
+
MySQL error 1022
+
MySQL error 1025
+
+
+
MySQL error 1215
+
+
+
+
+
MySQL error 1217
+
+
MySQL error 1239
+
MySQL error 1451
+
+
MySQL error 1452
+
+
нет ошибок
+
+
P.S. Если ваш случай не рассмотрен в статье, то задавайте вопрос на форуме SQLinfo. Вам ответят, а статья будет расширена.