Что происходит, если операция внутри транзакции завершается с ошибкой?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что происходит, если операция внутри транзакции завершается с ошибкой
Когда операция внутри транзакции завершается с ошибкой, поведение зависит от уровня изоляции, типа ошибки и того, как её обработала приложение. В большинстве случаев СУБД выполняет автоматический откат (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 свойства и предотвращает частичное обновление данных.