SQLDELETEDMLtutorial

Что такое DELETE в SQL? Удаление строк для начинающих

DELETE — это команда «убрать строки из таблицы». Простыми словами: что удаляем, почему WHERE обязателен, soft-delete vs hard-delete, разница с TRUNCATE и каскадное удаление через FK. Таблицы before/after, частые ошибки новичков, мини-резюме и три задачки.

5 мин чтенияСправочникSQL · DELETE · DML · tutorial

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:

id name email
1 Аня anya@example.com
2 Боб bob@example.com
3 Вера vera@example.com

Боб попросил удалить аккаунт:

DELETE FROM users WHERE id = 2;

Таблица после:

id name email
1 Аня anya@example.com
3 Вера vera@example.com

Боб ушёл. Аню и Веру не тронули.

Обрати внимание: 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).
  • В MySQL коммитит транзакцию неявно (откатить нельзя).

Правило простое: «снести всё» — 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;

Зачем это нужно:

  • Юзеры могут восстанавливать аккаунты («передумал удалять» — через 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;
-- → удалятся все 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 сразу, потом условие.
  • Перед DELETE на проде — SELECT COUNT(*) с тем же WHERE. Знаешь масштаб — проще решить, проводить ли в транзакции.
  • TRUNCATE — для «снести всё», в разы быстрее, но грубее (нет WHERE, нет триггеров, жёсткий lock).
  • Soft-delete (метка deleted_at) — частый паттерн, когда строки нужны для аудита или восстановления.
  • ON DELETE CASCADE экономит запросы, но удаляет рекурсивно — проверяй масштаб.

Закрепи на практике

Решай задачи в SQL-тренажёре с мгновенной проверкой и подсказками.

Открыть тренажёр