← Назад к вопросам

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

3.0 Senior🔥 121 комментариев
#DevOps и инфраструктура#Django

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что происходит, если операция внутри транзакции завершается с ошибкой

Когда операция внутри транзакции завершается с ошибкой, поведение зависит от уровня изоляции, типа ошибки и того, как её обработала приложение. В большинстве случаев СУБД выполняет автоматический откат (rollback) всей транзакции.

Автоматический откат

Стандартное поведение: При возникновении ошибки (нарушение constraint, синтаксическая ошибка, deadlock) СУБД автоматически откатывает все изменения, сделанные в этой транзакции.

import sqlite3

conn = sqlite3.connect(':memory:')
cursor = conn.cursor()

# Создаем таблицу
cursor.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT UNIQUE)')
conn.commit()

try:
    cursor.execute('INSERT INTO users (id, name) VALUES (1, "Alice")')
    cursor.execute('INSERT INTO users (id, name) VALUES (2, "Alice")')  # Ошибка UNIQUE
    conn.commit()
except sqlite3.IntegrityError as e:
    print(f'Ошибка: {e}')
    conn.rollback()  # Отката все изменения в транзакции
    print('Транзакция откачена, данные не изменены')

# Проверяем: пользователь Alice не добавлен
cursor.execute('SELECT COUNT(*) FROM users')
print(f'Пользователей в БД: {cursor.fetchone()[0]}')  # 0, не 1

Типы ошибок и их поведение

1. Нарушение ограничений (Constraint Violations):

# UNIQUE constraint
cursor.execute('INSERT INTO users (name) VALUES ("Alice")')
cursor.execute('INSERT INTO users (name) VALUES ("Alice")')  # IntegrityError
# Весь INSERT откатывается

2. Синтаксические ошибки:

try:
    cursor.execute('SELCT * FROM users')  # Опечатка
except sqlite3.OperationalError:
    # Ошибка парсинга, транзакция откатывается
    conn.rollback()

3. Deadlock (конфликт блокировок):

# При deadlock автоматический откат зависит от СУБД
# PostgreSQL: откатывает текущую транзакцию
# MySQL: откатывает последний запрос
try:
    cursor.execute('SELECT * FROM table1 FOR UPDATE')  # Блокировка
except Exception as e:
    conn.rollback()

Состояние транзакции после ошибки

В PostgreSQL:

import psycopg2

conn = psycopg2.connect('dbname=test')
cursor = conn.cursor()

cursor.execute('BEGIN')
cursor.execute('INSERT INTO users VALUES (1, "Alice")')
cursor.execute('INSERT INTO invalid_table VALUES (1)')  # Ошибка

# Транзакция теперь в состоянии "aborted"
print(cursor.connection.get_transaction_status())  # IDLE

# Все последующие запросы в этой транзакции будут игнорироваться
cursor.execute('INSERT INTO users VALUES (2, "Bob")')  # Не выполнится

conn.rollback()  # Откатываем

В SQLite:

# SQLite более простой: при ошибке вся транзакция откатывается
# или остаётся открытой для продолжения
try:
    conn.execute('BEGIN')
    conn.execute('INSERT INTO users VALUES (1, "Alice")')
    conn.execute('INSERT INTO invalid_table VALUES (1)')  # Ошибка
except:
    conn.rollback()  # Откатываем весь BEGIN

Явный контроль с try-except

Правильный паттерн:

from contextlib import contextmanager

@contextmanager
def transaction(conn):
    try:
        yield conn
        conn.commit()
    except Exception as e:
        conn.rollback()
        raise  # Пробрасываем ошибку дальше

# Использование
try:
    with transaction(conn) as conn:
        cursor = conn.cursor()
        cursor.execute('INSERT INTO users VALUES (1, "Alice")')
        cursor.execute('INSERT INTO users VALUES (2, "Alice")')  # Ошибка
except Exception as e:
    print(f'Транзакция не выполнена: {e}')

Уровни изоляции и влияние на откат

READ UNCOMMITTED:

  • Можно видеть грязные данные
  • При откате другой транзакции, эти данные больше не видны

READ COMMITTED (по умолчанию):

# Откат влияет только на эту транзакцию
cursor.execute('SET TRANSACTION ISOLATION LEVEL READ COMMITTED')

REPEATABLE READ / SERIALIZABLE:

# Могут быть deadlock ошибки, требующие отката и повтора
cursor.execute('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE')

Обработка ошибок в микросервисной архитектуре

Saga pattern при откате:

async def process_order(order_id):
    try:
        # Шаг 1: Резервируем товар
        await reserve_item(order_id)
        
        # Шаг 2: Обрабатываем платёж
        await process_payment(order_id)  # Ошибка здесь
        
        # Шаг 3: Подтверждаем
        await confirm_order(order_id)
    except PaymentError:
        # Откатываем Шаг 1
        await cancel_reservation(order_id)
        raise

Важные выводы

  • Автоматический откат — СУБД автоматически откатывает всю транзакцию при ошибке
  • Состояние "aborted" — в PostgreSQL транзакция становится неработающей до rollback
  • Повторить попытку — в случае deadlock нужно повторить всю транзакцию
  • Логирование — всегда логируй ошибки перед откатом для отладки
  • Savepoint — можно использовать для частичного отката в сложных транзакциях

Это гарантирует ACID свойства и предотвращает частичное обновление данных.