Ограничения внешнего ключа: когда использовать ON UPDATE и ON DELETE

196

Я проектирую свою схему базы данных, используя MySQL Workbench, что довольно круто, потому что вы можете создавать диаграммы и конвертировать их: P

В любом случае, я решил использовать InnoDB из-за поддержки внешнего ключа. Однако я заметил одну вещь: она позволяет вам устанавливать параметры «При обновлении» и «Удалить» для внешних ключей. Может кто-нибудь объяснить, где в простом примере можно использовать «Restrict», «Cascade» и set null?

Например, скажем, у меня есть userтаблица, которая включает в себя userID. И, скажем, у меня есть таблица сообщений, messageкоторая имеет множество ко многим, которая имеет 2 внешних ключа (которые ссылаются на тот же первичный ключ userIDв userтаблице). В этом случае полезны ли параметры «При обновлении» и «При удалении»? Если да, какой из них выбрать? Если это не хороший пример, не могли бы вы придумать хороший пример, чтобы проиллюстрировать, как они могут быть полезны?

Спасибо

meltuhamy
источник

Ответы:

485

Не стесняйтесь накладывать ограничения на базу данных. Вы обязательно будете иметь согласованную базу данных, и это одна из веских причин использовать базу данных. Особенно, если у вас есть несколько приложений, запрашивающих его (или только одно приложение, но с прямым режимом и пакетным режимом с использованием разных источников).

В MySQL у вас нет расширенных ограничений, как в postgreSQL, но, по крайней мере, ограничения внешнего ключа достаточно продвинуты.

Мы возьмем пример, корпоративную таблицу с пользовательской таблицей, содержащей людей из этой компании

CREATE TABLE COMPANY (
     company_id INT NOT NULL,
     company_name VARCHAR(50),
     PRIMARY KEY (company_id)
) ENGINE=INNODB;

CREATE TABLE USER (
     user_id INT, 
     user_name VARCHAR(50), 
     company_id INT,
     INDEX company_id_idx (company_id),
     FOREIGN KEY (company_id) REFERENCES COMPANY (company_id) ON...
) ENGINE=INNODB;

Давайте посмотрим на предложение ON UPDATE :

  • ОГРАНИЧЕНИЕ ОБНОВЛЕНИЯ : по умолчанию : если вы попытаетесь обновить company_id в таблице COMPANY, механизм отклонит операцию, если хотя бы один ПОЛЬЗОВАТЕЛЬ ссылается на эту компанию.
  • ОБНОВЛЕНИЕ НЕТ ДЕЙСТВИЙ : так же, как ОГРАНИЧЕНИЕ.
  • ОБ ОБНОВЛЕНИИ КАСКАДА : обычно лучший : если вы обновите company_id в строке таблицы COMPANY, механизм обновит его соответствующим образом для всех строк USER, ссылающихся на эту COMPANY (но триггеры не активированы в таблице USER, предупреждение). Двигатель будет отслеживать изменения для вас, это хорошо.
  • ON UPDATE SET NULL : если вы обновите company_id в строке таблицы COMPANY, механизм установит для соответствующих USER company_id значение NULL (должно быть доступно в поле USER company_id). Я не вижу ничего интересного в обновлении, но могу ошибаться.

А теперь на стороне ON DELETE :

  • ON DELETE RESTRICT : по умолчанию : если вы попытаетесь удалить идентификатор company_id в таблице COMPANY, механизм отклонит операцию, если хотя бы один пользователь USER хотя бы ссылается на эту компанию, может спасти вам жизнь.
  • В УДАЛЕНИИ НЕТ ДЕЙСТВИЙ : так же, как ОГРАНИЧЕНИЕ
  • ON DELETE CASCADE : опасно : если вы удалите строку компании в таблице COMPANY, механизм удалит также и соответствующих пользователей. Это опасно, но его можно использовать для автоматической очистки вторичных таблиц (поэтому это может быть то, что вам нужно, но совершенно точно не для примера COMPANY <-> USER)
  • ON DELETE SET NULL : handful : если вы удалите строку COMPANY, соответствующие пользователи автоматически будут иметь отношение к NULL. Если значение Null для пользователей без компании, это может быть хорошим поведением, например, может быть, вам нужно сохранить пользователей в вашем приложении как авторов некоторого контента, но удаление компании не является для вас проблемой.

обычно по умолчанию используется: ON DELETE RESTRICT ON UPDATE CASCADE . с некоторыми ON DELETE CASCADEдля таблиц дорожек (журналы - не все журналы - и тому подобное) и ON DELETE SET NULLкогда главная таблица является «простым атрибутом» для таблицы, содержащей внешний ключ, например таблица JOB для таблицы USER.

редактировать

Прошло много времени с тех пор, как я это написал. Теперь я думаю, что я должен добавить одно важное предупреждение. MySQL имеет одно большое документированное ограничение каскадами. Каскады не являются спусковыми механизмами стрельбы . Поэтому, если вы были достаточно уверены в этом движке, чтобы использовать триггеры, вам следует избегать каскадных ограничений.

Триггеры MySQL активируются только для изменений, внесенных в таблицы операторами SQL. Они не активируются ни для изменений в представлениях, ни для изменений в таблицах, сделанных API, которые не передают операторы SQL на MySQL Server

==> См. Ниже последнего редактирования, все движется в этом домене

Триггеры не активируются действиями внешнего ключа.

И я не думаю, что это будет исправлено однажды. Ограничения внешнего ключа управляются хранилищем InnoDb, а триггеры - движком MySQL SQL. Оба разделены. Innodb - это единственное хранилище с управлением ограничениями, возможно, они добавят триггеры непосредственно в механизм хранения данных, а может и нет.

Но у меня есть собственное мнение о том, какой элемент следует выбирать между плохой реализацией триггера и очень полезной поддержкой ограничений внешних ключей. И как только вы привыкнете к согласованности базы данных, вы полюбите PostgreSQL.

12/2017-Обновление этой правки о MySQL:

Как заявил @IstiaqueAhmed в комментариях, ситуация на эту тему изменилась. Поэтому перейдите по ссылке и проверьте реальную актуальную ситуацию (которая может измениться в будущем).

regilero
источник
8
ON DELETE CASCADE : dangerous- взять со щепоткой соли.
понедельник,
3
Вы должны быть осторожны с каскадированием, оно может заблокировать вашу систему, если потребуется изменить много записей. Перед использованием Cascde delete следует внимательно изучить, часто вы действительно хотите, чтобы удаление не происходило при наличии дочерних записей. Я не хотел бы, чтобы клиент удалял, чтобы стереть финансовые данные для рудеров, которые он имел ранее. Иногда лучше убедиться, что каскад не включен, и обеспечить возможность пометить записи как неактивные.
HLGEM
1
С точки зрения бизнес-логики, есть один случай, который может быть интересен SET NULLв ON UPDATE: обновление компании представляет собой отдел Компании> Отношения пользователя. Например: если компания меняет свой тип бизнеса, предыдущие пользователи больше не могут быть связаны с этим бизнесом, поэтомуNULL могут быть предпочтительнее для этого индекса.
CPHPython
1
@regilero, похоже, содержание вашей первой ссылки ( dev.mysql.com/doc/refman/5.6/en/triggers.html ) на сайт mysql изменилось. Это говорит This includes changes to base tables that underlie updatable viewsвместо того, что вы вставили то естьThey do not activate for changes in views
Istiaque Ahmed
6
«Я бы не хотел, чтобы клиент удалял финансовые данные для заказов, которые он имел ранее». В такой ситуации вам, возможно, все равно понадобятся данные клиента. Ваш дизайн, вероятно, должен помечать клиента как неактивного, а не удалять его строки из базы данных. На практике мой профессиональный опыт заключается в том, что вы на самом деле очень редко хотите что-либо удалять , предпочитая пометить неактивным по умолчанию. В тех случаях , когда окончательное удаление является нормально, CASCADE DELETEкак правило , также хорошо, даже предпочтительнее. Я не считаю это особенно опасным.
GrandOpener
3

В дополнение к ответу @MarkR - следует отметить, что многие PHP-фреймворки с ORM не распознают или не используют расширенную настройку БД (внешние ключи, каскадное удаление, уникальные ограничения), что может привести к непредвиденному поведению.

Например, если вы удалите запись, используя ORM, и вы DELETE CASCADEудалите записи в связанных таблицах, попытка ORM удалить эти связанные записи (часто автоматически) приведет к ошибке.

LXA
источник
11
Это было бы причиной не использовать этот конкретный ORM. Любой инструмент с плохой поддержкой баз данных не заслуживает доверия. Внешние ключи и каскадные удаления или обновления являются основами БД, а не продвинутыми концепциями, и никакая реальная база данных никогда не должна создаваться без ограничений внешнего ключа!
HLGEM
Проблема в том, что они выбрасывают ошибки. Можно ли ОГРАНИЧИТЬ УДАЛЕНИЯ, но двигатель не генерирует ошибок, но сохраняет семантику? Я бы хотел, чтобы моя программа продолжала работать и в то же время защищала другие данные от удаления.
TheRealChx101
2

Вам нужно будет рассмотреть это в контексте приложения. В общем случае вы должны разрабатывать приложение, а не базу данных (база данных просто является частью приложения).

Подумайте, как ваше приложение должно реагировать на различные случаи.

Действие по умолчанию состоит в том, чтобы ограничить (то есть не разрешить) операцию, которая обычно является тем, что вы хотите, поскольку она предотвращает глупые ошибки программирования. Тем не менее, на УДАЛИТЬ КАСКАД также может быть полезным. Это действительно зависит от вашего приложения и от того, как вы собираетесь удалять определенные объекты.

Лично я бы использовал InnoDB, потому что он не удаляет ваши данные (см. MyISAM, который делает), а не потому, что он имеет ограничения FK.

MarkR
источник