JavaRush /Курсы /SQL SELF /Работа с транзакциями для обеспечения целостности данных

Работа с транзакциями для обеспечения целостности данных

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

Давайте представим, что вы пишете приложение для интернет-магазина, и во время оплаты заказа вам нужно:

  1. Удержать деньги с карты клиента.
  2. Уменьшить количество товара на складе.
  3. Создать запись об успешной транзакции.

Что будет, если посреди этих действий что-то пойдет не так? Например, товар на складе закончился после того, как деньги были удержаны, но до создания записи о заказе? Всё пойдет кувырком: деньги "зависли", заказ не завершен, а ваш сервер получает тонны гневных писем (и, возможно, судебных исков).

Транзакции как раз помогают избежать таких ситуаций. Они позволяют сгруппировать несколько операций в одну "атомарную" единицу работы с базой данных. Это как кнопка "Отмена" в текстовом редакторе: если что-то пошло не так, просто откатитесь к началу.

Как транзакции обеспечивают целостность данных?

Транзакции основаны на концепции ACID:

  • Атомарность (Atomicity) — Все операции внутри транзакции выполняются либо полностью, либо ничего не выполняется. "Всё или ничего".
  • Согласованность (Consistency) — Данные остаются в согласованном состоянии до и после выполнения транзакции.
  • Изоляция (Isolation) — Одна транзакция не мешает другим.
  • Долговечность (Durability) — Когда транзакция завершена, её результат сохранен даже в случае сбоя системы.

Почему я это опять повторяю? Потому что это тот идеал, к которому все стремятся. И... который редко достижим. Когда мы второй раз вернемся к транзакциям в нашем курсе, вы поймете, что некоторыми ACID-принципами нам придется пожертвовать.

Так что наслаждайтесь временем, когда транзакции такие простые и красивые. И давайте уже перейдем к примерам!

Пример использования транзакций

Давайте рассмотрим сценарий добавления студента и регистрации его на курс.

Допустим, мы работаем с базой данных университета. У нас появились внештатные слушатели наших курсов. Если на курс есть место, тогда мы такого слушателя регистрируем как студента (временно) и добавляем на курс. Вот как это будет происходить.

При добавлении нового студента в базу и его регистрации на курс нам нужно:

  1. Добавить запись в таблицу students.
  2. Создать запись в таблице enrollments, связывающей студента с курсом.

Если что-то пойдет не так (например, курс уже заполнен), мы должны откатить операцию, чтобы данные не разошлись между таблицами. Вот как это делается:

-- Начало транзакции
BEGIN;

-- Шаг 1: Добавляем студента
INSERT INTO students (name, age, gender)
VALUES ('Otto Lin', 20, 'Male')
RETURNING id;

-- Допустим, вернулся id = 10

-- Шаг 2: Регистрируем его на курс
INSERT INTO enrollments (student_id, course_id)
VALUES (10, 5);

-- Всё прошло успешно? Фиксируем изменения
COMMIT;

Что происходит, если возникает ошибка?

Вдруг произошла ошибка при регистрации на курс: например, курс не существует. Если вы забудете про транзакцию, то запись о студенте в таблице students останется, а в таблице enrollments — нет. Это нарушает целостность данных. Чтобы этого избежать, мы можем использовать команду ROLLBACK.

-- Начало транзакции
BEGIN;

-- Шаг 1: Добавляем студента
INSERT INTO students (name, age, gender)
VALUES ('Otto Lin', 20, 'Male')
RETURNING id;

-- Шаг 2: Пытаемся зарегистрировать его на курс
INSERT INTO enrollments (student_id, course_id)
VALUES (10, 999); -- Ошибка: курса с id = 999 не существует!

-- Откатываем все изменения
ROLLBACK;

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

Использование SAVEPOINT для контроля

Теперь представим более сложный сценарий. Вы хотите выполнить несколько операций, но на каком-то этапе нужно откатиться только до определенной точки, а не отменять весь процесс.

Давайте реализуем пошаговую регистрацию студента

-- Начало транзакции
BEGIN;

-- Добавляем студента
SAVEPOINT add_student; -- Создаём точку сохранения
INSERT INTO students (name, age, gender)
VALUES ('Anna Song', 22, 'Female');

-- Регистрируем её на первый курс
SAVEPOINT enroll_course_1; -- Ещё одна точка сохранения
INSERT INTO enrollments (student_id, course_id)
VALUES (11, 5);

-- Регистрируем её на второй курс (ошибка здесь)
INSERT INTO enrollments (student_id, course_id)
VALUES (11, 999); -- Ошибка!

-- Откатываемся только к последней точке сохранения
ROLLBACK TO enroll_course_1;

-- Восстанавливаем процесс
INSERT INTO enrollments (student_id, course_id)
VALUES (11, 6);

-- Фиксируем изменения
COMMIT;

Таким образом, ошибки в одной части процесса не мешают сохранению данных в других.

Проверка на наличие изменений

Если SQL-запрос что-то меняет, то есть возможность проверить, были реально какие-то изменения или нет.

Может же быть ситуация, когда мы выполняли DELETE, но ни одна строка не попала под WHERE. Или выполняли UPDATE, а данные уже были изменены и по факту ничего не поменялось.

На этот случай есть специальная системная переменная FOUND. Которая указывает, были ли затронуты строки в последнем SQL-запросе:

  • FOUND = TRUE — запрос что-то обновил/удалил;
  • FOUND = FALSE — ничего не удалено или не изменено.

С обычным SELECT она не работает, только для отслеживания изменений.

Практическое применение: обработка платежей

Транзакции особенно полезны в финансовых приложениях. Давайте снова возьмемся за систему, которая должна перевести деньги с одного счета на другой.

-- Начало транзакции
BEGIN;

-- Шаг 1: Снимаем деньги с первого счета
UPDATE accounts
SET balance = balance - 100
WHERE id = 1 AND balance >= 100;

-- Шаг 2: Проверяем, что операция успешна (строки были изменены)
IF NOT FOUND THEN
    ROLLBACK; -- Откат, если средств недостаточно
    RAISE EXCEPTION 'Недостаточно средств!'; -- Ошибка! Кидаем исключение
END IF;

-- Шаг 3: Добавляем деньги на второй счет
UPDATE accounts
SET balance = balance + 100
WHERE id = 2;

-- Фиксируем транзакцию
COMMIT;

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

Особенности и типичные ошибки

Забыт COMMIT: если в конце транзакции забыть выполнить COMMIT, база данных будет "жить в ожидании", а изменения не сохранятся.

Забыт WHERE: обновление или удаление данных без условия может привести к катастрофическим последствиям. Например, команда DELETE FROM students без WHERE удалит всех студентов.

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

Транзакции — это ваш единственный друг, когда дело доходит до обеспечения целостности данных. Они помогают избежать неконсистентности, особенно в сложных сценариях, таких как регистрация пользователей, обработка платежей или обновление связанных таблиц. Освоив работу с командами BEGIN, COMMIT, ROLLBACK и SAVEPOINT, вы сможете создавать более надежные и безопасные приложения.

2
Задача
SQL SELF, 22 уровень, 2 лекция
Недоступна
Основы использования транзакций
Основы использования транзакций
Комментарии (10)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
BF 89 Уровень 22
5 марта 2026
вот как решить данную задачу, если не проходили конструкции
BF 89 Уровень 22
5 марта 2026
ну очень странно отрабатывала система задание
Анатолий Уровень 49
7 февраля 2026
вот такой код устроил валидатор:

BEGIN TRY
    -- 1. Начинаем транзакцию явным образом
    BEGIN TRANSACTION;

    -- 2. Попытка вставить данные
    INSERT INTO students (name, age)
    VALUES ('Иван Иванов', 20);

    -- 3. Если всё прошло успешно, фиксируем изменения
    COMMIT TRANSACTION;
    PRINT 'Данные успешно сохранены';
END TRY
BEGIN CATCH
    -- 4. Проверяем, есть ли активная транзакция, которую можно откатить
    IF @@TRANCOUNT > 0
    BEGIN
        ROLLBACK TRANSACTION;
        PRINT 'Произошла ошибка. Транзакция откачена';
    END;

    -- 5. Выводим детали ошибки (опционально)
    SELECT
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
2 августа 2025
Не понятна суть задания. ПРоходит проверку при

-- Начало транзакции
BEGIN;

-- Попытка вставить нового студента
INSERT INTO students(name, age) VALUES('Иван Иванов', 20);

-- Если вставка успешна, фиксируем транзакцию

COMMIT;
Хотя в условиях указано что необходимо использовать ROLLBACK;
Евгений Уровень 49 Expert
11 августа 2025
Потому что rollback происходит автоматически в случае, если в рамках транзакции произошла ошибка. Ключевое слово ROLLBACK используется, когда мы хотим прервать транзакцию вручную, т.е. с точки зрения БД всё корректно, но мы сами видим какое-то несоответствие (как в примере с NOT FOUND) и откатываем транзакцию с использованием ROLLBACK.
Slevin Уровень 56
14 сентября 2025
Automatic rollback in a database transaction upon an error depends on several factors, including the specific database system, its configuration, and the type of error. General Behavior: Not universally automatic: Transactions are not always automatically rolled back as soon as an error occurs. The default behavior in many systems is to only roll back the specific statement that caused the error, allowing the transaction to potentially continue. В лекциях была серьезная ошибка про этот "автоматический" ROLLBACK.
Slevin Уровень 56
14 сентября 2025
Вообще вся тема с BEGIN / COMMIT / ROLLBACK - очень плохо понимается через IDE, поскольку в них это реализовано другим образом, в WEB-STORM, например, функционал задается через переключение в Transaction: Manual, который по-умолчанию стоит в AUTO и действительно сам отменяет изменения если произошла ошибка и сам коммитит если нет. Но му и BEGIN не нужен... Попробуйте переключить Transaction -> Manual, и поработать с базой - увидите, что данные не будут вноситься, пока вы не нажмете на зеленую галочку (COMMIT) или стрелку (ROLLBACK) Для полного понимания процесса - попробуйте пообщаться с базой данных через консоль - psql. Также это будет работать и из вашего языка программирования через создание соединения и отправки запросов.
Евгений Уровень 49 Expert
18 сентября 2025
Почти так, но не совсем. Вот ссылка, где это немного затрагивается. Вообще, это зависит от БД, но в Postgres, если мы явно не указываем BEGIN, то транзакция создаётся автоматически для каждого выражения (и автоматически завершается после выполнения выражения). Когда мы включает режим Manual в WebStorm, то наоборот, клиент начинает автоматически выполнять BEGIN перед нашим выражением, заставляя нас явно делать COMMIT: ссылка. Я попробовал создать таблицу через psql, и это не требует ручного управления транзакцией. Но да, если перед созданием таблицы вручную открыть транзакцию, то её потом также придётся вручную закрывать 😏
Natalya Уровень 46
25 сентября 2025
у меня не проходит в их варианте. Уже столько всего перепробовала
Anonymous #3449047 Уровень 61
12 октября 2025
что они хотят в этой задаче? сделала точно так же, не проходит проверку, с rollback аналогично теоретической части тоже