Комментарии (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
Вывод
Вложенные транзакции — это мощный инструмент для:
- Обработки ошибок без отката всей работы
- Опциональных операций
- Обработки больших объемов данных
- Сложной бизнес-логики с проверками
Они позволяют писать более надежный и гибкий код, обрабатывая ошибки на нужном уровне детализации.