Soft delete - замість видалення рядка позначати його як видалений: deleted_at timestamptz. Застосунок фільтрує такі рядки в запитах (у Laravel - трейт SoftDeletes з глобальним скоупом).
Плюси:
- Відновлення помилково видаленого без бекапу.
- Історія й аудит: видно, що існувало й коли зникло.
- Посилання не ламаються: старі замовлення досі посилаються на «видалений» товар.
Мінуси й підводні камені:
- Унікальність ламається. Користувач видалив обліковий запис, а зареєструватися знову з тим самим email не може: рядок досі є, і
UNIQUE (email)спрацьовує. Рішення - частковий унікальний індекс:
CREATE UNIQUE INDEX users_email_active ON users (email) WHERE deleted_at IS NULL;
- Зовнішні ключі не знають про «видалення»: база не завадить створити замовлення на «видаленого» клієнта, а
ON DELETE CASCADEне спрацює. Каскад доводиться реалізовувати в коді. - Забуті фільтри: будь-який запит в обхід ORM (сирий SQL, звіти, аналітика, інший сервіс) бачить видалені рядки. Звідси витоки даних і неправильні цифри.
- Ростуть таблиці й індекси, і кожен запит несе додаткову умову. Індекси варто робити частковими (
WHERE deleted_at IS NULL). - Право на видалення даних (GDPR): «видалене» має бути видалене насправді. Soft delete не скасовує обов'язку стерти персональні дані.
Альтернативи:
- Архівна таблиця: при видаленні рядок переноситься в
deleted_usersтригером чи кодом. Основна таблиця лишається чистою, унікальність і ключі працюють нормально. - Статус замість «видалення» (
archived,closed), якщо це насправді бізнес-стан, а не видалення. - Аудит-лог зі знімком видаленого запису - для відновлення й історії.
Правило: soft delete - не дефолт для кожної таблиці. Він доречний там, де відновлення й історія справді потрібні, і з продуманими частковими індексами й очищенням.