Что такое Soft Delete (мягкое удаление) в базах данных?

Ответ

Soft Delete (мягкое удаление) — это паттерн, при котором запись в базе данных не удаляется физически оператором DELETE, а помечается как удалённая с помощью специального флага (например, колонки deleted_at типа TIMESTAMP или is_deleted типа BOOLEAN). Это позволяет сохранять данные для истории, аудита или возможности восстановления.

Типичная реализация:

  1. В таблицу добавляется поле deleted_at (NULL по умолчанию).
  2. При "удалении" в это поле записывается текущая дата и время (вместо настоящего удаления).
  3. Все основные выборки (SELECT) автоматически фильтруют записи по условию WHERE deleted_at IS NULL.

Пример на SQL:

-- Создание таблицы
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100),
    deleted_at TIMESTAMP NULL DEFAULT NULL
);

-- "Мягкое" удаление пользователя с id = 5
UPDATE users SET deleted_at = NOW() WHERE id = 5;

-- Выборка только активных пользователей
SELECT * FROM users WHERE deleted_at IS NULL;

-- Выборка всех пользователей, включая удалённых
SELECT * FROM users;

-- Восстановление пользователя
UPDATE users SET deleted_at = NULL WHERE id = 5;

Реализация в Laravel Eloquent: Laravel предоставляет трейт SoftDeletes для удобной работы с этим паттерном.

// В модели
use IlluminateDatabaseEloquentModel;
use IlluminateDatabaseEloquentSoftDeletes;

class User extends Model {
    use SoftDeletes; // Добавляет функциональность мягкого удаления
    protected $dates = ['deleted_at'];
}

// Использование
$user = User::find(1);
$user->delete(); // Заполняет колонку `deleted_at` текущей датой

// Стандартные запросы игнорируют удалённые записи
$activeUsers = User::all(); // Только где deleted_at IS NULL

// Для работы со всеми записями
$allUsers = User::withTrashed()->get();
$trashedUsers = User::onlyTrashed()->get();

// Восстановление
User::withTrashed()->find(1)->restore(); // Обнуляет deleted_at

// Полное физическое удаление
User::withTrashed()->find(1)->forceDelete();

Преимущества Soft Delete:

  • Безопасность данных: Возможность восстановить случайно удалённые данные.
  • Целостность ссылок: Внешние ключи не нарушаются, так как запись физически остаётся в таблице.
  • Аудит: Сохраняется полная история всех записей.

Недостатки и сложности:

  • Усложнение запросов: Необходимо всегда помнить о фильтрации по deleted_at. В Laravel это решается на уровне ORM.
  • Рост базы данных: Требуется периодическая архивация или очистка старых "удалённых" записей.
  • Уникальные индексы: Могут возникнуть конфликты, если уникальность должна учитывать только активные записи. Решение — использовать составные индексы, включающие deleted_at.

Ответ 18+ 🔞

Слушай, объясняю про эту вашу «мягкую удалёнку», как будто я тебе на кухне за пивом рассказываю. Представь: ты не выбрасываешь старый диван на помойку, а просто задвигаешь его на балкон и вешаешь табличку «не трогать, тут мыши завелись». Вот это и есть Soft Delete, ёпта.

Суть в чём: В базе данных запись не выпиливают нахуй через DELETE, а просто ставят ей флажок — типа, «удалена, но мы её помним». Обычно это поле deleted_at (когда удалили) или is_deleted (да или нет). И все нормальные запросы потом этот флажок смотрят и удалённое игнорят. Хитрая жопа, но иногда нужная.

Как это выглядит в коде:

-- Допустим, есть таблица с юзерами
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100),
    deleted_at TIMESTAMP NULL DEFAULT NULL -- Вот наш волшебный флажок!
);

-- Хотим удалить юзера с id=5. Но по-мягкому!
UPDATE users SET deleted_at = NOW() WHERE id = 5; -- Просто ставим дату, и всё

-- Когда выбираем активных юзеров, всегда добавляем условие
SELECT * FROM users WHERE deleted_at IS NULL; -- Только те, у кого NULL, т.е. живые

-- А если надо всех, включая «балконных» — просто выбираем без условия
SELECT * FROM users;

-- Передумали? Восстанавливаем!
UPDATE users SET deleted_at = NULL WHERE id = 5; -- И он снова в строю

А в Laravel это вообще ебушки-воробушки, там встроенная фича есть.

// В модели просто добавляем трейт
use IlluminateDatabaseEloquentModel;
use IlluminateDatabaseEloquentSoftDeletes;

class User extends Model {
    use SoftDeletes; // Всё, магия началась
    protected $dates = ['deleted_at'];
}

// Использование — проще некуда
$user = User::find(1);
$user->delete(); // Физически не удалит, просто в deleted_at время запишет

// Все обычные запросы будут автоматом игнорить удалённых
$activeUsers = User::all(); // Только живые, где deleted_at IS NULL

// Хочешь посмотреть, что на балконе валяется?
$allUsersWithTrash = User::withTrashed()->get(); // Все до единого
$onlyTrashed = User::onlyTrashed()->get(); // Только те, кого «удалили»

// Ой, передумал, верни диван обратно в комнату!
User::withTrashed()->find(1)->restore(); // deleted_at станет NULL

// А если реально надо нахуй выкинуть — тогда forceDelete
User::withTrashed()->find(1)->forceDelete(); // Вот теперь всё, приехали

Зачем этот цирк? Плюсы:

  • Не страшно ошибиться. Случайно удалил — восстановил, и волнение ебать прошло. Не надо бегать с бэкапами.
  • История сохраняется. Всегда можно посмотреть, что и когда «удалили». Для аудита — то, что надо.
  • Целостность не ебётся. Внешние ключи не ругаются, потому что запись-то на месте.

Но и минусов овердохуища:

  • Запросы усложняются. Надо везде помнить про этот deleted_at IS NULL, а то вдруг удалённого юзера в отчёт включишь. В Laravel, конечно, Eloquent за тебя думает, но в сыром SQL — головняк.
  • База распухает. Удалённые записи копятся, как хлам на балконе. Рано или поздно придётся проводить «генеральную уборку» и чистить старьё.
  • С уникальностью могут быть косяки. Допустим, у тебя email должен быть уникальным. Если юзер «удалился», а потом новый пытается зарегиться с таким же email — может быть конфликт, потому что запись-то в базе есть. Тут уже надо мозги включать, например, составные индексы делать.

Короче, инструмент полезный, но не серебряная пуля. Используй, когда нужна возможность отката или история изменений. А если данные реально мусорные и восстанавливать их не планируется — лучше сразу DELETE и не мучать базу.