Как корректно удалить записи в базе данных, когда на них ссылаются внешние ключи, а триггеры и опция ON DELETE CASCADE отсутствуют?

Даны две таблицы: <br/> t1 (c11, date) <br/> t2 (c12, c22, c33) <br/> Поле t1.c11 связано с полем t2.c22 отношением «один ко многим».
  • 10 марта 2026 г. 14:00
Ответы на вопрос 2
Короткий ответ — сначала нужно удалить дочерние записи (в t2), потом родительские (в t1). Это нужно потому, что FK запрещает удалять запись, на которую есть ссылки. Ниже — варианты и рекомендации с примерами для разных ситуаций и СУБД.

1) Простая операция для одного значения (в транзакции)
- Общая логика: BEGIN; DELETE FROM t2 WHERE c22 = :id; DELETE FROM t1 WHERE c11 = :id; COMMIT;

Postgres / MySQL / SQL Server (пример):
BEGIN;
DELETE FROM t2 WHERE c22 = 'some_key';
DELETE FROM t1 WHERE c11 = 'some_key';
COMMIT;

2) Удалить по критерию на родительской таблице (например date < ...)
- Удаляем дочерние строки, которые ссылаются на родителей, затем удаляем родителей.

Postgres:
BEGIN;
DELETE FROM t2 USING t1 WHERE t2.c22 = t1.c11 AND t1.date < '2024-01-01';
DELETE FROM t1 WHERE date < '2024-01-01';
COMMIT;

MySQL:
START TRANSACTION;
DELETE t2 FROM t2 JOIN t1 ON t2.c22 = t1.c11 WHERE t1.date < '2024-01-01';
DELETE FROM t1 WHERE date < '2024-01-01';
COMMIT;

SQL Server:
BEGIN TRANSACTION;
DELETE t2 FROM t2 INNER JOIN t1 ON t2.c22 = t1.c11 WHERE t1.date < '2024-01-01';
DELETE FROM t1 WHERE date < '2024-01-01';
COMMIT;

Альтернативно (портативно), сначала выбрать id родителей, затем удалить по IN/EXISTS:
BEGIN;
DELETE FROM t2 WHERE c22 IN (SELECT c11 FROM t1 WHERE date < '2024-01-01');
DELETE FROM t1 WHERE date < '2024-01-01';
COMMIT;

3) Удаление партиями (для больших объёмов, чтобы не держать долгие блокировки)
- Удалять малыми порциями в цикле (batch), пока не удалится всё.

Пример (SQL Server-подобный синтаксис):
WHILE 1=1
BEGIN
  DELETE TOP (1000) t2
  FROM t2 JOIN t1 ON t2.c22 = t1.c11
  WHERE t1.date < '2024-01-01';
  IF @@ROWCOUNT = 0 BREAK;
END
-- затем тоже партиями удалить из t1:
WHILE 1=1
BEGIN
  DELETE TOP (1000) FROM t1 WHERE date < '2024-01-01';
  IF @@ROWCOUNT = 0 BREAK;
END

4) Если можно менять схему (рекомендуется для долгосрочного решения)
- Добавить ON DELETE CASCADE на FK (или ON DELETE SET NULL), тогда СУБД сама удалит дочерние строки.
Пример (Postgres/MySQL, нужно указать имя ограничения):
ALTER TABLE t2 DROP CONSTRAINT fk_t2_t1; -- найти имя ограничения предварительно
ALTER TABLE t2 ADD CONSTRAINT fk_t2_t1 FOREIGN KEY (c22) REFERENCES t1(c11) ON DELETE CASCADE;

Учтите, что изменение схемы — операция, которую надо тестировать и делать с бэкапом.

5) Что не рекомендуется и когда возможно:
- Временно отключать FK — опасно (можно нарушить целостность), требует последующей проверки.
- Если FK создан как DEFERRABLE (Postgres), можно отложить проверку до конца транзакции: SET CONSTRAINTS fk_name DEFERRED; тогда можно удалить/вставить в произвольном порядке в пределах транзакции. Но это работает только если constraint был создан как DEFERRABLE.

6) Полезные команды для диагностики
- Найти имя FK (пример для PostgreSQL / MySQL / SQL Server см. документацию или information_schema/pg_catalog).
- Перед удалением — сделать бэкап и проверить, какие строки будут затронуты:
SELECT * FROM t1 WHERE date < '...';
SELECT * FROM t2 WHERE c22 IN (SELECT c11 FROM t1 WHERE date < '...');

Резюме:
- Правильный порядок — сначала дочерние строки (t2), потом родители (t1).
- Выполняйте в транзакции, либо партиями для больших объёмов.
- Если это регулярная операция, стоит рассмотреть изменение FK на ON DELETE CASCADE или ON DELETE SET NULL (после тестирования и бэкапа).
Сначала удалить соответствующие записи из t2 или установить в них c22 в NULL, в зависимости от задачи, затем удалять записи из t1.
Похожие вопросы