JavaRush /Курсы /SQL SELF /Типичные ошибки при работе с транзакциями

Типичные ошибки при работе с транзакциями

SQL SELF
40 уровень , 4 лекция
Открыта

В этой лекции мы сосредоточимся на типичных ошибках, возникающих при работе с транзакциями, и способах их избегания. Поверьте, даже самый блестящий SQL-мастер иногда забывает поставить COMMIT! Мы дадим советы, как превратить ошибки в транзакциях в редкость.

К сожалению (или к счастью), базы данных — это не волшебный замок, где всё всегда работает безупречно. Ошибки в работе с транзакциями — это довольно частое явление, особенно для начинающих разработчиков. Давайте разберем их подробно.

Забытая команда COMMIT или ROLLBACK

Забыть завершить транзакцию — это настоящая "классика жанра". Представьте себе ресторан, где вы заказали блюдо, но официант забыл принести чек. В мире PostgreSQL это означает, что база данных "зависает" в состоянии транзакции, держит ресурсы и блокирует другие операции.

Пример ошибки:

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- Упс! Мы забыли добавить COMMIT или ROLLBACK.

Когда транзакция "зависает", блокировка ресурса может распространяться на всю таблицу. Если администратор базы данных знает, что дело плохо, он может завершить её "жёстким" способом. Но лучше не доводить до этого.

Как избежать?

  • Всегда завершайте транзакцию явно: COMMIT или ROLLBACK.
  • Используйте клиентские инструменты, которые автоматически напоминают о зависших транзакциях.
  • Если транзакция не завершена, а вы перезапускаете приложение, база данных автоматически выполнит ROLLBACK, но это не всегда удобно для состояния системы.

Использование неправильного уровня изоляции

Выбор уровня изоляции может показаться скучной формальностью, но он играет ключевую роль в предотвращении аномалий. Например, если вы используете READ UNCOMMITTED для важной финансовой операции, возможно чтение "грязных" данных, которые позже могут быть отменены.

Пример:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
-- Читаем данные, которые изменяются другой транзакцией
SELECT balance FROM accounts WHERE account_id = 1;
-- Другая транзакция выполнит ROLLBACK, и ваши данные окажутся недействительными.

Как избежать?

  • Определите, насколько важны данные для вашего приложения.
  • Используйте READ COMMITTED для большинства сценариев, чтобы избежать "грязного" чтения.
  • Применяйте более строгие уровни изоляции REPEATABLE READ, SERIALIZABLE для операций, где критично избежать изменений или фантомных данных.

Конфликт транзакций и блокировки

Иногда две или более транзакции пытаются изменить одни и те же данные. В таком случае PostgreSQL блокирует одну из них, пока другая не завершится. Это может привести к ситуации deadlock (взаимной блокировки).

Пример ошибки:

-- Первая транзакция
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;

-- Вторая транзакция
BEGIN;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;
-- Ожидание первой транзакции...

Если обе транзакции удерживают ресурсы, которые нужны друг другу, возникает тупик. PostgreSQL обнаружит тупик, завершит одну из транзакций с ошибкой и выдаст сообщение:

ERROR: deadlock detected

Как избежать?

  • Придерживайтесь фиксированного порядка выполнения операций в транзакциях.
  • Минимизируйте время выполнения транзакции, чтобы уменьшить вероятность блокировки.
  • Используйте уровень изоляции SERIALIZABLE только тогда, когда это действительно необходимо.

Ошибка с SAVEPOINT

SAVEPOINT — замечательный инструмент для частичного отката, но его неправильное использование может привести к путанице. Например, если вы забыли освободить точку сохранения (RELEASE SAVEPOINT), это может привести к лишним блокировкам или ошибкам.

Пример ошибки:

BEGIN;
SAVEPOINT my_savepoint;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
ROLLBACK TO SAVEPOINT my_savepoint;
-- Забыли освободить SAVEPOINT!

Как избежать?

  • Убедитесь, что вы удаляете SAVEPOINT, если он больше не нужен.
  • Старайтесь не создавать слишком много точек сохранения, чтобы не усложнять запросы.

Несовместимость транзакций с внешними системами

Представьте, что транзакция в PostgreSQL пытается взаимодействовать с внешними системами: отправка уведомлений, обновление API и т.д. Если что-то пойдет не так с внешней системой, откатить изменения будет сложно.

Пример:

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- Не удается отправить уведомление: email-server не отвечает.
COMMIT; -- Изменения сохранены, но уведомление не отправлено.

Как избежать?

  • По возможности изолируйте операции взаимодействия с внешними системами.
  • Используйте промежуточные таблицы или очереди задач, чтобы координировать действия с внешними системами.

Ошибки из-за больших транзакций

Большие транзакции, которые включают множество операций, имеют тенденцию быть более уязвимыми к ошибкам: блокировкам, таймаутам и тупикам.

Пример:

BEGIN;
-- Несколько тысяч операций обновления
UPDATE orders SET status = 'completed' WHERE delivery_date < CURRENT_DATE;
COMMIT; -- Может занять значительное время.

Как избежать?

  • Разбивайте большие транзакции на несколько меньших.
  • Используйте батчи для обновления данных.
  • Минимизируйте объём данных, которые изменяются внутри одной транзакции.

Забытая проверка ошибок

Не все SQL-запросы в транзакции могут пройти успешно. Например, если одна из операций выдает ошибку, вся транзакция окажется в состоянии провала.

Пример:

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = -1; -- Ошибка: account_id не существует.
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
COMMIT; -- Не выполняется из-за ошибки.

Как избежать?

  • Всегда проверяйте результат выполнения каждой операции.
  • Используйте обработку ошибок в ваших запросах или клиентском коде.

Неправильное понимание поведения ROLLBACK

Многие разработчики считают, что ROLLBACK отменяет изменения и возвращает всё в исходное состояние. Однако ROLLBACK работает только в рамках текущей транзакции.

Пример заблуждения:

UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
ROLLBACK; -- Ошибка! Это не работает, так как операция не была в транзакции.

Как избежать?

  • Помните: BEGIN — друг, а без него ROLLBACK бессилен.
  • Всегда оборачивайте критические операции в транзакции.
2
Задача
SQL SELF, 40 уровень, 4 лекция
Недоступна
Использование `SAVEPOINT` для частичного отката изменений
Использование `SAVEPOINT` для частичного отката изменений
1
Опрос
Введение в уровни изоляции транзакций, 40 уровень, 4 лекция
Недоступен
Введение в уровни изоляции транзакций
Введение в уровни изоляции транзакций
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Anonymous #3449047 Уровень 61
8 ноября 2025
В лекции при откате к точке сохранения используется rollback to savepoint savepoint_name. Валидатор же распознает это как ошибку и требует rollback to savepoint_name.
Vlad Tagunkov Уровень 56
25 января 2026
даже вебсторм предлагает

ROLLBACK TO SAVEPOINT name;
но валидатор говорит что это ошибка.