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

Что такое вложенная транзакция?

1.0 Junior🔥 61 комментариев
#Другое

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

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

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

Вложенная транзакция (Nested Transaction / Savepoint)

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

Основная идея

Транзакция
├─ Операция 1
├─ Операция 2
│  ├─ SAVEPOINT 1
│  ├─ Операция 2.1
│  ├─ Операция 2.2
│  └─ ROLLBACK TO SAVEPOINT 1  ← откат только операций 2.1 и 2.2
├─ Операция 3
└─ COMMIT  ← коммитятся операции 1, 3 и первая часть 2

Основные команды

# SQL синтаксис
BEGIN;                      # Начало транзакции
  INSERT INTO users ...     # Операция 1
  SAVEPOINT sp1;            # Точка сохранения
  INSERT INTO logs ...      # Операция 2
  ROLLBACK TO SAVEPOINT sp1;  # Откат операции 2
  INSERT INTO audit ...     # Операция 3
COMMIT;                     # Коммитятся операции 1 и 3

Пример в Python с psycopg2

import psycopg2

conn = psycopg2.connect("dbname=mydb")
cur = conn.cursor()

try:
    cur.execute("BEGIN")
    
    # Основная операция
    cur.execute("INSERT INTO users (name) VALUES (%s)", ('John',))
    
    # Точка сохранения
    cur.execute("SAVEPOINT sp1")
    
    try:
        # Рискованная операция
        cur.execute("INSERT INTO risky_table VALUES (%s)", (data,))
    except Exception as e:
        print(f"Ошибка: {e}")
        # Откатываем только до SAVEPOINT
        cur.execute("ROLLBACK TO SAVEPOINT sp1")
    
    # Эта операция выполнится в любом случае
    cur.execute("INSERT INTO audit_log (action) VALUES (%s)", ('user_created',))
    
    # Коммитим всё
    cur.execute("COMMIT")
    conn.commit()
    
except Exception as e:
    cur.execute("ROLLBACK")
    conn.rollback()
    print(f"Критическая ошибка: {e}")

SQLAlchemy с вложенными транзакциями

from sqlalchemy import create_engine, event
from sqlalchemy.orm import Session

engine = create_engine('postgresql://...')
session = Session(engine)

try:
    # Основная операция
    user = User(name='John')
    session.add(user)
    session.flush()  # Выполняет операцию
    
    # Вложенная транзакция (nested)
    with session.begin_nested():
        try:
            # Рискованная операция
            session.add(RiskyTable(data=value))
            session.flush()
        except Exception:
            # Откатывается только код внутри begin_nested
            pass
    
    # Эта операция выполнится
    session.add(AuditLog(action='user_created'))
    
    # Коммитим
    session.commit()

except Exception as e:
    session.rollback()
    print(f"Ошибка: {e}")

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

from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from decimal import Decimal

def process_order(session: Session, order_id: int):
    """
    Обработать заказ с проверками и откатами
    """
    try:
        # 1. Основная операция — обновляем статус заказа
        order = session.query(Order).filter(Order.id == order_id).first()
        order.status = 'processing'
        session.flush()
        
        # 2. Вложенная транзакция для платежа
        with session.begin_nested():
            try:
                # Проверяем баланс
                user = order.user
                if user.balance < order.total_amount:
                    raise ValueError("Insufficient balance")
                
                # Снимаем деньги
                user.balance -= order.total_amount
                
                # Создаем запись о платеже
                payment = Payment(
                    order_id=order_id,
                    amount=order.total_amount,
                    status='completed'
                )
                session.add(payment)
                session.flush()
            except ValueError as e:
                # Откатываем только платеж, заказ остается
                print(f"Платеж не прошел: {e}")
                raise  # Пробрасываем исключение
        
        # 3. Если платеж прошел — обновляем статус
        order.status = 'paid'
        
        # 4. Логируем действие
        session.add(AuditLog(order_id=order_id, action='payment_processed'))
        
        # Коммитим всё
        session.commit()
        return True
        
    except Exception as e:
        session.rollback()
        print(f"Критическая ошибка: {e}")
        return False

Вложенные транзакции внутри друг друга

from sqlalchemy.orm import Session

def complex_operation(session: Session):
    try:
        # Операция 1
        session.add(Item1())
        session.flush()
        
        # Первая вложенная транзакция
        with session.begin_nested():
            session.add(Item2())
            session.flush()
            
            # Вложенная внутри вложенной
            with session.begin_nested():
                try:
                    session.add(Item3())  # Может упасть
                    session.flush()
                except:
                    # Откатываем только Item3
                    pass
            
            # Item2 остается, Item3 откачен
            session.add(Item4())
        
        # Item1, Item2, Item4 коммитятся
        session.commit()
    
    except Exception:
        session.rollback()

Когда использовать вложенные транзакции

1. Обработка опциональных операций

def create_user_with_profile(session: Session, user_data, profile_data):
    try:
        # Основная операция
        user = User(**user_data)
        session.add(user)
        session.flush()
        
        # Опциональная операция
        with session.begin_nested():
            try:
                profile = Profile(**profile_data, user_id=user.id)
                session.add(profile)
                session.flush()
            except ValueError:
                # Профиль не создаст, но пользователь будет
                pass
        
        session.commit()
    except:
        session.rollback()

2. Обработка ошибок в цикле

def import_users(session: Session, users_list):
    imported = 0
    errors = 0
    
    for user_data in users_list:
        with session.begin_nested():
            try:
                user = User(**user_data)
                session.add(user)
                session.flush()
                imported += 1
            except Exception as e:
                print(f"Error importing {user_data}: {e}")
                errors += 1
    
    session.commit()
    return imported, errors

3. Условные операции

def transfer_money(session: Session, from_user_id, to_user_id, amount):
    try:
        # Основная операция
        transaction = Transaction(
            from_user_id=from_user_id,
            to_user_id=to_user_id,
            amount=amount
        )
        session.add(transaction)
        session.flush()
        
        # Условная операция — отправить уведомление
        with session.begin_nested():
            try:
                notification = Notification(
                    user_id=to_user_id,
                    message=f"Получил {amount}"
                )
                session.add(notification)
                session.flush()
            except:
                # Если уведомление не прошло — не критично
                pass
        
        session.commit()
    except:
        session.rollback()

Важные моменты

1. Savepoints работают только внутри транзакции

# ❌ Неправильно
conn.execute("SAVEPOINT sp1")  # Ошибка: нет транзакции

# ✅ Правильно
conn.execute("BEGIN")
conn.execute("SAVEPOINT sp1")

2. Количество уровней вложенности ограничено

# Можно делать вложенные транзакции, но глубина может быть ограничена
# Обычно 10-20 уровней — более чем достаточно

3. ROLLBACK откатывает всю транзакцию

# ROLLBACK — откатывает всё
# ROLLBACK TO SAVEPOINT — откатывает до конкретной точки

4. Разные БД — разная поддержка

# PostgreSQL — полная поддержка вложенных транзакций
# MySQL — с InnoDB поддерживает
# SQLite — поддерживает
# Oracle — поддерживает
# SQL Server — поддерживает

Отладка вложенных транзакций

from sqlalchemy import event
from sqlalchemy.engine import Engine

@event.listens_for(Engine, "before_cursor_execute")
def before_cursor_execute(conn, cursor, statement, parameters, context, executemany):
    print(f"Executing: {statement}")

# Теперь видно все команды SQL, включая SAVEPOINT

Вывод

Вложенные транзакции — это мощный инструмент для:

  • Обработки ошибок без отката всей работы
  • Опциональных операций
  • Обработки больших объемов данных
  • Сложной бизнес-логики с проверками

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

Что такое вложенная транзакция? | PrepBro