Чтобы правильно ответить на этот вопрос, сначала нужно решить: что означает «удалить» в контексте этой системы / приложения?
Чтобы ответить на этот вопрос, вам нужно ответить еще на один вопрос: почему удаляются записи?
Существует ряд веских причин, по которым пользователю может потребоваться удалить данные. Обычно я нахожу, что существует только одна причина (для каждой таблицы), почему удаление может быть необходимым. Вот некоторые примеры:
- Освободить дисковое пространство;
- Жесткое удаление требуется в соответствии с политикой хранения / конфиденциальности;
- Поврежденные / безнадежно некорректные данные, их легче удалить и восстановить, чем восстановить.
- Большинство строк будут удалены, например, журнал таблицы ограничены X записей / дней.
Есть также несколько очень плохих причин для жесткого удаления (подробнее об этом позже):
- Чтобы исправить небольшую ошибку. Это обычно подчеркивает лень разработчика и враждебный пользовательский интерфейс.
- Чтобы «аннулировать» транзакцию (например, счет, который никогда не должен был быть выставлен).
- Потому что ты можешь .
Вы спросите, почему это так важно? Что не так с хорошим оле DELETE
?
- В любой системе, даже удаленно привязанной к деньгам, жесткое удаление нарушает всевозможные бухгалтерские ожидания, даже если перемещено в таблицу архива / надгробной плиты. Правильный способ справиться с этим - задним числом .
- Архивные таблицы имеют тенденцию отклоняться от действующей схемы. Если вы забудете хотя бы об одном добавленном столбце или каскаде, вы просто навсегда потеряете эти данные.
- Жесткое удаление может быть очень дорогой операцией, особенно с каскадами . Многие люди не понимают, что каскадирование более чем одного уровня (или, в некоторых случаях, любое каскадирование, в зависимости от СУБД) приведет к операциям на уровне записей вместо операций установки.
- Повторное частое жесткое удаление ускоряет процесс фрагментации индекса.
Итак, мягкое удаление лучше, верно? Нет, не совсем:
- Настройка каскадов становится чрезвычайно сложной. Вы почти всегда получаете то, что кажется клиенту осиротевшими строками.
- Вы можете отслеживать только одно удаление. Что если строка удаляется и удаляется несколько раз?
- Производительность чтения снижается, хотя это может быть несколько снижено с помощью секционирования, представлений и / или отфильтрованных индексов.
- Как уже упоминалось ранее, в некоторых сценариях / юрисдикциях это может быть на самом деле незаконно.
Правда в том, что оба эти подхода неверны. Удаление это неправильно. Если вы на самом деле задаете этот вопрос, то это означает, что вы моделируете текущее состояние вместо транзакций. Это плохая, плохая практика в базе данных.
Уди Дахан написал об этом в « Не удалять - просто не делай» . Существует всегда какой - то задачи, сделки, активность , или (мой предпочтительный термин) событие , которое на самом деле представляет собой «Удалить». Это нормально, если впоследствии вы захотите денормализовать в таблицу «текущее состояние» для повышения производительности, но сделайте это после того, как вы завершили транзакционную модель, а не раньше.
В этом случае у вас есть «пользователи». Пользователи по сути являются клиентами. Клиенты имеют деловые отношения с вами. Эти отношения не просто исчезают, потому что они отменили свою учетную запись. Что на самом деле происходит:
- Клиент создает аккаунт
- Клиент отменяет аккаунт
- Клиент обновляет аккаунт
- Клиент отменяет аккаунт
- ...
В каждом случае это один и тот же клиент и, возможно, одна и та же учетная запись (т. Е. Каждое продление учетной записи является новым соглашением об обслуживании). Так почему вы удаляете строки? Это очень легко моделировать:
+-----------+ +-------------+ +-----------------+
| Account | --->* | Agreement | --->* | AgreementStatus |
+-----------+ +-------------+ +----------------+
| Id | | Id | | AgreementId |
| Name | | AccountId | | EffectiveDate |
| Email | | ... | | StatusCode |
+-----------+ +-------------+ +-----------------+
Вот и все. Это все, что нужно сделать. Вам никогда не нужно ничего удалять. Выше приведен довольно распространенный дизайн, который обеспечивает хорошую степень гибкости, но его можно немного упростить; Вы можете решить, что вам не нужен уровень «Соглашение», и просто «Account» перейти к таблице «AccountStatus».
Если в вашем приложении часто требуется получить список активных соглашений / учетных записей, то это (немного) хитрый запрос, но для этого нужны представления:
CREATE VIEW ActiveAgreements AS
SELECT agg.Id, agg.AccountId, acc.Name, acc.Email, s.EffectiveDate, ...
FROM AgreementStatus s
INNER JOIN Agreement agg
ON agg.Id = s.AgreementId
INNER JOIN Account acc
ON acc.Id = agg.AccountId
WHERE s.StatusCode = 'ACTIVE'
AND NOT EXISTS
(
SELECT 1
FROM AgreementStatus so
WHERE so.AgreementId = s.AgreementId
AND so.EffectiveDate > s.EffectiveDate
)
И вы сделали. Теперь у вас есть кое-что со всеми преимуществами программных удалений, но ни с одним из недостатков:
- Потерянные записи не являются проблемой, потому что все записи видны всегда; вы просто выбираете из другого ракурса, когда это необходимо.
- «Удаление» - это обычно невероятно дешевая операция - просто вставка одной строки в таблицу событий.
- Там никогда не бывает шанс потери истории, когда - либо , независимо от того , насколько сильно вы завинчивать.
- Вы по-прежнему можете жестко удалить учетную запись, если вам нужно (например, по соображениям конфиденциальности), и быть уверенным в том, что удаление будет происходить чисто и не будет мешать любой другой части приложения / базы данных.
Единственная проблема, которую нужно решить, это проблема производительности. Во многих случаях это на самом деле не является проблемой из-за включенного кластерного индекса AgreementStatus (AgreementId, EffectiveDate)
- там очень мало запросов ввода-вывода. Но если это когда-либо является проблемой, есть способы решить это, используя триггеры, индексированные / материализованные представления, события уровня приложения и т. Д.
Однако не стоит слишком рано беспокоиться о производительности - более важно правильно спроектировать проект, и «правильный» в этом случае означает использование базы данных так, как она предназначена для использования в качестве транзакционной системы.