DELETE — это команда «убрать строки из таблицы». Не «очистить значения», а именно вынести строку из таблицы насовсем.
Если продолжать аналогию с блокнотом контактов: INSERT — добавить новую страницу. UPDATE — поправить запись. DELETE — вырвать страницу целиком. После него человека в блокноте больше нет.
Зачем нужен DELETE
В реальной жизни:
- Пользователь удалил аккаунт — его строку из
users надо убрать.
- Корзина оплачена и оформлена в заказ — записи из
cart_items больше не нужны.
- Сессия истекла — строку из
sessions можно почистить.
DELETE это чистильщик. Без него таблицы только растут.
Базовый синтаксис
DELETE FROM users
WHERE id = 42;
Две части:
DELETE FROM users — из какой таблицы удаляем.
WHERE id = 42 — какие именно строки.
В отличие от UPDATE, у DELETE нет SET — нечего задавать, мы строки убираем целиком.
Пример с таблицей
Таблица users:
Боб попросил удалить аккаунт:
DELETE FROM users WHERE id = 2;
Таблица после:
Боб ушёл. Аню и Веру не тронули.
Обрати внимание: id = 2 пропал, и следующий пользователь будет с id = 4 (не 2). PostgreSQL счётчики автоинкрементов не «возвращает» — это нормально и правильно. ID не должны переиспользоваться, иначе запутаешься, на какого Боба ссылается старая запись.
WHERE — это снова must-have
Самая страшная ошибка SQL — забыть WHERE у DELETE:
DELETE FROM users;
Никаких подтверждений. PostgreSQL и MySQL по умолчанию выполнят молча. На прод-таблице со ста тысячами юзеров — это часы восстановления из бэкапа и тысячи писем «куда делся мой аккаунт».
Привычки те же, что у UPDATE:
1. Сначала SELECT с тем же WHERE — посмотри, что улетит:
SELECT id, name, email FROM users WHERE id = 42;
DELETE FROM users WHERE id = 42;
2. В транзакции — есть шанс откатиться, если кол-во строк не сошлось:
BEGIN;
DELETE FROM sessions WHERE expires_at < NOW();
COMMIT;
3. Привычка набирать DELETE FROM ... WHERE сразу — без WHERE мозг спотыкается о незавершённое условие и не нажимает Enter.
DELETE vs TRUNCATE
Если хочется почистить всю таблицу (например, staging-таблица перед новой загрузкой), DELETE FROM table без WHERE сработает — но он удаляет построчно: каждая строка проходит через триггеры, пишется в WAL-лог, обновляются индексы. На миллионе строк — минуты.
Для «снести всё разом» есть TRUNCATE:
TRUNCATE TABLE staging_imports;
Postgres не удаляет построчно — он пересоздаёт файл таблицы. В сотни раз быстрее на больших таблицах.
Но TRUNCATE:
- Нельзя позвать с
WHERE — он или всё, или ничего.
- Берёт жёсткую блокировку — пока работает, никто не может ни читать, ни писать.
- Не вызывает
BEFORE/AFTER DELETE триггеры (есть отдельные триггеры на TRUNCATE).
- В MySQL коммитит транзакцию неявно (откатить нельзя).
Правило простое: «снести всё» — TRUNCATE, «снести часть» — DELETE.
Soft-delete vs hard-delete
«Hard-delete» — то, что мы разобрали: DELETE FROM действительно убирает строку из таблицы.
«Soft-delete» — это не команда, а паттерн. Вместо физического удаления в строку добавляется метка «удалено», а сама строка остаётся:
UPDATE users SET deleted_at = NOW() WHERE id = 42;
SELECT * FROM users WHERE deleted_at IS NULL;
Зачем это нужно:
- Юзеры могут восстанавливать аккаунты («передумал удалять» — через 30 дней).
- На строку могут ссылаться другие таблицы (заказы), и физическое удаление каскадом снесёт связанные данные.
- Audit-trail — кто, когда, почему «удалил».
Минусы:
- Все запросы должны помнить про
WHERE deleted_at IS NULL. Забыл — увидел «удалённое».
- Таблица только растёт.
- Уникальные ограничения мешают:
UNIQUE(email) не даст пользователю снова зарегистрироваться, потому что старый «soft-удалённый» дубль остался.
В реальных проектах часто комбинируют: soft-delete на год, потом cron-задача делает реальный hard-delete.
ON DELETE CASCADE
Если на foreign key стоит ON DELETE CASCADE, удаление родительской строки автоматически уносит все связанные:
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INT REFERENCES customers(id) ON DELETE CASCADE,
amount NUMERIC
);
DELETE FROM customers WHERE id = 5;
Удобно для очистки (один DELETE уносит всё дерево). Опасно для случайного удаления (не заметил, что на customer'а ссылаются 200 заказов — все улетели).
Альтернативы для FK:
ON DELETE RESTRICT — запретит удаление родителя, если есть дети. Защищает от случайностей.
ON DELETE SET NULL — обнулит FK у детей, но строки оставит.
Частые ошибки новичков
1. DELETE без WHERE. Всё уже сказали — это самая дорогая опечатка SQL. Привычка набирать DELETE FROM x WHERE сразу — единственное лекарство.
2. WHERE с истинным «всегда». Опечатки типа WHERE 1=1, WHERE name OR true — формально валидны, но удалят всё. Параметризованные запросы в коде + ревью SQL.
3. Удаление без транзакции на проде. Нажал — улетело — откатить нечем. BEGIN; DELETE …; COMMIT; — обязательная привычка для всех destructive операций.
4. Каскад снёс больше, чем ожидал. ON DELETE CASCADE идёт рекурсивно: удалил customer → удалились его orders → удалились их order_items. Перед DELETE родителя — SELECT COUNT(*) по детям, чтобы знать масштаб.
5. Забытый фильтр soft-delete. Если в проекте принят soft-delete, а ты пишешь DELETE FROM users WHERE id = 42 — формально правильно, но ломает паттерн (потеряются audit и возможность восстановить). В таких проектах вместо DELETE всегда UPDATE … SET deleted_at = NOW().
6. Производительность DELETE на большой таблице. Удаление миллиона строк за раз держит блокировки, тормозит реплики, заполняет WAL. Bulk-delete — батчами по 10K-50K с паузой между. Cron-задачи на чистку обычно так и пишут.
Мини-резюме
DELETE убирает строки. Структура таблицы остаётся, как была.
WHERE обязателен. Без него — пустая таблица. Привычка: DELETE FROM x WHERE сразу, потом условие.
- Перед DELETE на проде —
SELECT COUNT(*) с тем же WHERE. Знаешь масштаб — проще решить, проводить ли в транзакции.
TRUNCATE — для «снести всё», в разы быстрее, но грубее (нет WHERE, нет триггеров, жёсткий lock).
- Soft-delete (метка
deleted_at) — частый паттерн, когда строки нужны для аудита или восстановления.
ON DELETE CASCADE экономит запросы, но удаляет рекурсивно — проверяй масштаб.
DELETE— это команда «убрать строки из таблицы». Не «очистить значения», а именно вынести строку из таблицы насовсем.Если продолжать аналогию с блокнотом контактов:
INSERT— добавить новую страницу.UPDATE— поправить запись.DELETE— вырвать страницу целиком. После него человека в блокноте больше нет.Зачем нужен DELETE
В реальной жизни:
usersнадо убрать.cart_itemsбольше не нужны.sessionsможно почистить.DELETEэто чистильщик. Без него таблицы только растут.Базовый синтаксис
DELETE FROM users WHERE id = 42;Две части:
DELETE FROM users— из какой таблицы удаляем.WHERE id = 42— какие именно строки.В отличие от
UPDATE, уDELETEнетSET— нечего задавать, мы строки убираем целиком.Пример с таблицей
Таблица
users:Боб попросил удалить аккаунт:
DELETE FROM users WHERE id = 2;Таблица после:
Боб ушёл. Аню и Веру не тронули.
Обрати внимание:
id = 2пропал, и следующий пользователь будет сid = 4(не 2). PostgreSQL счётчики автоинкрементов не «возвращает» — это нормально и правильно. ID не должны переиспользоваться, иначе запутаешься, на какого Боба ссылается старая запись.WHERE — это снова must-have
Самая страшная ошибка SQL — забыть
WHEREуDELETE:DELETE FROM users; -- → ВСЕ строки исчезли. Таблица пустая.Никаких подтверждений. PostgreSQL и MySQL по умолчанию выполнят молча. На прод-таблице со ста тысячами юзеров — это часы восстановления из бэкапа и тысячи писем «куда делся мой аккаунт».
Привычки те же, что у
UPDATE:1. Сначала
SELECTс тем жеWHERE— посмотри, что улетит:SELECT id, name, email FROM users WHERE id = 42; -- увидел: Иван, ivan@... — да, того и хотел удалить DELETE FROM users WHERE id = 42;2. В транзакции — есть шанс откатиться, если кол-во строк не сошлось:
BEGIN; DELETE FROM sessions WHERE expires_at < NOW(); -- "DELETE 47" — норм COMMIT; -- если бы было "DELETE 47000" — ROLLBACK;3. Привычка набирать
DELETE FROM ... WHEREсразу — безWHEREмозг спотыкается о незавершённое условие и не нажимает Enter.DELETE vs TRUNCATE
Если хочется почистить всю таблицу (например, staging-таблица перед новой загрузкой),
DELETE FROM tableбезWHEREсработает — но он удаляет построчно: каждая строка проходит через триггеры, пишется в WAL-лог, обновляются индексы. На миллионе строк — минуты.Для «снести всё разом» есть
TRUNCATE:TRUNCATE TABLE staging_imports;Postgres не удаляет построчно — он пересоздаёт файл таблицы. В сотни раз быстрее на больших таблицах.
Но
TRUNCATE:WHERE— он или всё, или ничего.BEFORE/AFTER DELETEтриггеры (есть отдельные триггеры на TRUNCATE).Правило простое: «снести всё» —
TRUNCATE, «снести часть» —DELETE.Soft-delete vs hard-delete
«Hard-delete» — то, что мы разобрали:
DELETE FROMдействительно убирает строку из таблицы.«Soft-delete» — это не команда, а паттерн. Вместо физического удаления в строку добавляется метка «удалено», а сама строка остаётся:
-- Вместо DELETE UPDATE users SET deleted_at = NOW() WHERE id = 42; -- Все запросы должны фильтровать по NULL SELECT * FROM users WHERE deleted_at IS NULL;Зачем это нужно:
Минусы:
WHERE deleted_at IS NULL. Забыл — увидел «удалённое».UNIQUE(email)не даст пользователю снова зарегистрироваться, потому что старый «soft-удалённый» дубль остался.В реальных проектах часто комбинируют: soft-delete на год, потом cron-задача делает реальный hard-delete.
ON DELETE CASCADE
Если на foreign key стоит
ON DELETE CASCADE, удаление родительской строки автоматически уносит все связанные:CREATE TABLE orders ( id SERIAL PRIMARY KEY, customer_id INT REFERENCES customers(id) ON DELETE CASCADE, amount NUMERIC ); DELETE FROM customers WHERE id = 5; -- → удалятся все orders с customer_id = 5 тожеУдобно для очистки (один
DELETEуносит всё дерево). Опасно для случайного удаления (не заметил, что на customer'а ссылаются 200 заказов — все улетели).Альтернативы для FK:
ON DELETE RESTRICT— запретит удаление родителя, если есть дети. Защищает от случайностей.ON DELETE SET NULL— обнулит FK у детей, но строки оставит.Частые ошибки новичков
1. DELETE без WHERE. Всё уже сказали — это самая дорогая опечатка SQL. Привычка набирать
DELETE FROM x WHEREсразу — единственное лекарство.2. WHERE с истинным «всегда». Опечатки типа
WHERE 1=1,WHERE name OR true— формально валидны, но удалят всё. Параметризованные запросы в коде + ревью SQL.3. Удаление без транзакции на проде. Нажал — улетело — откатить нечем.
BEGIN; DELETE …; COMMIT;— обязательная привычка для всех destructive операций.4. Каскад снёс больше, чем ожидал.
ON DELETE CASCADEидёт рекурсивно: удалилcustomer→ удалились егоorders→ удалились ихorder_items. ПередDELETEродителя —SELECT COUNT(*)по детям, чтобы знать масштаб.5. Забытый фильтр soft-delete. Если в проекте принят soft-delete, а ты пишешь
DELETE FROM users WHERE id = 42— формально правильно, но ломает паттерн (потеряются audit и возможность восстановить). В таких проектах вместо DELETE всегдаUPDATE … SET deleted_at = NOW().6. Производительность DELETE на большой таблице. Удаление миллиона строк за раз держит блокировки, тормозит реплики, заполняет WAL. Bulk-delete — батчами по 10K-50K с паузой между. Cron-задачи на чистку обычно так и пишут.
Мини-резюме
DELETEубирает строки. Структура таблицы остаётся, как была.WHEREобязателен. Без него — пустая таблица. Привычка:DELETE FROM x WHEREсразу, потом условие.SELECT COUNT(*)с тем жеWHERE. Знаешь масштаб — проще решить, проводить ли в транзакции.TRUNCATE— для «снести всё», в разы быстрее, но грубее (нетWHERE, нет триггеров, жёсткий lock).deleted_at) — частый паттерн, когда строки нужны для аудита или восстановления.ON DELETE CASCADEэкономит запросы, но удаляет рекурсивно — проверяй масштаб.