Когда и зачем использовать каскадирование в SQL Server?

149

При настройке внешних ключей в SQL Server, при каких обстоятельствах он должен каскадироваться при удалении или обновлении, и каковы причины этого?

Это, вероятно, относится и к другим базам данных.

Больше всего я ищу конкретные примеры каждого сценария, желательно от того, кто их успешно использовал.

Джоэл Коухорн
источник
4
Этот вопрос не кажется строго связанным с SQL Server и выглядит скорее как теоретический, общий вопрос. Было бы более полезным для сообщества, если вы удалите sql-serverтег.
Клапас
4
@clapas Честно говоря, если бы я спросил об этом сегодня, это было бы не по теме. Если бы не высокие мнения / голоса, указывающие, что это имеет значение для сообщества, я бы просто удалил его.
Джоэл Коухорн
6
@JoelCoehoorn - Очевидно, что эти типы вопросов имеют значение. Это значение не рассеивается с течением времени. Вопрос в моей голове состоит в том, какую ценность мы теряем, отвергая такие вопросы сегодня?
P.Brian.Mackey
2
@ P.Brian.Mackey Здесь, здесь! Некоторые из лучших вопросов / ответов, которые я видел, - это те, за которых проголосовали или за которые проголосовали не по теме ... но вы можете сказать ОГРОМНЫМ количеством голосов, что у многих был точно такой же вопрос!
Энтони Григгс
Каскадные действия принимают сериализуемые блокировки.
Митч Пшеничный

Ответы:

127

Резюме того, что я видел до сих пор:

  • Некоторые люди совсем не любят каскадирование.

Каскад Удалить

  • Каскадное удаление может иметь смысл, когда семантика отношений может включать в себя эксклюзивное «является частью » описания. Например, запись OrderLine является частью ее родительского заказа, и OrderLines никогда не будут разделены между несколькими заказами. Если ордер должен был исчезнуть, OrderLine также должен был бы, и строка без ордера была бы проблемой.
  • Канонический пример для Каскадного удаления - SomeObject и SomeObjectItems, где не имеет никакого смысла, чтобы запись элементов когда-либо существовала без соответствующей основной записи.
  • Вы не должны использовать Каскадное удаление, если вы сохраняете историю или используете «мягкое / логическое удаление», когда вы устанавливаете только столбец удаленных битов в 1 / true.

Каскадное обновление

  • Каскадное обновление может иметь смысл, когда вы используете реальный ключ, а не суррогатный ключ (столбец идентификатора / автоинкремента) в таблицах.
  • Канонический пример для Cascade Update - это когда у вас есть изменяемый внешний ключ, например, имя пользователя, которое можно изменить.
  • Не следует использовать Cascade Update с ключами, которые являются столбцами Identity / autoincrement.
  • Каскадное обновление лучше всего использовать в сочетании с уникальным ограничением.

Когда использовать каскадирование

  • Возможно, вы захотите получить дополнительное строгое подтверждение от пользователя, прежде чем разрешить каскадную операцию, но это зависит от вашего приложения.
  • Каскадирование может привести к неприятностям, если вы неправильно настроили свои внешние ключи. Но вы должны быть в порядке, если вы делаете это правильно.
  • Неразумно использовать каскадирование, прежде чем полностью его понять. Однако это полезная функция, и поэтому стоит потратить время на ее понимание.
Джоэл Коухорн
источник
3
Обратите внимание, что каскадные обновления также часто используются, когда «так называемые» естественные ключи не являются настоящими эффективными уникальными ключами. На самом деле я убежден, что каскадные обновления нужны только для плохо нормализованных моделей баз данных, и они открывают врата для грязных таблиц и грязного кода.
Филипп Грондиер
1
Вам не хватает одного важного момента, каскадирование может создать огромные проблемы с производительностью, если есть много дочерних записей.
HLGEM
13
@HLGEM - я не вижу актуальности. Если каскадные операции вызывают замедление, эквивалентный ручной процесс либо вызовет такое же замедление, либо не будет должным образом защищен в случае необходимости отката транзакции.
Джоэл Коухорн
3
Почему это имеет значение, есть ли каскадное обновление столбца IDENTITY или автоинкремента? Я могу понять , почему это не было бы необходимо , потому что вы не должны изменять эти (произвольные) значения, но если один из них сделал изменения, по крайней мере , ссылочная целостность будет нетронутым.
Кенни Эвитт
3
10 пуль? Ну, теперь мы знаем, что Джоэл не стреляет из револьвера.
Нил N
69

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

Что плохо, так это неправильное использование внешних ключей, например создание их задом наперед.

Пример Хуана Мануэля является каноническим примером, если вы используете код, есть гораздо больше шансов оставить поддельные DocumentItems в базе данных, которая придет и укусит вас.

Каскадные обновления полезны, например, когда у вас есть ссылки на данные, которые могут измениться, например, первичным ключом таблицы пользователей является комбинация имени и фамилии. Затем вы хотите, чтобы изменения в этой комбинации распространялись туда, где на них ссылаются.

@Aidan, та ясность, на которую вы ссылаетесь, обуславливается высокой стоимостью, вероятность того, что ложные данные останутся в вашей базе данных, а это не мало . Для меня это, как правило, просто незнакомство с БД и неспособность определить, какие ФК существуют, прежде чем работать с БД, которые вызывают этот страх. Или это, или постоянное неправильное использование каскада, использование его там, где сущности не были концептуально связаны, или где вы должны сохранять историю.

Винко Врсалович
источник
7
Во-первых, использование такого «естественного» первичного ключа - очень плохая идея.
Ник Джонсон
1
Идея состояла в том, чтобы показать пример каскадных обновлений, но я согласен, что это не лучший пример. Расположение файлов может быть лучшим примером.
Винко Врсалович
4
RE: Комментарий направлен на Эйдана. Нет, отключение CASCADE для FK не увеличивает шансы на получение ложных данных. Это уменьшает вероятность того, что команда повлияет на большее количество данных, чем ожидалось, и увеличит код. Исключение ФК полностью оставляет вероятность ложных данных.
Шеннон Северанс
5
По крайней мере, два раза в моей карьере я видел, как опасные для бизнеса последствия неправильно понятого удаления каскада я очень не склонен использовать их сам во всех случаях, кроме самых очевидных. В обоих случаях данные были удалены в результате каскада, который действительно должен был быть сохранен, но его не было - и что он отсутствовал, не обнаруживался до тех пор, пока обычный цикл резервного копирования не потерял возможность простого восстановления. Винко прав с чисто логической точки зрения, однако в реальном мире использование каскадов подвергает человека ошибкам и непредвиденным последствиям больше, чем хотелось бы.
Cruachan
7
@Cruachan: Правило, на мой взгляд, простое. Если данные не так сильно связаны, как бесполезные без родительских данных, то это не гарантирует каскадные отношения. Это я и пытался адресовать в последней фразе моего ответа.
Винко Врсалович
17

Я никогда не использую каскадные удаления.

Если я хочу что-то удалить из базы данных, я хочу явно указать базе данных, что я хочу удалить.

Конечно, они являются функцией, доступной в базе данных, и могут быть случаи, когда их можно использовать, например, если у вас есть таблица 'order' и таблица 'orderItem', вы можете захотеть очистить элементы при удалении порядок.

Мне нравится ясность, которую я получаю от выполнения этого в коде (или хранимой процедуре), а не от «волшебства».

По той же причине я тоже не фанат триггеров.

Стоит заметить, что если вы удалите «заказ», вы получите отчет «На 1 строку затронут», даже если каскадное удаление удалило 50 «элементов OrderItem».

Loofer
источник
34
Почему бы не избавиться и от первичных ключей? Вы получите ясность в обеспечении уникальных значений в вашем коде.
MusiGenesis
4
@MusiGenesis, Эйдан не выступал за удаление ФК. FK по-прежнему защищает данные, но без CASCADE ON .... неожиданного волшебства не происходит.
Шеннон Северанс
7
@Vinko: удаление и обновление имеют четко определенную семантику по умолчанию. Изменение поведения с помощью каскада или триггера для выполнения большей работы оставляет шанс сделать больше, чем предполагалось. Нет, я не работаю без тестирования и да, мои базы данных задокументированы. Но я помню каждый кусок документации при написании кода? Если я хочу семантику более высокого уровня, такую ​​как удаление parent & children, я напишу и использую SP для этого.
Шеннон Северанс
3
@Vinko. проблема магии не в компетентных разработчиках или администраторах баз данных, а в том, что 5 лет спустя Джо Интерен получил задание «простого» обслуживания, когда администратор базы данных находится в отпуске, и кто затем испортил корпоративные данные, так что никто не осознал этого. Каскады имеют свое место, но важно учитывать все обстоятельства, включая человеческий фактор, перед их развертыванием.
Cruachan
5
@Vinko: Почему СП «Гасп»? ИП вызывающе идут туда, где база данных является критически важным корпоративным активом. Там сильный аргумент в своем роде обстоятельства говорил о том, чтобы ограничить все доступы данных для ЗЛ, или , по крайней мере , все , кроме Select. Смотрите мой ответ под stackoverflow.com/questions/1171769/…
Cruachan
13

Я много работаю с каскадным удалением.

Приятно знать, что тот, кто работает с базой данных, никогда не оставит нежелательных данных. Если зависимости растут, я просто изменяю ограничения на диаграмме в Management Studio и мне не нужно настраивать sp или dataacces.

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

Матиас Ф
источник
1
Я знаю, что это очень старо, но +1 за упоминание о циклической ссылке в CASCADE DELETE.
Код Maverick
2
Простите за новый вопрос: что на самом деле произойдет, если вы получите круговую ссылку?
Тим Ловелл-Смит
10

Я много работаю с базой данных и редко нахожу каскадные удаления полезными. Единственный раз, когда я использовал их эффективно, это база данных отчетов, которая обновляется ночными заданиями. Я проверяю, что любые измененные данные импортируются правильно, удаляя все записи верхнего уровня, которые изменились с момента последнего импорта, а затем повторно импортируйте измененные записи и все, что с ними связано. Это избавляет меня от необходимости писать множество сложных удалений, которые смотрят снизу вверх в моей базе данных.

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

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

Martynnw
источник
2
Вы заставили меня любопытно узнать больше. Почему вы сильно предпочитаете логические удаления? Имеют ли данные, с которыми вы работаете, какое-либо отношение к этому?
Тим Ловелл-Смит
9

Один пример - когда у вас есть зависимости между сущностями ... то есть: Document -> DocumentItems (когда вы удаляете Document, DocumentItems не имеет причин для существования)

хуан
источник
5

Используйте каскадное удаление там, где вы хотите удалить запись с FK, если удалена соответствующая запись PK. Другими словами, где запись не имеет смысла без ссылки на запись.

Я считаю, что каскадное удаление полезно, чтобы гарантировать, что мертвые ссылки удаляются по умолчанию, а не вызывают нулевые исключения.

TestPattern
источник
5

ON Удалить каскад:

Когда вы хотите, чтобы строки в дочерней таблице были удалены Если соответствующая строка удалена в родительской таблице.

Если при каскадном удалении не используется, возникнет ошибка для ссылочной целостности .

Обновление каскада:

Когда вы хотите, чтобы изменение в первичном ключе было обновлено во внешнем ключе

Durai Amuthan.H
источник
5

Я слышал о администраторах баз данных и / или «Политике компании», которые запрещают использовать «Каскад при удалении» (и другие) исключительно из-за плохого опыта в прошлом. В одном случае парень написал три триггера, которые в итоге вызвали друг друга. Три дня восстановления привели к полному запрету триггеров, все из-за действий одного idjit.

Конечно, иногда вместо «Каскад при удалении» нужны триггеры, например, когда необходимо сохранить некоторые дочерние данные. Но в других случаях вполне допустимо использовать каскадный метод при удалении. Ключевое преимущество «Каскада при удалении» состоит в том, что он захватывает ВСЕ дочерние элементы; Пользовательская письменная процедура триггера / сохранения может не работать, если она не закодирована правильно.

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

Разве не в этом вся суть развития?

BrianBurkill
источник
Я не думал, что это удалит все ... ты имеешь в виду, что функция на самом деле делает то, что говорит? ...
Джоэл Коухорн
3

Одной из причин поместить в каскадное удаление (а не делать это в коде) является повышение производительности.

Случай 1: с каскадным удалением

 DELETE FROM table WHERE SomeDate < 7 years ago;

Случай 2: без каскадного удаления

 FOR EACH R IN (SELECT FROM table WHERE SomeDate < 7 years ago) LOOP
   DELETE FROM ChildTable WHERE tableId = R.tableId;
   DELETE FROM table WHERE tableId = R.tableid;
   /* More child tables here */
 NEXT

Во-вторых, когда вы добавляете дополнительную дочернюю таблицу с каскадным удалением, код в случае 1 продолжает работать.

Я бы поставил только каскад, в котором семантика отношений является «частью». В противном случае какой-то идиот удалит половину вашей базы данных, когда вы сделаете:

DELETE FROM CURRENCY WHERE CurrencyCode = 'USD'
WW.
источник
5
Не зная, какую базу данных вы используете, я бы предположил, что ваше ручное удаление работает хуже, чем каскадное удаление, потому что оно не установлено на основе. В большинстве баз данных вы можете удалять на основе объединения с другой таблицей и, таким образом, удалять на основе наборов гораздо быстрее, чем циклически проходить по записям.
HLGEM
2

Я стараюсь избегать удалений или обновлений, которые я явно не запрашивал на сервере SQL.

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

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

pauliephonic
источник
2

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

Единственный случай, в котором я бы использовал, - это если данные в табличной таблице строго контролируются (например, ограничены разрешения) и обновляются или удаляются только через контролируемый процесс (например, обновление программного обеспечения), который был проверен.

Джен А
источник
1

Удаление или обновление S, которое удаляет значение внешнего ключа, найденное в некоторых кортежах R, может быть обработано одним из трех способов:

  1. непринятие
  2. распространения
  3. обнуление.

Распространение называется каскадным.

Есть два случая:

‣ Если кортеж в S был удален, удалите кортежи R, которые ссылались на него.

‣ Если кортеж в S был обновлен, обновите значение в R кортежей, которые к нему относятся.

Маянк Чопра
источник
0

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

Я только что ввел каскадное удаление для новой таблицы пересечений между двумя уже существующими таблицами (пересечение только для удаления) после того, как каскадное удаление не поощрялось в течение довольно долгого времени. Это также не так уж плохо, если данные будут потеряны.

Однако в таблицах списков, похожих на перечисление, это плохо: кто-то удаляет запись 13 - желтый из таблицы «colors», и все желтые элементы в базе данных удаляются. Кроме того, они иногда обновляются способом delete-all-insert-all, что приводит к полной пропущенной ссылочной целостности. Конечно, это неправильно, но как вы измените сложное программное обеспечение, работающее в течение многих лет, когда внедрение истинной ссылочной целостности может привести к неожиданным побочным эффектам?

Другая проблема заключается в том, что исходные значения внешнего ключа должны сохраняться даже после удаления первичного ключа. Можно создать столбец надгробной плиты и опцию ON DELETE SET NULL для исходного FK, но для этого снова требуются триггеры или специальный код для поддержания избыточного (кроме после удаления PK) значения ключа.

Эрик Харт
источник
0

Каскадные удаления чрезвычайно полезны при реализации логических сущностей супертипа и подтипа в физической базе данных.

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

Каскадное удаление может быть очень полезным инструментом для:

1) Убедитесь, что удаление записи супертипа также удаляет соответствующую отдельную запись подтипа.

2) Убедитесь, что любое удаление записи подтипа также удаляет запись супертипа. Это достигается путем реализации триггера удаления «вместо» в таблице подтипов, который отправляет и удаляет соответствующую запись супертипа, которая, в свою очередь, каскадно удаляет запись подтипа.

Использование каскадного удаления таким образом гарантирует, что никогда не будет ни одной записи супертипа или подтипа, независимо от того, была ли сначала удалена запись супертипа или сначала запись подтипа.

Марк Аллен
источник
Хороший пример. В JPA это объединенная таблица InheritanceStrategy. Для 1): Как правило, вы используете каркас постоянного уровня (EclipseLink, Hibernate, ...), который реализует удаленную последовательность для объединенного объекта, чтобы сначала удалить объединенную часть, а затем супер часть. Но если у вас есть более простое программное обеспечение, такое как задание на импорт или архивирование, удобно иметь возможность просто удалить объект, выполнив удаление в супер части. Относительно 2): согласен, но в этом случае клиент должен уже знать, что он работает над объединенной / подчиненной частью объекта.
Лев