Программирование SQLDML и запросыРазработчик серверной части

В системе нужно «удалить» записи, сохранив их идентификаторы для ссылок. Почему замена DELETE на UPDATE с п...

В системе нужно «удалить» записи, сохранив их идентификаторы для ссылок. Почему замена DELETE на UPDATE с признаком удаления меняет семантику запросов?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

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

Исторический контекст

Мягкое удаление появилось как практический приём для сохранения истории, возможности восстановления и аудита без физического удаления данных. Оно особенно полезно, когда на записи ссылаются другие объекты или законодательные и бизнес-процессы требуют сохранять историю изменений.

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

Постановка проблемы

После мягкого удаления строка остаётся в таблице. Запрос без фильтра по признаку удаления может показать её пользователю, включить в сумму продаж или позволить повторно обработать заказ.

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

Подробное решение

Физическое удаление уменьшает количество строк и может активировать каскадное удаление, запреты внешнего ключа или триггеры — конкретное поведение зависит от ограничений и СУБД. После DELETE обычный запрос уже не найдёт удалённую строку.

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

Минимальная схема поведения:

-- Мягкое удаление UPDATE orders SET deleted_at = CURRENT_TIMESTAMP WHERE order_id = 42; -- Только активные заказы SELECT order_id, total FROM orders WHERE deleted_at IS NULL;

Недостаток мягкого удаления — риск забыть фильтр. Его снижают представлениями для активных данных, репозиториями с централизованным условием, политиками доступа и тестами, проверяющими отсутствие архивных строк в пользовательских выборках.

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

Ситуация из практики

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

Вариант с физическим DELETE проще для запросов и экономит место, но требует отдельного архива и может привести к потере связанной истории. Вариант с флагом is_deleted проще проверять, однако он допускает неоднозначность состояний; дата deleted_at дополнительно показывает факт и момент удаления.

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

Что кандидаты часто упускают

1. Достаточно ли добавить фильтр мягкого удаления только в основной запрос приложения?

Нет. Строки могут попадать в отчёты, фоновые задачи, административные API, подзапросы и JOIN. Если хотя бы один путь чтения не применяет правило, архивная запись может снова повлиять на результат. Надёжнее централизовать доступ к активным данным, но всё равно проверять SQL на уровне тестов и ревью.

2. Что произойдёт с уникальностью при мягком удалении пользователя?

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

3. Можно ли считать мягкое удаление эквивалентом физического удаления для внешних ключей?

Нет. При DELETE СУБД может запретить удаление родителя или удалить зависимые строки каскадно. При UPDATE с признаком удаления родитель и зависимости остаются, поэтому ссылочная целостность не меняется, но бизнес-логика может запретить работу с «удалённым» объектом. Это правило нужно явно реализовать в запросах, ограничениях или прикладном коде.