Транзакции и ACID

Рейтинг: 55.2% · 12 голосов
Подробный курс по PostgreSQL: SQL по демобазе, типы данных и JSONB, индексы, транзакции и MVCC, VACUUM, EXPLAIN и планировщик, роли и привилегии, репликация, бэкап, настройка под нагрузку и под 1С. Актуально на 2026.
Ответить
Аватара пользователя
Egor_DBA
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Транзакции и ACID

Сообщение Egor_DBA »

Оглавление курса (47)
  1. Что такое PostgreSQL: история, философия, где применяется
  2. Урок 1. Что нового в PostgreSQL 15 и далее (15 -> 16 -> 17)
  3. Установка и первый запуск: Linux, Windows, Docker
  4. psql детально: метакоманды и работа в консоли
  5. Демобаза Авиаперевозки Postgres Pro: структура и развёртывание
  6. SELECT: проекция, фильтрация, сортировка
  7. Транзакции и ACID (вы здесь)
  8. Уровни изоляции транзакций: Read Committed, Repeatable Read, Serializable
  9. Соединения таблиц (JOIN)
  10. Агрегация: GROUP BY, HAVING, GROUPING SETS
  11. Подзапросы и CTE (WITH), рекурсия
  12. Оконные функции
  13. Изменение данных: INSERT/UPDATE/DELETE, UPSERT, MERGE
  14. Числовые и булевы типы, NULL и трёхзначная логика
  15. Строки, текст, дата и время, таймзоны
  16. Массивы, диапазоны, enum, составные типы
  17. JSON и JSONB: операторы и индексация
  18. DDL: CREATE TABLE, схемы, ALTER
  19. Ограничения целостности
  20. Представления и материализованные представления
  21. Последовательности, IDENTITY, генерируемые столбцы
  22. Индексы: B-tree и когда индекс не используется
  23. Типы индексов: Hash, GiST, SP-GiST, GIN, BRIN
  24. Продвинутые индексы: частичные, по выражению, покрывающие
  25. Обслуживание индексов и таблиц: bloat, REINDEX, CONCURRENTLY
  26. Полнотекстовый поиск
  27. Роли и пользователи
  28. Привилегии: GRANT/REVOKE
  29. Row-Level Security и безопасность данных
  30. Подключение приложения: pg_hba.conf, SSL, пулы соединений
  31. Серверное программирование: функции и процедуры
  32. Триггеры и события
  33. Архитектура PostgreSQL: процессы и память
  34. MVCC: версии строк и видимость
  35. VACUUM, autovacuum, freeze и wraparound
  36. WAL, контрольные точки и долговечность
  37. Блокировки и взаимоблокировки
  38. Планировщик запросов
  39. EXPLAIN и EXPLAIN ANALYZE: чтение планов
  40. Конфигурация сервера: ключевые параметры
  41. Настройка под нагрузку и под 1С
  42. Параллельные запросы и партиционирование
  43. Резервное копирование и восстановление
  44. Репликация: потоковая и hot standby
  45. Логическая репликация и кластерные решения
  46. Внешние данные, расширения и сертификация
  47. Разбор планов запросов на explain.tensor.ru: глубокое чтение EXPLAIN ANALYZE
Урок 6. Транзакции и ACID

Представьте, что вы списываете деньги с карты пассажира и в этот же момент создаёте ему бронирование. Если деньги ушли, а бронь не записалась (или наоборот), у вас битые данные и злой клиент. Транзакция в postgresql - это способ сказать базе: вот пачка изменений, примени их все или не применяй ни одного. В этом уроке разберём, что стоит за словом ACID, как работают BEGIN, COMMIT и ROLLBACK, почему psql по умолчанию коммитит каждую команду отдельно, и как откатить часть работы через SAVEPOINT, не теряя остальное. Примеры - на демобазе Авиаперевозки.

Изображение

Как это работает

Транзакция - это логическая единица работы. Внутри неё может быть один INSERT, а может десяток UPDATE по разным таблицам. Снаружи всё это выглядит как одно неделимое действие: либо мир увидел все изменения сразу, либо не увидел ничего.

Четыре буквы ACID описывают гарантии, которые даёт СУБД. Атомарность (A) - все операции транзакции применяются целиком или откатываются целиком, промежуточного состояния для других нет. Согласованность (C) - транзакция переводит базу из одного корректного состояния в другое, не нарушая ограничений целостности (первичные ключи, внешние ключи, CHECK, NOT NULL). Изоляция (I) - параллельные транзакции не лезут друг другу в недоделанную работу, каждая работает как будто она одна. Долговечность (D) - после COMMIT данные уже не пропадут, даже если сервер сразу выключат из розетки.

За долговечность в PostgreSQL отвечает WAL - журнал предзаписи. Прежде чем менять страницы данных на диске, сервер пишет в журнал запись об изменении и сбрасывает её на диск при коммите. Если питание пропало, при следующем старте PostgreSQL проигрывает журнал и восстанавливает все подтверждённые транзакции. Поэтому COMMIT - это не просто слово, а гарантия, подкреплённая записью на диск.

Изоляцию PostgreSQL обеспечивает через MVCC - многоверсионность. Каждая строка имеет версии, и транзакция видит снимок данных на свой момент. Незакоммиченные чужие изменения вам не видны - грязного чтения тут не бывает в принципе. Глубоко уровни изоляции разберём в следующем уроке, сейчас важно понять: ваша незавершённая транзакция изолирована от остальных.

Теперь про автокоммит. В psql каждый отдельный SQL-оператор по умолчанию выполняется в своей маленькой транзакции и сразу коммитится. Это удобно для одиночных команд, но опасно, когда нужно несколько шагов как одно целое. Чтобы объединить их, вы явно открываете транзакцию командой BEGIN, делаете работу и закрываете её через COMMIT (применить) или ROLLBACK (откатить всё). Между BEGIN и COMMIT автокоммита нет.

SAVEPOINT - это именованная точка внутри транзакции. Сделав сейвпойнт, вы можете откатиться к нему через ROLLBACK TO, отменив только то, что было после, и продолжить транзакцию дальше. Это частичный откат: вся транзакция при этом ещё жива.

Важная особенность PostgreSQL: если внутри транзакции возникла ошибка, вся транзакция переходит в состояние aborted, и любой следующий запрос вернёт ошибку про current transaction is aborted, пока вы не сделаете ROLLBACK или не откатитесь к сейвпойнту. Это частые грабли новичков и боль при миграции postgresql для 1с, где привыкли к более снисходительному поведению других СУБД.

SQL и примеры

Базовый перевод денег между двумя бронями - классический пример атомарности. Либо обе строки изменятся, либо ни одна.

Код: Выделить всё

BEGIN;
UPDATE bookings SET total_amount = total_amount - 10000
  WHERE book_ref = '0824C5';
UPDATE bookings SET total_amount = total_amount + 10000
  WHERE book_ref = '00044C';
COMMIT;
Если между двумя UPDATE упадёт сервер, после рестарта вы увидите либо обе строки изменёнными, либо обе старыми - половинчатого списания не будет.

Откат, когда передумали. Проверяем сумму, она нас не устраивает - отменяем все изменения транзакции:

Код: Выделить всё

BEGIN;
UPDATE ticket_flights SET amount = amount * 1.10
  WHERE flight_id = 1;
-- посмотрели, поняли что подняли цену не тому рейсу
ROLLBACK;
После ROLLBACK таблица в исходном состоянии, будто ничего не было.

Частичный откат через SAVEPOINT. Создаём бронь, потом два билета, но второй билет ошибочный - откатываем только его:

Код: Выделить всё

BEGIN;
INSERT INTO bookings (book_ref, book_date, total_amount)
  VALUES ('FF00AA', now(), 0);

INSERT INTO tickets (ticket_no, book_ref, passenger_id, passenger_name)
  VALUES ('0005550000001', 'FF00AA', '1234 567890', 'IVAN PETROV');

SAVEPOINT before_second;

INSERT INTO tickets (ticket_no, book_ref, passenger_id, passenger_name)
  VALUES ('0005550000001', 'FF00AA', '0000 000000', 'DUP');  -- дубль ticket_no, ошибка

ROLLBACK TO before_second;   -- отменили только проблемный билет
COMMIT;                       -- бронь и первый билет сохранены
В итоге зафиксированы бронь и первый билет, а ошибочная вставка испарилась. Без сейвпойнта ошибка перевела бы транзакцию в aborted и COMMIT сработал бы как ROLLBACK.

Посмотреть свой текущий идентификатор транзакции и убедиться, что вы внутри неё:

Код: Выделить всё

BEGIN;
SELECT pg_current_xact_id();   -- в PG 13+; раньше txid_current()
COMMIT;
Если транзакция ничего не писала, ей могут вообще не выдать настоящий xid - PostgreSQL экономит идентификаторы для читающих транзакций.

Частые грабли
  • Забыли COMMIT и ушли с открытой транзакцией - она держит блокировки и старые версии строк, мешает другим и раздувает работу для vacuum. Долгая открытая транзакция - враг здоровья базы.
  • Ошибка внутри BEGIN переводит транзакцию в aborted, и все следующие команды падают с current transaction is aborted. Лечится ROLLBACK или ROLLBACK TO savepoint, а не повтором запроса.
  • Думают, что ROLLBACK откатит уже закоммиченную транзакцию. Нет: после COMMIT обратной дороги нет, только новая компенсирующая транзакция.
  • Полагаются на автокоммит psql там, где нужна атомарность нескольких шагов. Многошаговую логику всегда оборачивайте в BEGIN/COMMIT.
  • Внутри транзакции выполнили TRUNCATE или DDL и решили, что это нельзя откатить. В PostgreSQL почти весь DDL транзакционный и откатывается ROLLBACK - приятное отличие от ряда других СУБД.
  • Путают ROLLBACK (откат всей транзакции) и ROLLBACK TO savepoint (частичный откат). Второй не закрывает транзакцию.
Мини-лаба
  • Подключитесь к демобазе: psql -d demo. Проверьте режим автокоммита командой \echo :AUTOCOMMIT (в psql он включён по умолчанию).
  • Откройте транзакцию: BEGIN; и выполните UPDATE bookings SET total_amount = total_amount + 1 WHERE book_ref = (SELECT book_ref FROM bookings LIMIT 1);
  • В другом терминале откройте второй psql и сделайте SELECT той же строки - убедитесь, что изменение пока не видно (изоляция).
  • Вернитесь в первый сеанс, сделайте SAVEPOINT sp1; и ещё один UPDATE, затем ROLLBACK TO sp1; - проверьте, что второй UPDATE отменён, а первый жив.
  • Сделайте намеренную ошибку (вставьте строку с дублирующимся ключом), убедитесь, что транзакция перешла в aborted и следующий SELECT падает.
  • Выполните ROLLBACK; - вся транзакция отменена. Проверьте в обоих сеансах, что total_amount вернулся к исходному.
  • Повторите сценарий, но завершите COMMIT вместо ROLLBACK, и убедитесь, что изменение теперь видно второму сеансу.
Контрольные вопросы
  • Что означает каждая буква в ACID и какой компонент PostgreSQL отвечает за долговечность?
  • Чем отличается поведение psql с автокоммитом от работы внутри явного BEGIN/COMMIT?
  • Что произойдёт с транзакцией после ошибки в одном из её запросов и как из этого состояния выйти?
  • В чём разница между ROLLBACK и ROLLBACK TO SAVEPOINT?
  • Почему долго открытая транзакция вредна для базы и при чём тут vacuum?
  • Можно ли в PostgreSQL откатить DDL-команду (например CREATE TABLE), выполненную внутри транзакции?
👍2 ❤️1 🔥 😄 🤔2
Аватара пользователя
envoy13
Сообщения: 1
Зарегистрирован: 14 май 2026, 00:01

Re: Транзакции и ACID

Сообщение envoy13 »

А правда что в постгресе даже CREATE TABLE откатывается? В мускуле привык что DDL коммитит молча и ROLLBACK уже не помогает, тут аж непривычно.
👍 ❤️ 🔥1 😄 🤔
Аватара пользователя
gpukun
Сообщения: 1
Зарегистрирован: 15 май 2026, 09:08

Re: Транзакции и ACID

Сообщение gpukun »

Поймал current transaction is aborted и сидел тупил почему все запросы падают. Оказалось надо ROLLBACK сделать, а не повторять INSERT по сто раз)
👍 ❤️1 🔥 😄 🤔1
Ответить
← Предыдущая глава
SELECT: проекция, фильтрация, сортировка
Следующая глава →
Уровни изоляции транзакций: Read Committed, Repeatable Read, Serializable

Все главы курса «PostgreSQL: от первого запроса до продакшена»

Поделиться темой: ✈ Telegram VK
  • Похожие темы
Похожие запросы: уровни изоляции транзакций и блокировки в postgresql

Вернуться в «PostgreSQL: от первого запроса до продакшена»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя