Как обрабатывать удаления в базе данных?

44

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

Abie
источник
Я забыл упомянуть, что во втором случае помеченные записи необходимо будет удалить или переместить по истечении некоторого разумного промежутка времени.
Abie
Какую базу данных вы используете?
Эван Кэрролл
Temporal Table - лучшее решение для SQL Server 2016 и выше.
Самер

Ответы:

37

Да, я бы определенно выбрал второй вариант, но я бы добавил еще одно поле в поле даты.

Итак, вы добавляете:

delete       boolean
delete_date  timestamp

Это позволило бы вам дать время для восстановления действий.

Если время меньше часа, можно восстановить.

Чтобы действительно удалить удаленную запись, просто создайте хранимую процедуру, которая будет очищать каждую запись с удалением, установленным в значение true, и временем, превышающим один час, и помещать ее как вкладку cron, которая запускается каждые 24 часа.

Час это только пример.

Spredzy
источник
В качестве альтернативы у вас может быть другой флаг - cleanedили что-то - что указывает на то, что данные, связанные с этой записью, были правильно, полностью удалены. Запись может быть восстановлена, если cleanedона не истинна, и в этом случае она не подлежит восстановлению .
Гаурав
14
Это общий подход. Я обычно использую одно поле, deleted_atсодержащее как семантику deleteлогического значения, так и delete_dateметку времени. Если дескриптор deleted_atis, NULLрегистр deleteесть FALSEи delete_dateесть NULL, deleted_atсодержит дескриптор метки времени, регистр deleteis TRUEи delete_dateсодержит метку времени, экономя время, память и логику приложения.
Julien
1
Мне нравится логическое поле и поле даты. В зависимости от того, как вы реализуете логику удаления, у вас может быть даже отдельная таблица, которая содержит дату и уникальный ключ для записи, которая была «удалена». Хранимые процедуры делают это легко. Требуется дополнительное пространство на строку, необходимое до 1 бита против 8+. Вы также сможете составлять отчеты об удалениях за день, не касаясь исходной таблицы.
AndrewSQL
Примечание: удалить это зарезервированное слово в MySQL.
Джейсон Рикард
Помните, что отфильтрованный индекс на вашем deletedполе может значительно повысить производительность, когда вы запрашиваете не удаленные строки
Ross Presser
21

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

Мы храним старые версии в отдельной таблице аудита (поэтому для таблицы some_table где также есть таблица с именем some_table_audit), которая идентична тому, что имеет дополнительный идентификатор версии (временную метку, если ваша БД поддерживает временные значения достаточно гранулярно, целочисленный номер версии или UUID, который является внешним ключом к общей таблице аудита, и т. д.), и обновлять таблицу аудита автоматически по триггеру (поэтому нам не нужно сообщать весь код, который обновляет записи, о требовании аудита).

Сюда:

  • операция удаления - это просто простое удаление - нет необходимости добавлять какой-либо дополнительный код к этому (хотя вы можете захотеть записать, кто запрашивал, какие строки должны быть удалены, даже если они на самом деле не удалены)
  • вставки и обновления так же просты
  • вы можете реализовать восстановление или возврат, просто вернув «нормальную» строку к старой версии (триггер аудита снова сработает, поэтому таблица журнала аудита также отразит это изменение)
  • Вы можете предложить возможность просмотреть или вернуться к любой прошлой версии, а не просто восстановить последнюю.
  • Вам не нужно добавлять "помечено как удаленное?" проверяет каждую кодовую точку, которая относится к рассматриваемой таблице, или логику «обновить копию аудита» для каждой кодовой точки, которая удаляет / обновляет строки (хотя вам нужно решить, что делать с удаленными строками в таблице аудита: у нас есть флажок удален / не отмечен для каждой версии, поэтому в истории нет дыры, если записи будут удалены, а затем удалены)
  • хранение контрольных копий в отдельной таблице означает, что вы можете легко разделить их на разные файловые группы.

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


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

  • SQL Server 2016 представил «временные таблицы с системными версиями», которые выполняют большую часть этой работы за вас, и, кроме того, предоставляется некоторый приятный синтаксический сахар для упрощения создания и поддержки исторических запросов, и они координируют подмножество изменений схемы между таблицы базы и истории. Они не без своих предостережений, но они являются мощным инструментом для такого рода целей. Аналогичные функции также доступны в других системах БД.

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

Дэвид Спиллетт
источник
Как вы справляетесь с удалением и переименованием столбцов? Установить все на nullable?
Стейн
1
@Stijn: Это не часто, что структуры меняются, так что не так много. Колонны, как правило, никогда не удаляются после того, как они существуют в производстве - если они перестают использоваться, просто отбросьте все ограничения, которые остановят их, с большим значением NULL (или добавьте значения по умолчанию, чтобы справиться с ограничениями, используя «магическое значение», хотя это выглядит более грязным) и прекратить ссылаться на них в другом коде. Для переименований: добавьте новое, прекратите использование старого и скопируйте данные из старого в новое, если это необходимо. Если вы переименовываете столбцы, просто убедитесь, что в базовую таблицу и таблицу аудита внесены одинаковые изменения одновременно.
Дэвид Спиллетт
9

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

poelinca
источник
7

Я бы пошел с отдельной таблицей. В Ruby on Rails есть acts_as_versionedплагин, который в основном сохраняет строку в другой таблице с постфиксом, _versionпрежде чем обновляет его. Хотя вам не нужно такое точное поведение, оно также должно работать для вашего случая (скопируйте перед удалением).

Как и @Spredzy, я бы также порекомендовал добавить delete_dateстолбец, чтобы иметь возможность программно очищать записи, которые не были восстановлены после X часов / дней / чего бы то ни было.

Майкл Коля
источник
4

Решение, которое мы используем для этого внутри компании, состоит в том, чтобы иметь столбец состояния с некоторыми жестко закодированными значениями для некоторых конкретных состояний объекта: Удаленный, Активный, Неактивный, Открытый, Закрытый, Заблокированный - каждый статус с некоторым значением, используемым в приложении. С точки зрения БД мы не удаляем объекты, мы просто меняем статус и храним историю каждого изменения в таблице объектов.

Мэриан
источник
3

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

Питер Тейлор
источник
Это не просто вопрос зрения. Любые операции, выполняемые с множеством, должны исключать «удаленные» записи.
Abie
2

Подобно тому, что предложил Spredzy, мы используем поле метки времени для удаления во всех наших приложениях. Логическое значение излишне, так как установленная метка времени указывает, что запись была удалена. Таким образом, наш PDO всегда добавляет AND (deleted IS NULL OR deleted = 0)к операторам выбора, если модель явно не запрашивает включение удаленных записей.

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

Брайан Эйджи
источник
0

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

YaHozna
источник
0

Я привык видеть строки таблицы со столбцами типа «DeletedDate», и они мне не нравятся. Само понятие «удалено» заключается в том, что запись не должна была быть сделана в первую очередь. Практически, они не могут быть удалены из базы данных, но я не хочу, чтобы они были с моими горячими данными. Логически удаленные строки, по определению, являются холодными данными, если кто-то специально не хочет видеть удаленные данные.

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

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

Затем, удаление становится вставкой-удалением. Сначала удаляемая строка вставляется в ее «удаленную» копию схемы. Соответствующая строка в основной таблице может быть удалена. Однако необходимо добавить дополнительную логику где-то вдоль линии. Нарушения внешнего ключа могут быть обработаны.

Внешние ключи должны быть правильно обработаны. Это плохая практика, когда логически удаляется строка, но у первичной / уникальной строки есть столбцы в других таблицах, которые на нее ссылаются. Такого не должно быть. Обычное задание может удалять строки вдов (строки, первичные ключи которых не имеют ссылок в других таблицах, несмотря на наличие внешнего ключа. Это, однако, бизнес-логика.

Общее преимущество заключается в сокращении метаданных в таблице и улучшении производительности, которое она приносит. Столбец «deleteDate» говорит, что эта строка на самом деле не должна быть здесь, но для удобства мы оставляем ее там и позволяем SQL-запросу обрабатывать ее. Если копия удаленной строки хранится в «удаленной» схеме, то основная таблица с горячими данными имеет более высокий процент горячих данных (при условии, что они своевременно заархивированы) и меньше ненужных столбцов метаданных. Индексы и запросы больше не должны учитывать это поле. Чем короче размер строки, тем больше строк можно разместить на странице, тем быстрее может работать SQL Server.

Основным недостатком является размер операции. Теперь есть две операции вместо одной, а также дополнительная логика и обработка ошибок. Это может привести к большей блокировке, чем в противном случае потребовалось бы обновление одного столбца. Транзакция удерживает блокировки таблицы дольше, и в ней участвуют две таблицы. Удаление данных о производстве, по крайней мере, по моему опыту, делается редко. Тем не менее, в одной из основных таблиц 7,5% из почти 100 миллионов записей имеют запись в столбце «DeletedDate».

В качестве ответа на вопрос, приложение должно быть осведомлено о «undelete's». Для этого просто нужно сделать то же самое в обратном порядке: вставить строку из «удаленной» схемы в основную таблицу, а затем удалить строку из «удаленной схемы». Опять же, необходима дополнительная логика и обработка ошибок, чтобы избежать ошибок, проблем с внешними ключами и тому подобного.

Шон Редмонд
источник