DELETE — это команда, которая удаляет строки из таблицы.
Важно сразу почувствовать разницу: DELETE не очищает отдельную ячейку и не меняет значение в колонке. Он убирает строку целиком.
Представьте обычную тетрадь с контактами:
INSERT — добавить новую запись;
UPDATE — исправить телефон или имя в существующей записи;
DELETE — вырвать запись из тетради полностью.
После DELETE строки в таблице больше нет. Не «имя стало пустым», не «email стал NULL», а вся запись исчезла из результата обычных запросов.
Зачем нужен DELETE
В реальных проектах данные не только добавляются и обновляются. Иногда их нужно удалять.
Например:
- пользователь удалил аккаунт;
- товар сняли с продажи и решили убрать из временной таблицы;
- старая сессия истекла;
- корзину оформили в заказ, и временные строки корзины больше не нужны;
- импорт завершился, и служебную таблицу можно очистить;
- старые логи нужно удалить, чтобы таблица не разрасталась бесконечно.
Без удаления таблицы со временем превращаются в склад, куда всё приносят, но ничего никогда не выносят. Такой склад быстро становится тяжёлым, медленным и неудобным.
DELETE — это аккуратный инструмент уборки. Но инструмент опасный: если ошибиться с условием, можно удалить намного больше, чем хотелось.
Базовый синтаксис DELETE
Самый простой вариант выглядит так:
DELETE FROM users
WHERE id = 42;
Здесь две главные части:
DELETE FROM users
Говорим, из какой таблицы удаляем строки.
WHERE id = 42
Говорим, какие именно строки нужно удалить.
У DELETE нет части SET, как у UPDATE, потому что мы ничего не присваиваем. Мы не меняем колонки, а удаляем строки целиком.
Можно читать запрос почти обычным языком:
Удали из таблицы users строки, у которых id равен 42.
Пример с таблицей пользователей
Пусть есть таблица users:
Боб попросил удалить аккаунт. Мы знаем, что его id равен 2.
Пишем:
DELETE FROM users
WHERE id = 2;
После выполнения таблица станет такой:
Строка с id = 2 исчезла. Остальные строки остались на месте.
Обратите внимание: в таблице теперь нет id = 2. И это нормально. Если потом добавить нового пользователя, он обычно получит следующий номер, например id = 4, а не снова 2.
Почему так? Потому что идентификаторы лучше не переиспользовать. Старые логи, заказы, события и внешние системы могли когда-то ссылаться на пользователя с id = 2. Если потом выдать этот же id другому человеку, начнётся путаница.
WHERE — самая важная часть DELETE
Самая страшная ошибка с DELETE — забыть WHERE.
Вот такой запрос удалит не одного пользователя, а все строки из таблицы:
DELETE FROM users;
После него таблица users станет пустой.
База данных не спросит: «Вы точно хотите удалить всех пользователей?»
Она просто выполнит команду.
Поэтому правило номер один:
Перед DELETE всегда думайте о WHERE.
Хорошая привычка — начинать запрос так:
DELETE FROM users
WHERE
А уже потом дописывать условие.
Так мозг сразу привыкает: DELETE без условия — незаконченная и опасная команда.
Сначала SELECT, потом DELETE
Перед удалением полезно сначала выполнить SELECT с тем же самым условием.
Например, вы хотите удалить пользователя с id = 42.
Сначала проверяем:
SELECT id, name, email
FROM users
WHERE id = 42;
Смотрим результат. Если это действительно нужный пользователь, только потом выполняем:
DELETE FROM users
WHERE id = 42;
Это простая привычка, которая спасает от дорогих ошибок.
Особенно важно делать так на реальных данных, а не в учебной песочнице.
Проверка количества строк перед удалением
Если вы удаляете не одну строку, а много, полезно заранее узнать масштаб.
Например, хотим удалить старые сессии:
SELECT COUNT(*) AS rows_to_delete
FROM sessions
WHERE expires_at < NOW();
Если результат 47, всё выглядит спокойно.
Если результат 470000, стоит остановиться и подумать:
- точно ли условие правильное;
- не слишком ли большой объём для одного удаления;
- не повлияет ли это на пользователей;
- не лучше ли удалять данные пачками.
После проверки можно выполнять:
DELETE FROM sessions
WHERE expires_at < NOW();
SELECT COUNT(*) перед DELETE — очень хорошая привычка для всех операций, которые могут затронуть много строк.
DELETE в транзакции
Если вы удаляете важные данные, лучше делать это в транзакции.
Транзакция позволяет сначала выполнить удаление, посмотреть результат, а потом решить: подтвердить изменения или откатить.
Пример:
BEGIN;
DELETE FROM sessions
WHERE expires_at < NOW();
COMMIT;
Если после DELETE вы видите, что удалилось ожидаемое количество строк, делаете COMMIT.
Если что-то пошло не так, можно откатить:
ROLLBACK;
Например:
BEGIN;
DELETE FROM sessions
WHERE expires_at < NOW();
ROLLBACK;
После ROLLBACK изменения не сохранятся.
Для учебных примеров это может казаться лишним. Но в рабочей базе транзакции для опасных операций — это как ремень безопасности. Большую часть времени он просто есть, но однажды может спасти от серьёзной аварии.
DELETE с RETURNING в PostgreSQL
В PostgreSQL у DELETE есть удобная возможность — RETURNING.
Она позволяет удалить строки и сразу увидеть, что именно было удалено.
Например:
DELETE FROM users
WHERE id = 2
RETURNING id, name, email;
Результат может быть таким:
Это удобно, когда нужно:
- проверить удалённые строки;
- записать удалённые данные в лог;
- вернуть удалённый объект из API;
- убедиться, что удалился именно тот пользователь.
Можно даже вернуть все колонки:
DELETE FROM users
WHERE id = 2
RETURNING *;
Но на больших удалениях с RETURNING * нужно быть осторожным: если удалится много строк, база вернёт большой результат.
DELETE удаляет строки, но не таблицу
DELETE убирает данные, но сама таблица остаётся.
Например:
DELETE FROM users
WHERE id = 2;
После этого таблица users всё ещё существует. У неё остаются те же колонки, индексы, ограничения, права доступа.
Если удалить все строки:
DELETE FROM users;
таблица тоже останется. Она просто будет пустой.
Это важно не путать с DROP TABLE.
DROP TABLE удаляет саму таблицу целиком:
DROP TABLE users;
После DROP TABLE исчезают и данные, и структура таблицы. Это уже не уборка строк, а снос самого шкафа вместе со всеми полками.
DELETE vs UPDATE
Иногда новичок путает DELETE и UPDATE.
Если нужно изменить значение в строке, нужен UPDATE.
Например, пользователь поменял email:
UPDATE users
SET email = 'new_bob@example.com'
WHERE id = 2;
Строка осталась, изменился только email.
Если нужно убрать пользователя целиком, нужен DELETE:
DELETE FROM users
WHERE id = 2;
Строка исчезла.
Простой ориентир:
- поменять данные внутри строки —
UPDATE;
- убрать строку из таблицы —
DELETE.
DELETE vs TRUNCATE
Иногда нужно удалить не часть строк, а вообще все строки из таблицы.
Например, есть временная таблица для импорта:
staging_imports
Перед новой загрузкой её нужно очистить.
Можно сделать так:
DELETE FROM staging_imports;
Это сработает. Но DELETE удаляет строки построчно. Для каждой строки база выполняет обычную процедуру удаления: учитывает ограничения, триггеры, журнал изменений, индексы.
На маленькой таблице разницы почти не видно. На большой таблице это может быть медленно.
Для полной очистки таблицы есть TRUNCATE.
TRUNCATE TABLE staging_imports;
TRUNCATE обычно работает намного быстрее, потому что не удаляет каждую строку по отдельности, а очищает таблицу целиком более грубым способом.
Но у TRUNCATE есть важные особенности:
- нельзя написать
WHERE;
- он удаляет все строки;
- он берёт более сильные блокировки;
- обычные
DELETE-триггеры не срабатывают;
- поведение в транзакциях зависит от СУБД: в PostgreSQL
TRUNCATE можно откатить в транзакции, а в MySQL он обычно делает неявный коммит.
Правило простое:
- нужно удалить часть строк — используйте
DELETE;
- нужно быстро очистить всю таблицу — смотрите в сторону
TRUNCATE;
- сомневаетесь — выбирайте более осторожный вариант и сначала проверьте данные через
SELECT.
Soft delete и hard delete
Обычный DELETE физически удаляет строку из таблицы. Такой подход часто называют hard delete.
Например:
DELETE FROM users
WHERE id = 42;
Строка исчезла.
Но в реальных проектах часто используют другой подход — soft delete.
Soft delete означает: мы не удаляем строку физически, а помечаем её как удалённую.
Например, добавляют колонку deleted_at:
ALTER TABLE users
ADD COLUMN deleted_at TIMESTAMPTZ;
А вместо удаления делают UPDATE:
UPDATE users
SET deleted_at = NOW()
WHERE id = 42;
Теперь пользователь вроде бы удалён, но строка всё ещё лежит в таблице.
Обычные запросы должны показывать только неудалённых пользователей:
SELECT *
FROM users
WHERE deleted_at IS NULL;
Зачем нужен soft delete
Soft delete используют, когда данные нельзя просто выкинуть без следа.
Например:
- пользователь может восстановить аккаунт;
- нужно хранить историю действий;
- к строке привязаны заказы, платежи, документы;
- есть юридические или бизнес-требования к аудиту;
- администратор должен видеть, кто и когда был удалён.
Представьте интернет-магазин. Пользователь удалил аккаунт, но у него были оплаченные заказы. Если физически удалить пользователя и все связанные данные, можно сломать историю продаж, отчёты и бухгалтерию.
В такой ситуации часто лучше не удалять строку сразу, а пометить её удалённой.
Минусы soft delete
Soft delete удобен, но у него есть цена.
Главный минус: все запросы должны помнить про фильтр.
WHERE deleted_at IS NULL
Если забыть этот фильтр, в выдачу попадут удалённые строки.
Например, хотели показать активных пользователей:
SELECT *
FROM users;
А получили и активных, и удалённых.
Правильнее:
SELECT *
FROM users
WHERE deleted_at IS NULL;
Ещё один минус: таблица продолжает расти. Строки не исчезают, а только помечаются.
Также могут появиться сложности с уникальностью. Например, если есть ограничение на email:
email TEXT UNIQUE
Пользователь удалил аккаунт через soft delete, а потом решил зарегистрироваться снова с тем же email. Старая строка всё ещё в таблице, поэтому обычный UNIQUE может не дать создать новую.
В PostgreSQL такие задачи часто решают частичными уникальными индексами, например уникальность только среди неудалённых строк:
CREATE UNIQUE INDEX users_email_active_idx
ON users(email)
WHERE deleted_at IS NULL;
Soft delete — полезный паттерн, но его нужно проектировать осознанно.
ON DELETE CASCADE
DELETE может удалить не только одну строку, но и запустить удаление связанных строк, если так настроены внешние ключи.
Допустим, есть покупатели и заказы.
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE
);
Создадим таблицу заказов:
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INT NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
amount NUMERIC(12, 2) NOT NULL
);
Здесь важная часть:
REFERENCES customers(id) ON DELETE CASCADE
Она означает:
Если удалить покупателя, автоматически удали его заказы.
Теперь такой запрос:
DELETE FROM customers
WHERE id = 5;
может удалить не только строку из customers, но и все строки из orders, где customer_id = 5.
Это удобно, когда дочерние данные не имеют смысла без родительской строки.
Например:
- удалить пост и все его комментарии;
- удалить корзину и все её элементы;
- удалить черновик и все связанные временные данные.
Но это опасно, если вы не ожидаете каскадного удаления.
Удалили одну строку — а вместе с ней улетели десятки, тысячи или миллионы связанных строк.
ON DELETE RESTRICT
Более осторожный вариант — запретить удаление родительской строки, если на неё кто-то ссылается.
Например:
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INT NOT NULL REFERENCES customers(id) ON DELETE RESTRICT,
amount NUMERIC(12, 2) NOT NULL
);
Теперь база не даст удалить покупателя, пока у него есть заказы.
Это часто безопаснее для важных бизнес-данных. Например, в интернет-магазине заказ — это не просто «дочерняя запись». Это история покупки, деньги, отчёты, поддержка, возвраты.
Поэтому физически удалять покупателя вместе со всеми заказами может быть плохой идеей.
ON DELETE SET NULL
Есть ещё вариант: при удалении родителя оставить дочерние строки, но убрать ссылку.
CREATE TABLE comments (
id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(id) ON DELETE SET NULL,
body TEXT NOT NULL
);
Если удалить пользователя, комментарии останутся, но user_id станет NULL.
Это подходит, когда дочерняя запись может жить без родителя.
Например, можно оставить комментарий, но показывать автора как удалённого пользователя.
Важно: колонка должна разрешать NULL. Если написать так:
user_id INT NOT NULL REFERENCES users(id) ON DELETE SET NULL
возникнет конфликт: действие хочет поставить NULL, а колонка запрещает NULL.
Как безопасно удалять родительские строки
Если у таблицы есть дочерние таблицы, перед удалением стоит проверить, что будет затронуто.
Например, перед удалением покупателя можно посмотреть его заказы:
SELECT COUNT(*) AS orders_count
FROM orders
WHERE customer_id = 5;
Если включены каскады, полезно понимать весь масштаб.
Иногда цепочка может быть длинной:
customer -> orders -> order_items -> item_discounts
Удаление одной строки в начале цепочки может затронуть много таблиц.
Поэтому перед удалением родительских сущностей важно знать:
- какие внешние ключи на неё ссылаются;
- есть ли
ON DELETE CASCADE;
- сколько дочерних строк будет затронуто;
- нужно ли физическое удаление или лучше soft delete.
DELETE на больших таблицах
Удалить 10 строк — обычно не проблема.
Удалить 10 миллионов строк — совсем другая история.
Большой DELETE может:
- долго держать блокировки;
- создавать большую нагрузку на диск;
- раздувать журнал изменений;
- тормозить репликацию;
- мешать обычным запросам;
- оставлять после себя много «мёртвых» строк, которые потом нужно обслуживать.
Поэтому большие удаления часто делают пачками.
Например, не удалять сразу всё старше года одним запросом, а удалять по 10 000 строк за раз.
В PostgreSQL можно использовать подход через подзапрос:
DELETE FROM events
WHERE id IN (
SELECT id
FROM events
WHERE created_at < NOW() - INTERVAL '1 year'
LIMIT 10000
);
Такую операцию можно повторять в фоне, пока старые строки не закончатся.
Это мягче для базы, чем один огромный DELETE.
Размер пачки зависит от проекта. Где-то нормально удалять по 50 000 строк, где-то лучше по 1 000. Главное — не превращать чистку в удар молотком по рабочей базе.
DELETE и индексы
Индексы помогают не только искать строки, но и удалять их быстрее.
Например:
DELETE FROM sessions
WHERE user_id = 42;
Если на sessions(user_id) есть индекс, база быстрее найдёт нужные строки.
Без индекса ей, возможно, придётся просмотреть всю таблицу.
Но есть и обратная сторона: при удалении строк нужно обновлять индексы. Поэтому удаление большого количества строк всё равно может быть тяжёлым.
Практическое правило:
- если часто удаляете строки по колонке, по ней часто нужен индекс;
- если удаляете огромные объёмы, думайте о батчах, архивации или партиционировании.
DELETE и права доступа
В реальной базе не каждый пользователь должен иметь право удалять данные.
Для DELETE нужны соответствующие права.
Это важно для безопасности. Например, аналитик может иметь право читать данные через SELECT, но не иметь права удалять пользователей, заказы или платежи.
На уровне идеи это выглядит так:
GRANT SELECT ON users TO analyst;
А право на удаление можно не выдавать.
Команда DELETE меняет данные необратимо для обычного пользователя, поэтому доступ к ней должен быть ограничен.
Частые ошибки новичков
Ошибка 1. DELETE без WHERE
Самая дорогая ошибка:
DELETE FROM users;
Такой запрос удалит все строки из таблицы.
Безопаснее всегда сначала писать:
DELETE FROM users
WHERE id = 42;
И перед этим проверять:
SELECT *
FROM users
WHERE id = 42;
Ошибка 2. Условие, которое всегда истинно
Иногда WHERE вроде бы есть, но оно бесполезное.
Например:
DELETE FROM users
WHERE 1 = 1;
Условие 1 = 1 истинно для каждой строки. Значит, удалятся все строки.
Такие условия иногда случайно появляются в динамически собранных SQL-запросах. Поэтому запросы на удаление в коде нужно писать особенно внимательно и использовать параметры, а не склеивать SQL из строк как попало.
Ошибка 3. Перепутать AND и OR
Допустим, нужно удалить старые неактивные сессии.
Правильно:
DELETE FROM sessions
WHERE expires_at < NOW()
AND is_active = FALSE;
А если случайно написать OR:
DELETE FROM sessions
WHERE expires_at < NOW()
OR is_active = FALSE;
удалится гораздо больше строк: все истёкшие сессии плюс все неактивные, даже если они не старые.
Перед DELETE с несколькими условиями особенно полезно делать SELECT COUNT(*).
Ошибка 4. Удалять без транзакции на важных данных
Плохо:
DELETE FROM orders
WHERE created_at < NOW() - INTERVAL '1 year';
Сразу нажали Enter — и всё.
Лучше:
BEGIN;
SELECT COUNT(*) AS rows_to_delete
FROM orders
WHERE created_at < NOW() - INTERVAL '1 year';
DELETE FROM orders
WHERE created_at < NOW() - INTERVAL '1 year';
COMMIT;
Если количество строк неожиданное, можно сделать ROLLBACK.
Ошибка 5. Не учитывать ON DELETE CASCADE
Запрос выглядит маленьким:
DELETE FROM customers
WHERE id = 5;
Но если есть каскадные связи, он может удалить связанные заказы, позиции заказов и другие строки.
Перед удалением родительской сущности всегда полезно проверить дочерние таблицы.
Ошибка 6. Физически удалять то, что нужно для истории
Не все данные стоит удалять через hard delete.
Например, оплаченные заказы, финансовые операции, документы, важные действия пользователей часто нужны для истории, отчётов и расследований.
Иногда вместо:
DELETE FROM users
WHERE id = 42;
лучше использовать soft delete:
UPDATE users
SET deleted_at = NOW()
WHERE id = 42;
Выбор зависит от предметной области. Но удалять важные бизнес-данные физически просто потому, что так проще, — опасная привычка.
Ошибка 7. Забыть фильтр soft delete
Если в проекте принят soft delete, обычные запросы должны учитывать deleted_at.
Плохо:
SELECT *
FROM users;
Лучше:
SELECT *
FROM users
WHERE deleted_at IS NULL;
Иначе удалённые пользователи снова появятся в списках, отчётах или поиске.
Ошибка 8. Удалять огромный объём одним запросом
Такой запрос может быть тяжёлым:
DELETE FROM events
WHERE created_at < NOW() - INTERVAL '2 years';
Если таблица большая, лучше удалять пачками, следить за нагрузкой и заранее оценить количество строк.
Безопасный порядок действий перед DELETE
Для новичка хорошая схема такая:
- Сформулировать условие удаления.
- Выполнить
SELECT с этим условием.
- Проверить строки глазами или через
COUNT(*).
- Если данные важные — открыть транзакцию.
- Выполнить
DELETE.
- Проверить количество удалённых строк.
- Сделать
COMMIT или ROLLBACK.
Например:
BEGIN;
SELECT COUNT(*) AS rows_to_delete
FROM sessions
WHERE expires_at < NOW();
DELETE FROM sessions
WHERE expires_at < NOW();
COMMIT;
А если результат неожиданно большой:
ROLLBACK;
Эта схема чуть длиннее, чем просто написать один DELETE, зато намного спокойнее.
Как читать DELETE-запрос
Возьмём запрос:
DELETE FROM cart_items
WHERE user_id = 10
AND product_id = 25;
Читайте его так:
Удали из таблицы cart_items строки, которые относятся к пользователю 10 и товару 25.
А такой запрос:
DELETE FROM sessions
WHERE expires_at < NOW();
означает:
Удали все сессии, срок действия которых уже истёк.
Если вы не можете простым языком объяснить, какие строки удалит запрос, выполнять его рано. Сначала нужно разобраться с условием.
Главное
DELETE удаляет строки из таблицы. Не очищает отдельные поля, не меняет значения, а убирает строки целиком.
Самое важное:
DELETE FROM table_name указывает таблицу, из которой удаляем;
WHERE определяет, какие строки нужно удалить;
- без
WHERE удалятся все строки из таблицы;
- перед удалением полезно выполнить
SELECT или SELECT COUNT(*) с тем же условием;
- для важных данных лучше использовать транзакцию;
- в PostgreSQL удобно применять
RETURNING, чтобы увидеть удалённые строки;
DELETE удаляет данные, но не удаляет саму таблицу;
TRUNCATE быстрее очищает всю таблицу, но не умеет удалять часть строк;
- hard delete физически удаляет строку;
- soft delete помечает строку удалённой, например через
deleted_at;
ON DELETE CASCADE может автоматически удалить связанные строки;
- большие удаления лучше делать аккуратно, часто пачками.
Главная мысль простая: DELETE — команда сильная. Она нужна в любом проекте, но обращаться с ней нужно внимательно. Сначала проверьте, какие строки попадут под условие, и только потом удаляйте.
DELETE— это команда, которая удаляет строки из таблицы.Важно сразу почувствовать разницу:
DELETEне очищает отдельную ячейку и не меняет значение в колонке. Он убирает строку целиком.Представьте обычную тетрадь с контактами:
INSERT— добавить новую запись;UPDATE— исправить телефон или имя в существующей записи;DELETE— вырвать запись из тетради полностью.После
DELETEстроки в таблице больше нет. Не «имя стало пустым», не «email сталNULL», а вся запись исчезла из результата обычных запросов.Зачем нужен DELETE
В реальных проектах данные не только добавляются и обновляются. Иногда их нужно удалять.
Например:
Без удаления таблицы со временем превращаются в склад, куда всё приносят, но ничего никогда не выносят. Такой склад быстро становится тяжёлым, медленным и неудобным.
DELETE— это аккуратный инструмент уборки. Но инструмент опасный: если ошибиться с условием, можно удалить намного больше, чем хотелось.Базовый синтаксис DELETE
Самый простой вариант выглядит так:
DELETE FROM users WHERE id = 42;Здесь две главные части:
DELETE FROM usersГоворим, из какой таблицы удаляем строки.
WHERE id = 42Говорим, какие именно строки нужно удалить.
У
DELETEнет частиSET, как уUPDATE, потому что мы ничего не присваиваем. Мы не меняем колонки, а удаляем строки целиком.Можно читать запрос почти обычным языком:
Пример с таблицей пользователей
Пусть есть таблица
users:Боб попросил удалить аккаунт. Мы знаем, что его
idравен2.Пишем:
DELETE FROM users WHERE id = 2;После выполнения таблица станет такой:
Строка с
id = 2исчезла. Остальные строки остались на месте.Обратите внимание: в таблице теперь нет
id = 2. И это нормально. Если потом добавить нового пользователя, он обычно получит следующий номер, напримерid = 4, а не снова2.Почему так? Потому что идентификаторы лучше не переиспользовать. Старые логи, заказы, события и внешние системы могли когда-то ссылаться на пользователя с
id = 2. Если потом выдать этот жеidдругому человеку, начнётся путаница.WHERE — самая важная часть DELETE
Самая страшная ошибка с
DELETE— забытьWHERE.Вот такой запрос удалит не одного пользователя, а все строки из таблицы:
DELETE FROM users;После него таблица
usersстанет пустой.База данных не спросит: «Вы точно хотите удалить всех пользователей?»
Она просто выполнит команду.
Поэтому правило номер один:
Перед
DELETEвсегда думайте оWHERE.Хорошая привычка — начинать запрос так:
DELETE FROM users WHEREА уже потом дописывать условие.
Так мозг сразу привыкает:
DELETEбез условия — незаконченная и опасная команда.Сначала SELECT, потом DELETE
Перед удалением полезно сначала выполнить
SELECTс тем же самым условием.Например, вы хотите удалить пользователя с
id = 42.Сначала проверяем:
SELECT id, name, email FROM users WHERE id = 42;Смотрим результат. Если это действительно нужный пользователь, только потом выполняем:
DELETE FROM users WHERE id = 42;Это простая привычка, которая спасает от дорогих ошибок.
Особенно важно делать так на реальных данных, а не в учебной песочнице.
Проверка количества строк перед удалением
Если вы удаляете не одну строку, а много, полезно заранее узнать масштаб.
Например, хотим удалить старые сессии:
SELECT COUNT(*) AS rows_to_delete FROM sessions WHERE expires_at < NOW();Если результат
47, всё выглядит спокойно.Если результат
470000, стоит остановиться и подумать:После проверки можно выполнять:
DELETE FROM sessions WHERE expires_at < NOW();SELECT COUNT(*)передDELETE— очень хорошая привычка для всех операций, которые могут затронуть много строк.DELETE в транзакции
Если вы удаляете важные данные, лучше делать это в транзакции.
Транзакция позволяет сначала выполнить удаление, посмотреть результат, а потом решить: подтвердить изменения или откатить.
Пример:
BEGIN; DELETE FROM sessions WHERE expires_at < NOW(); COMMIT;Если после
DELETEвы видите, что удалилось ожидаемое количество строк, делаетеCOMMIT.Если что-то пошло не так, можно откатить:
ROLLBACK;Например:
BEGIN; DELETE FROM sessions WHERE expires_at < NOW(); ROLLBACK;После
ROLLBACKизменения не сохранятся.Для учебных примеров это может казаться лишним. Но в рабочей базе транзакции для опасных операций — это как ремень безопасности. Большую часть времени он просто есть, но однажды может спасти от серьёзной аварии.
DELETE с RETURNING в PostgreSQL
В PostgreSQL у
DELETEесть удобная возможность —RETURNING.Она позволяет удалить строки и сразу увидеть, что именно было удалено.
Например:
DELETE FROM users WHERE id = 2 RETURNING id, name, email;Результат может быть таким:
Это удобно, когда нужно:
Можно даже вернуть все колонки:
DELETE FROM users WHERE id = 2 RETURNING *;Но на больших удалениях с
RETURNING *нужно быть осторожным: если удалится много строк, база вернёт большой результат.DELETE удаляет строки, но не таблицу
DELETEубирает данные, но сама таблица остаётся.Например:
DELETE FROM users WHERE id = 2;После этого таблица
usersвсё ещё существует. У неё остаются те же колонки, индексы, ограничения, права доступа.Если удалить все строки:
DELETE FROM users;таблица тоже останется. Она просто будет пустой.
Это важно не путать с
DROP TABLE.DROP TABLEудаляет саму таблицу целиком:DROP TABLE users;После
DROP TABLEисчезают и данные, и структура таблицы. Это уже не уборка строк, а снос самого шкафа вместе со всеми полками.DELETE vs UPDATE
Иногда новичок путает
DELETEиUPDATE.Если нужно изменить значение в строке, нужен
UPDATE.Например, пользователь поменял email:
UPDATE users SET email = 'new_bob@example.com' WHERE id = 2;Строка осталась, изменился только email.
Если нужно убрать пользователя целиком, нужен
DELETE:DELETE FROM users WHERE id = 2;Строка исчезла.
Простой ориентир:
UPDATE;DELETE.DELETE vs TRUNCATE
Иногда нужно удалить не часть строк, а вообще все строки из таблицы.
Например, есть временная таблица для импорта:
Перед новой загрузкой её нужно очистить.
Можно сделать так:
DELETE FROM staging_imports;Это сработает. Но
DELETEудаляет строки построчно. Для каждой строки база выполняет обычную процедуру удаления: учитывает ограничения, триггеры, журнал изменений, индексы.На маленькой таблице разницы почти не видно. На большой таблице это может быть медленно.
Для полной очистки таблицы есть
TRUNCATE.TRUNCATE TABLE staging_imports;TRUNCATEобычно работает намного быстрее, потому что не удаляет каждую строку по отдельности, а очищает таблицу целиком более грубым способом.Но у
TRUNCATEесть важные особенности:WHERE;DELETE-триггеры не срабатывают;TRUNCATEможно откатить в транзакции, а в MySQL он обычно делает неявный коммит.Правило простое:
DELETE;TRUNCATE;SELECT.Soft delete и hard delete
Обычный
DELETEфизически удаляет строку из таблицы. Такой подход часто называют hard delete.Например:
DELETE FROM users WHERE id = 42;Строка исчезла.
Но в реальных проектах часто используют другой подход — soft delete.
Soft delete означает: мы не удаляем строку физически, а помечаем её как удалённую.
Например, добавляют колонку
deleted_at:ALTER TABLE users ADD COLUMN deleted_at TIMESTAMPTZ;А вместо удаления делают
UPDATE:UPDATE users SET deleted_at = NOW() WHERE id = 42;Теперь пользователь вроде бы удалён, но строка всё ещё лежит в таблице.
Обычные запросы должны показывать только неудалённых пользователей:
SELECT * FROM users WHERE deleted_at IS NULL;Зачем нужен soft delete
Soft delete используют, когда данные нельзя просто выкинуть без следа.
Например:
Представьте интернет-магазин. Пользователь удалил аккаунт, но у него были оплаченные заказы. Если физически удалить пользователя и все связанные данные, можно сломать историю продаж, отчёты и бухгалтерию.
В такой ситуации часто лучше не удалять строку сразу, а пометить её удалённой.
Минусы soft delete
Soft delete удобен, но у него есть цена.
Главный минус: все запросы должны помнить про фильтр.
WHERE deleted_at IS NULLЕсли забыть этот фильтр, в выдачу попадут удалённые строки.
Например, хотели показать активных пользователей:
SELECT * FROM users;А получили и активных, и удалённых.
Правильнее:
SELECT * FROM users WHERE deleted_at IS NULL;Ещё один минус: таблица продолжает расти. Строки не исчезают, а только помечаются.
Также могут появиться сложности с уникальностью. Например, если есть ограничение на email:
email TEXT UNIQUEПользователь удалил аккаунт через soft delete, а потом решил зарегистрироваться снова с тем же email. Старая строка всё ещё в таблице, поэтому обычный
UNIQUEможет не дать создать новую.В PostgreSQL такие задачи часто решают частичными уникальными индексами, например уникальность только среди неудалённых строк:
CREATE UNIQUE INDEX users_email_active_idx ON users(email) WHERE deleted_at IS NULL;Soft delete — полезный паттерн, но его нужно проектировать осознанно.
ON DELETE CASCADE
DELETEможет удалить не только одну строку, но и запустить удаление связанных строк, если так настроены внешние ключи.Допустим, есть покупатели и заказы.
CREATE TABLE customers ( id SERIAL PRIMARY KEY, email TEXT NOT NULL UNIQUE );Создадим таблицу заказов:
CREATE TABLE orders ( id SERIAL PRIMARY KEY, customer_id INT NOT NULL REFERENCES customers(id) ON DELETE CASCADE, amount NUMERIC(12, 2) NOT NULL );Здесь важная часть:
REFERENCES customers(id) ON DELETE CASCADEОна означает:
Теперь такой запрос:
DELETE FROM customers WHERE id = 5;может удалить не только строку из
customers, но и все строки изorders, гдеcustomer_id = 5.Это удобно, когда дочерние данные не имеют смысла без родительской строки.
Например:
Но это опасно, если вы не ожидаете каскадного удаления.
Удалили одну строку — а вместе с ней улетели десятки, тысячи или миллионы связанных строк.
ON DELETE RESTRICT
Более осторожный вариант — запретить удаление родительской строки, если на неё кто-то ссылается.
Например:
CREATE TABLE orders ( id SERIAL PRIMARY KEY, customer_id INT NOT NULL REFERENCES customers(id) ON DELETE RESTRICT, amount NUMERIC(12, 2) NOT NULL );Теперь база не даст удалить покупателя, пока у него есть заказы.
Это часто безопаснее для важных бизнес-данных. Например, в интернет-магазине заказ — это не просто «дочерняя запись». Это история покупки, деньги, отчёты, поддержка, возвраты.
Поэтому физически удалять покупателя вместе со всеми заказами может быть плохой идеей.
ON DELETE SET NULL
Есть ещё вариант: при удалении родителя оставить дочерние строки, но убрать ссылку.
CREATE TABLE comments ( id SERIAL PRIMARY KEY, user_id INT REFERENCES users(id) ON DELETE SET NULL, body TEXT NOT NULL );Если удалить пользователя, комментарии останутся, но
user_idстанетNULL.Это подходит, когда дочерняя запись может жить без родителя.
Например, можно оставить комментарий, но показывать автора как удалённого пользователя.
Важно: колонка должна разрешать
NULL. Если написать так:user_id INT NOT NULL REFERENCES users(id) ON DELETE SET NULLвозникнет конфликт: действие хочет поставить
NULL, а колонка запрещаетNULL.Как безопасно удалять родительские строки
Если у таблицы есть дочерние таблицы, перед удалением стоит проверить, что будет затронуто.
Например, перед удалением покупателя можно посмотреть его заказы:
SELECT COUNT(*) AS orders_count FROM orders WHERE customer_id = 5;Если включены каскады, полезно понимать весь масштаб.
Иногда цепочка может быть длинной:
Удаление одной строки в начале цепочки может затронуть много таблиц.
Поэтому перед удалением родительских сущностей важно знать:
ON DELETE CASCADE;DELETE на больших таблицах
Удалить 10 строк — обычно не проблема.
Удалить 10 миллионов строк — совсем другая история.
Большой
DELETEможет:Поэтому большие удаления часто делают пачками.
Например, не удалять сразу всё старше года одним запросом, а удалять по 10 000 строк за раз.
В PostgreSQL можно использовать подход через подзапрос:
DELETE FROM events WHERE id IN ( SELECT id FROM events WHERE created_at < NOW() - INTERVAL '1 year' LIMIT 10000 );Такую операцию можно повторять в фоне, пока старые строки не закончатся.
Это мягче для базы, чем один огромный
DELETE.Размер пачки зависит от проекта. Где-то нормально удалять по 50 000 строк, где-то лучше по 1 000. Главное — не превращать чистку в удар молотком по рабочей базе.
DELETE и индексы
Индексы помогают не только искать строки, но и удалять их быстрее.
Например:
DELETE FROM sessions WHERE user_id = 42;Если на
sessions(user_id)есть индекс, база быстрее найдёт нужные строки.Без индекса ей, возможно, придётся просмотреть всю таблицу.
Но есть и обратная сторона: при удалении строк нужно обновлять индексы. Поэтому удаление большого количества строк всё равно может быть тяжёлым.
Практическое правило:
DELETE и права доступа
В реальной базе не каждый пользователь должен иметь право удалять данные.
Для
DELETEнужны соответствующие права.Это важно для безопасности. Например, аналитик может иметь право читать данные через
SELECT, но не иметь права удалять пользователей, заказы или платежи.На уровне идеи это выглядит так:
GRANT SELECT ON users TO analyst;А право на удаление можно не выдавать.
Команда
DELETEменяет данные необратимо для обычного пользователя, поэтому доступ к ней должен быть ограничен.Частые ошибки новичков
Ошибка 1. DELETE без WHERE
Самая дорогая ошибка:
DELETE FROM users;Такой запрос удалит все строки из таблицы.
Безопаснее всегда сначала писать:
DELETE FROM users WHERE id = 42;И перед этим проверять:
SELECT * FROM users WHERE id = 42;Ошибка 2. Условие, которое всегда истинно
Иногда
WHEREвроде бы есть, но оно бесполезное.Например:
DELETE FROM users WHERE 1 = 1;Условие
1 = 1истинно для каждой строки. Значит, удалятся все строки.Такие условия иногда случайно появляются в динамически собранных SQL-запросах. Поэтому запросы на удаление в коде нужно писать особенно внимательно и использовать параметры, а не склеивать SQL из строк как попало.
Ошибка 3. Перепутать AND и OR
Допустим, нужно удалить старые неактивные сессии.
Правильно:
DELETE FROM sessions WHERE expires_at < NOW() AND is_active = FALSE;А если случайно написать
OR:DELETE FROM sessions WHERE expires_at < NOW() OR is_active = FALSE;удалится гораздо больше строк: все истёкшие сессии плюс все неактивные, даже если они не старые.
Перед
DELETEс несколькими условиями особенно полезно делатьSELECT COUNT(*).Ошибка 4. Удалять без транзакции на важных данных
Плохо:
DELETE FROM orders WHERE created_at < NOW() - INTERVAL '1 year';Сразу нажали Enter — и всё.
Лучше:
BEGIN; SELECT COUNT(*) AS rows_to_delete FROM orders WHERE created_at < NOW() - INTERVAL '1 year'; DELETE FROM orders WHERE created_at < NOW() - INTERVAL '1 year'; COMMIT;Если количество строк неожиданное, можно сделать
ROLLBACK.Ошибка 5. Не учитывать ON DELETE CASCADE
Запрос выглядит маленьким:
DELETE FROM customers WHERE id = 5;Но если есть каскадные связи, он может удалить связанные заказы, позиции заказов и другие строки.
Перед удалением родительской сущности всегда полезно проверить дочерние таблицы.
Ошибка 6. Физически удалять то, что нужно для истории
Не все данные стоит удалять через hard delete.
Например, оплаченные заказы, финансовые операции, документы, важные действия пользователей часто нужны для истории, отчётов и расследований.
Иногда вместо:
DELETE FROM users WHERE id = 42;лучше использовать soft delete:
UPDATE users SET deleted_at = NOW() WHERE id = 42;Выбор зависит от предметной области. Но удалять важные бизнес-данные физически просто потому, что так проще, — опасная привычка.
Ошибка 7. Забыть фильтр soft delete
Если в проекте принят soft delete, обычные запросы должны учитывать
deleted_at.Плохо:
SELECT * FROM users;Лучше:
SELECT * FROM users WHERE deleted_at IS NULL;Иначе удалённые пользователи снова появятся в списках, отчётах или поиске.
Ошибка 8. Удалять огромный объём одним запросом
Такой запрос может быть тяжёлым:
DELETE FROM events WHERE created_at < NOW() - INTERVAL '2 years';Если таблица большая, лучше удалять пачками, следить за нагрузкой и заранее оценить количество строк.
Безопасный порядок действий перед DELETE
Для новичка хорошая схема такая:
SELECTс этим условием.COUNT(*).DELETE.COMMITилиROLLBACK.Например:
BEGIN; SELECT COUNT(*) AS rows_to_delete FROM sessions WHERE expires_at < NOW(); DELETE FROM sessions WHERE expires_at < NOW(); COMMIT;А если результат неожиданно большой:
ROLLBACK;Эта схема чуть длиннее, чем просто написать один
DELETE, зато намного спокойнее.Как читать DELETE-запрос
Возьмём запрос:
DELETE FROM cart_items WHERE user_id = 10 AND product_id = 25;Читайте его так:
А такой запрос:
DELETE FROM sessions WHERE expires_at < NOW();означает:
Если вы не можете простым языком объяснить, какие строки удалит запрос, выполнять его рано. Сначала нужно разобраться с условием.
Главное
DELETEудаляет строки из таблицы. Не очищает отдельные поля, не меняет значения, а убирает строки целиком.Самое важное:
DELETE FROM table_nameуказывает таблицу, из которой удаляем;WHEREопределяет, какие строки нужно удалить;WHEREудалятся все строки из таблицы;SELECTилиSELECT COUNT(*)с тем же условием;RETURNING, чтобы увидеть удалённые строки;DELETEудаляет данные, но не удаляет саму таблицу;TRUNCATEбыстрее очищает всю таблицу, но не умеет удалять часть строк;deleted_at;ON DELETE CASCADEможет автоматически удалить связанные строки;Главная мысль простая:
DELETE— команда сильная. Она нужна в любом проекте, но обращаться с ней нужно внимательно. Сначала проверьте, какие строки попадут под условие, и только потом удаляйте.