Внешний ключ в качестве первичного ключа не работает, если таблица FK имеет составной первичный ключ (FK не является частью составного)

две таблицы, созданные с помощью этих скриптов:

create table user_auth
(
    username varchar(255) NOT NULL,
    password varchar(255) NOT NULL,
    user_id varchar(36) NOT NULL,
    PRIMARY KEY(username, password)
)engine=innodb;

create table user
(
    user_id varchar(36) NOT NULL,
    PRIMARY KEY(user_id),
    FOREIGN KEY(user_id) REFERENCES user_auth(user_id) ON DELETE CASCADE
)engine=innodb;

Я получаю сообщение об ошибке: не могу создать таблицу 'xxx.user' (номер ошибки: 150)

Ошибка внешнего ключа.

ЕСЛИ вместо этого я делаю это (удаляю ограничение внешнего ключа, но по-прежнему ссылаюсь на user_auth):

create table user_auth
(
    username varchar(255) NOT NULL,
    password varchar(255) NOT NULL,
    user_id varchar(36) NOT NULL,
    PRIMARY KEY(username, password)
)engine=innodb;

create table user
(
    user_id varchar(36) NOT NULL REFERENCES user_auth(user_id) ON DELETE CASCADE,
    PRIMARY KEY(user_id),
)engine=innodb;

Все просто замечательно, НО я могу вставить user_id в таблицу user, не имея соответствующего ключа в user_auth, что создает брешь в моей ссылочной целостности.

А теперь для удовольствия скажу, что я делаю следующее (удалите составной ключ и сделайте user_id первичным ключом в user_auth):

create table user_auth
(
    user_id varchar(36) NOT NULL,
    PRIMARY KEY(username, password)
)engine=innodb;

create table user
(
    user_id varchar(36) NOT NULL,
    PRIMARY KEY(user_id),
    FOREIGN KEY(user_id) REFERENCES user_auth(user_id) ON DELETE CASCADE
)engine=innodb;

Это тоже работает, хотя у меня нет составного имени пользователя и пароля для обеспечения уникальности.

У меня такое чувство, что мне не хватает чего-то очень важного в том, как работает MySQL. Просвети пожалуйста.

Спасибо за ваше время!

ОБНОВЛЕНИЕ

В статье ypercube, на которую ссылается их ответ, в третьем пункте упоминается порядок PK, и соответствующий FK должен быть одинаковым. Если я добавлю user_id в качестве ПК в user_auth в следующем порядке, скрипт заработает:

create table user_auth
(
    username varchar(255) NOT NULL,
    password varchar(255) NOT NULL,
    user_id varchar(36) NOT NULL,
    PRIMARY KEY(user_id, username, password)
)engine=innodb;

create table user
(
    user_id varchar(36) NOT NULL,
    PRIMARY KEY(user_id),
    FOREIGN KEY(user_id) REFERENCES user_auth(user_id) ON DELETE CASCADE
)engine=innodb;

Таким образом, я все еще могу SELECT и тому подобное, но теперь я не могу создавать повторяющиеся комбинации имени пользователя и пароля, и, таким образом, каждая учетная запись будет уникальной, поскольку должна существовать запись имени пользователя / пароля / идентификатора пользователя, прежде чем я смогу вставить данные пользователя.


person PandemoniumSyndicate    schedule 31.01.2013    source источник
comment
Вам нужен составной ПК в user_auth?   -  person ypercubeᵀᴹ    schedule 31.01.2013
comment
Я мог бы просто использовать имя пользователя в качестве ПК. Я пробовал это, но все равно не пошел.   -  person PandemoniumSyndicate    schedule 31.01.2013
comment
Ваша последняя попытка должна сработать, если у вас было PRIMARY KEY(user_id) в таблице user_auth (вы говорите, что сделали, но код другой. Вы также можете добавить UNIQUE KEY (username, password) или просто UNIQUE KEY (username), в зависимости от того, что вам нужно.   -  person ypercubeᵀᴹ    schedule 31.01.2013
comment
Вы правы насчет этого, оказывается, дело в порядке. Выложу как обновление.   -  person PandemoniumSyndicate    schedule 31.01.2013
comment
Другой вопрос. Почему 2 разных стола, а не один? И если вы хотите иметь более одной аутентификации для пользователя, то ваш FK находится в неправильном направлении. Он должен быть на user_auth и ссылаться на таблицу user.   -  person ypercubeᵀᴹ    schedule 31.01.2013
comment
Это то, что я сначала подумал, но потом мне нужно внести записи в user, прежде чем я сделаю user_auth, что неплохо, если, конечно, я не хочу видеть, существует ли уже комбинация имени пользователя и пароля. Затем мне нужно проверить свой PHP-код. Вместо этого наличие user в качестве дочернего элемента означает, что комбинация имени пользователя и пароля должна быть доступна, прежде чем я смогу создать учетную запись пользователя, поэтому он покрывается в MySQL за счет ссылочной целостности. Кроме того, я бы предпочел ссылаться на данные пользователя по идентификатору пользователя, я действительно использую имя пользователя / пароль только для проверки логинов.   -  person PandemoniumSyndicate    schedule 31.01.2013
comment
Нет нет нет! Вы не хотите PRIMARY KEY(user_id, username, password). Сделайте это PRIMARY KEY(user_id), UNIQUE KEY (username, password)   -  person ypercubeᵀᴹ    schedule 31.01.2013
comment
FK должен ссылаться либо на первичный ключ, либо на уникальный ключ. Даже если MySQL допускает иное, это не рекомендуется. Прочтите в документации MySQL: FOREIGN KEY Ограничения , где говорится: Отклонение от стандартов SQL: ограничение FOREIGN KEY, которое ссылается на не-UNIQUE ключ, не является стандартным SQL.   -  person ypercubeᵀᴹ    schedule 31.01.2013


Ответы (2)


Пытаться:

create table user_auth
(
    username varchar(255) NOT NULL,
    password varchar(255) NOT NULL,
    user_auth_id varchar(36) NOT NULL,
    PRIMARY KEY(username, password)
)engine=innodb;

create table user
(
    user_id varchar(36) NOT NULL,
    user_username varchar(36) NOT NULL, 
    user_pass varchar(36) NOT NULL, 
    INDEX(user_username, user_pass),
    PRIMARY KEY(user_id),
    FOREIGN KEY(user_username, user_pass) REFERENCES user_auth(username, password) ON DELETE CASCADE
)engine=innodb;

Ссылка: http://dev.mysql.com/doc/refman/5.5/en/innodb-foreign-key-constraints.html

person Anda Iancu    schedule 31.01.2013
comment
Эта ссылка помогла, проблема вращается вокруг пункта три: InnoDB требует индексов внешних ключей и ключей, на которые есть ссылки, чтобы проверки внешнего ключа могли быть быстрыми и не требовали сканирования таблицы. В ссылающейся таблице должен быть индекс, в котором столбцы внешнего ключа перечислены как первые столбцы в том же порядке. Такой индекс создается для ссылающейся таблицы автоматически, если он не существует. Этот индекс может быть отброшен позже, если вы создадите другой индекс, который можно использовать для принудительного применения ограничения внешнего ключа. index_name, если указано, используется, как описано ранее. - person PandemoniumSyndicate; 31.01.2013
comment
Таким образом, я могу сохранить структуру своей таблицы, если добавлю user_id в качестве первичного ключа в user_auth и упорядочу его следующим образом: PRIMARY KEY (user_id, username, password), что соответствует строгим ограничениям пункта три как оба ключа (PK в user_auth и FK в пользователе) в том же порядке. - person PandemoniumSyndicate; 31.01.2013

Пункт 3 в http://dev.mysql.com/doc/refman/5.5/en/innodb-foreign-key-constraints.html говорит обо всем. Поэтому, если я добавлю user_id в качестве ПК в user_auth (я думал, что упоминал, что пробовал это, но, похоже, я пропустил пример сценария), он все равно может не сработать:

create table user_auth
(
    username varchar(255) NOT NULL,
    password varchar(255) NOT NULL,
    user_id varchar(36) NOT NULL,
    PRIMARY KEY(username, password, user_id) -- <== DOES NOT WORK
)engine=innodb;

create table user
(
    user_id varchar(36) NOT NULL,
    PRIMARY KEY(user_id),
    FOREIGN KEY(user_id) REFERENCES user_auth(user_id) ON DELETE CASCADE
)engine=innodb;

Но если я изменю порядок ...

create table user_auth
(
    username varchar(255) NOT NULL,
    password varchar(255) NOT NULL,
    user_id varchar(36) NOT NULL,
    PRIMARY KEY(user_id, username, password) -- <== WORKS!
)engine=innodb;

create table user
(
    user_id varchar(36) NOT NULL,
    PRIMARY KEY(user_id),
    FOREIGN KEY(user_id) REFERENCES user_auth(user_id) ON DELETE CASCADE
)engine=innodb;

Потому что для того, чтобы использовать PK user_auth как FK, они должны быть в том же порядке, что и индексы (прокомментируйте, если я что-то неправильно понимаю).

Но с точки зрения дизайна таблиц у нас все еще есть проблема, составной ключ означает, что у меня могут быть дубликаты user_ids в user_auth, поэтому более разумный дизайн будет:

create table user_auth
(
    username varchar(255) NOT NULL,
    password varchar(255) NOT NULL,
    user_id varchar(36) NOT NULL,
    PRIMARY KEY(user_id),
    UNIQUE KEY(username) -- <== I should allow duplicate passwords, so just usernames should be               unique
)engine=innodb;

create table user
(
    user_id varchar(36) NOT NULL,
    PRIMARY KEY(user_id),
    FOREIGN KEY(user_id) REFERENCES user_auth(user_id) ON DELETE CASCADE
)engine=innodb;

Намного лучше

Главный вывод: имеет значение порядок составных ключей!

person PandemoniumSyndicate    schedule 31.01.2013
comment
Прочтите ответ Билла Карвина на этот вопрос: Проблема Mysql и FK, особенно последний абзац, в котором говорится , я чувствую необходимость подчеркнуть последний пункт. Вы получите аномальные данные, если определите внешние ключи для столбцов с неуникальным индексом в родительском элементе. Это, вероятно, приведет к тому, что ваши запросы будут сообщать строки несколько раз, когда вы выполняете соединения. Вы не должны использовать такое поведение InnoDB; вам следует определять внешние ключи только для уникальных родительских столбцов. - person ypercubeᵀᴹ; 31.01.2013
comment
Вы не должны использовать такое поведение InnoDB; вам следует определять внешние ключи только для уникальных родительских столбцов. user_id однозначно проиндексирован? Как гид? Или мы о другом говорим? - person PandemoniumSyndicate; 31.01.2013
comment
Родительский столбец user_auth(user_id), который не уникален в вашем коде. Это только первый столбец уникального индекса. - person ypercubeᵀᴹ; 31.01.2013
comment
Ах! Таким образом, разрешая имя пользователя, пароль и user_id быть составными, это позволяет мне (потенциально) иметь дубликаты user_id, если их имя пользователя и пароль отличаются? Это то, что ваш комментарий выше исправляет, имея PRIMARY KEY (user_id) и UNIQUE KEY (имя пользователя, пароль) (который, вероятно, у меня будет UNIQUE (имя пользователя), я должен разрешить дублирование паролей). Это верно? - person PandemoniumSyndicate; 31.01.2013
comment
Да, в том-то и дело. И затем (если есть повторяющиеся идентификаторы с другим именем пользователя и / или паролем), строки в таблице user, которые ссылаются на этот (повторяющийся) идентификатор, фактически будут ссылаться на более чем одну строку в user_auth. И это опасно. - person ypercubeᵀᴹ; 31.01.2013
comment
Отлично, отмечу это в своем ответе! - person PandemoniumSyndicate; 31.01.2013