Безопасный DELETE: последний взгляд
История о предварительном SELECT, резервной копии и проверке выборки перед необратимым удалением данных.
PostgreSQLSQLБезопасность данныхИнженерные практикиСтажер Коля сидел перед монитором, как сапёр перед бомбой. Задача была простой: удалить тестовые записи, созданные до 1 сентября. В его редакторе горели два слова, от которых у любого инженера холодеет в груди: DELETE FROM.
Он перепроверил условие WHERE трижды. created_at < '2025-09-01' и status = 'test'. Логично. Безопасно. Но палец замер над клавишей Enter. А что, если?.. Что, если в полночь системное время сбоило? Или какой-то старый импорт пометил реальные заказы как тестовые?
В этот момент рядом бесшумно возник Макс с кружкой кофе. Он не заглядывал в монитор. Он посмотрел на Колю.
— Ты сейчас похож на человека, который собирается произнести необратимую фразу, — тихо сказал Макс. — Фразу, после которой мир уже не будет прежним.
Коля нервно усмехнулся:
— Да просто старые данные чищу. Но чувство именно такое.
Макс кивнул, и в его взгляде не было ни капли осуждения. Только полное, тотальное понимание.
— Все мы держим в руках скальпель. Каждый день. И грань между хирургом и мясником — не в умении резать. Она — в искусстве последнего взгляда.
Он мягко подвинул к себе клавиатуру.
— Мы не пишем команды. Мы ведём диалог с системой, в которой живут судьбы, деньги и чужие надежды. И прежде чем отдать приказ, мудрый правитель всегда спрашивает: «Покажите мне тех, кого коснётся моё решение».
Макс не стал ничего исправлять. Он скопировал условие удаления в SELECT, добавил сортировку и ограничение для ручного просмотра:
SELECT *FROM ordersWHERE created_at < date '2025-09-01' AND status = 'test'ORDER BY created_at, idLIMIT 500;— Посмотри им в глаза, — сказал он. — Не как строкам. А как историям, которые ты собираешься стереть.
На экране появились сотни записей. Коля пробежался по ним взглядом… и замер. Одна из записей. Статус — ‘test’. Дата — августовская. Но сумма — огромная. И комментарий: «Демо для ключевого клиента N, не трогать до согласования». Эту запись завели вручную для презентации, и она попала под все формальные критерии удаления.
Он молча добавил в WHERE ещё одно условие: AND source IS DISTINCT FROM 'manual_demo'. В отличие от !=, такая проверка не потеряет строки с NULL незаметно. Затем снова запустил SELECT и отдельно проверил количество строк.
Перед удалением Коля сохранил точную выборку в резервную таблицу с номером заявки и выполнил изменение в явной транзакции:
BEGIN;
CREATE TABLE support.orders_cleanup_20250905 ASSELECT *FROM ordersWHERE created_at < date '2025-09-01' AND status = 'test' AND source IS DISTINCT FROM 'manual_demo';
DELETE FROM ordersWHERE created_at < date '2025-09-01' AND status = 'test' AND source IS DISTINCT FROM 'manual_demo'RETURNING id;
-- После сверки количества и контрольной выборки:COMMIT;-- При любом сомнении вместо COMMIT: ROLLBACK;Только после сверки Коля зафиксировал транзакцию. Для большой выборки этот сценарий пришлось бы разбить на пакеты: долгий DELETE сам способен создать нагрузку, удерживать старые версии строк и раздувать WAL.
Макс сделал глоток кофе.
— Видишь? — он улыбнулся. — Ты не просто удалил мусор. Ты спас ценность. Это и есть работа хирурга. Один лишний взгляд — одна спасённая вселенная.
Он похлопал его по плечу и пошёл дальше, оставив его наедине с простой, но жизненно важной истиной: самый мощный инструмент инженера — это не право менять, а мудрость не навредить.