Зовнішні ключові обмеження: коли використовувати ON UPDATE та ON DELETE


196

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

У будь-якому разі, я вирішив використовувати InnoDB через підтримку зовнішнього ключа. Одне, що я помітив, - це те, що вона дозволяє встановлювати параметри On Update і Delete для зовнішніх ключів. Чи може хтось пояснити, де в простому прикладі можуть бути використані "Обмежити", "Каскад" та встановити нуль?

Наприклад, скажіть, що у мене є userтаблиця, яка містить a userID. І скажіть, у мене є таблиця повідомлень, messageяка є багатьма-багатьма, яка має 2 зовнішніх ключа (які посилаються на той самий первинний ключ userIDу userтаблиці). Чи корисне встановлення параметрів "Оновити" та "Видалити" в цьому випадку? Якщо так, то який я обираю? Якщо це не гарний приклад, чи можете ви запропонувати хороший приклад проілюструвати, як вони можуть бути корисними?

Дякую

Відповіді:


485

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

У MySQL у вас немає розширених обмежень, як у постгрега, але принаймні обмеження для зовнішніх ключів є досить розширеними.

Візьмемо для прикладу таблицю компанії з таблицею користувачів, що містить людей із тез компанії

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 :

  • ON UPDATE RESTRICT : за замовчуванням : якщо ви спробуєте оновити company_id у таблиці КОМПАНІЯ, двигун відкине операцію, якщо один USER хоча б посилається на цю компанію.
  • НА ОНОВЛЕННЯ НЕ ДІЙ : те саме, що і RESTRICT.
  • НА ОНОВЛЕННЯ КАСКАДИ : найкращий, як правило : якщо ви оновите company_id у рядку таблиці КОМПАНІЯ, двигун оновить його відповідно до всіх рядків USER, на які посилається ця КОМПАНІЯ (але жодних тригерів, активованих на таблиці USER, попередження). Двигун буде відслідковувати зміни для вас, це добре.
  • НА ОНОВЛЕННЯ НАСТРОЮЙТЕ НУЛЬ: якщо ви оновите company_id у рядку таблиці КОМПАНІЯ, двигун встановить відповідні USERs company_id на NULL (має бути доступним у полі USER company_id). Я не бачу нічого цікавого, що можна зробити з цим під час оновлення, але я можу помилятися.

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

  • ON DELETE RESTRICT : за замовчуванням : якщо ви спробуєте видалити ID company_id з таблиці КОМПАНІЯ, двигун відкине операцію, якщо один користувач, принаймні, посилається на цю компанію, може врятувати ваше життя.
  • НА ВИДАЛЕННЯ НЕ ДІЙ : те саме, що і RESTRICT
  • ON DELETE CASCADE : небезпечно : якщо ви видалите рядок компанії в таблиці КОМПАНІЯ, двигун також видалить пов’язані з цим ПОТРІБНИКИ. Це небезпечно, але може бути використане для автоматичного очищення на вторинних таблицях (тому це може бути щось, що вам хочеться, але абсолютно точно не для КОМПАНІЇ <-> ПРИКЛАДНИЙ приклад)
  • ON DELETE SET NULL : пригорща : якщо ви видалите рядок COMPANY, відповідні ПОТРІБНИКИ автоматично матимуть відношення до NULL. Якщо Null - це ваша цінність для користувачів, які не мають компанії, це може бути хорошою поведінкою, наприклад, можливо, вам потрібно буде тримати користувачів у вашій програмі як авторів певного вмісту, але видалення компанії не є проблемою для вас.

як правило, моїм замовчуванням є: ВИДАЛИТИ ОГРАНИЧЕННЯ НА ОНОВЛЕННЯ КАСКАДИ . з деякими 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 у коментарях, ситуація з цього приводу змінилася. Тому перейдіть за посиланням і перевірте реальну актуальну ситуацію (яка може знову змінитися в майбутньому).


8
ON DELETE CASCADE : dangerous- візьміть із щіпкою солі.
день, коли

3
Вам потрібно бути обережним з каскадом, це може заблокувати вашу систему, якщо потрібно змінити багато записів. Видалення Cascde слід особливо уважно переглянути, перш ніж використовувати, часто ви хочете, щоб видалення не відбулося, якщо є дочірні записи. Я не хотів би, щоб клієнт видаляв, щоб стерти фінансові дані для тих, що мали раніше. Іноді краще переконатись, що какадування не ввімкнено, і створити спосіб зміцнення записів як неактивних.
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
написано

6
"Я не хотів би, щоб клієнт видаляв, щоб стерти фінансові дані для замовлень, які він мав раніше". У такій ситуації вам, ймовірно, все одно потрібні дані замовника. Ваш дизайн, ймовірно, повинен відзначати клієнта як неактивного, не видаляючи його рядок із бази даних. На практиці, на моєму професійному досвіді, ви насправді дуже рідко хочете щось видалити , вважаючи за краще зазначати неактивне. У випадках, коли остаточно видалити все нормально, CASCADE DELETEзазвичай це також добре, навіть бажано. Я не вважаю це особливо небезпечним.
GrandOpener

3

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

Наприклад, якщо ви видалите запис за допомогою ORM, а ви DELETE CASCADEвидалите записи у відповідних таблицях, спроба ORM видалити ці пов'язані записи (часто автоматичні) призведе до помилки.


11
Це було б підставою не використовувати саме цю ОРМ. Будь-який інструмент, який погано підтримує базу даних, не заслуговує на довіру. Іноземні ключі та каскадні делети або оновлення - це основи db, а не розширені концепції, і жодна реальна база даних ніколи не повинна розроблятися без обмежень зовнішніх ключів!
HLGEM

Проблема полягає в тому, що вони кидають помилки. Чи можна RESTRICT DELETES, але двигун не створює помилок, але все ж підтримує семантику? Я хотів би, щоб моя програма продовжувала працювати, в той же час захищаючи інші дані від видалення.
TheRealChx101

2

Вам потрібно буде розглянути це в контексті програми. Взагалі слід створити додаток, а не базу даних (база даних просто є частиною програми).

Подумайте, як ваша заява повинна реагувати на різні випадки.

Дія за замовчуванням полягає в обмеженні (тобто не дозволенні) операції, яка зазвичай є тим, що ви хочете, оскільки це запобігає дурним помилкам програмування. Однак на DELETE CASCADE також може бути корисно. Це дійсно залежить від вашої програми та того, як ви маєте намір видалити конкретні об’єкти.

Особисто я би використовував InnoDB, оскільки він не сміття ваших даних (cf MyISAM, що це робить), а не тому, що він має обмеження FK.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.