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

Как выглядит транзакция записи в бд?

2.0 Middle🔥 131 комментариев
#Базы данных (SQL)#Soft Skills

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

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

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

Как выглядит транзакция записи в БД

Транзакция — это последовательность SQL операций, которые либо выполняются полностью, либо откатываются полностью. Основана на принципе ACID.

Принцип ACID

A — Atomicity (Атомарность)

  • Транзакция либо полностью выполняется, либо полностью откатывается
  • Нет состояния «частично выполнено»

C — Consistency (Согласованность)

  • После транзакции БД находится в согласованном состоянии
  • Ограничения и правила БД соблюдаются

I — Isolation (Изоляция)

  • Одновременные транзакции не мешают друг другу
  • Уровни изоляции: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE

D — Durability (Долговечность)

  • После коммита данные сохраняются, даже при сбое

Цикл жизни транзакции

import psycopg2

# 1. Подключение
conn = psycopg2.connect("dbname=mydb user=postgres")
cursor = conn.cursor()

# 2. НАЧАЛО ТРАНЗАКЦИИ (неявно начинается с первой команды)
cursor.execute("BEGIN")  # Явное начало

# 3. ОПЕРАЦИИ ЗАПИСИ
cursor.execute("""
    UPDATE accounts 
    SET balance = balance - 100 
    WHERE account_id = 1
""")
print(f"Обновлено строк: {cursor.rowcount}")  # 1

cursor.execute("""
    UPDATE accounts 
    SET balance = balance + 100 
    WHERE account_id = 2
""")
print(f"Обновлено строк: {cursor.rowcount}")  # 1

# 4. КОММИТ (фиксация изменений)
conn.commit()
print("Транзакция успешно выполнена")

# Или откат при ошибке
conn.close()

С обработкой ошибок

def transfer_money(from_id: int, to_id: int, amount: float) -> bool:
    """Переводит деньги между счётами с откатом при ошибке"""
    conn = psycopg2.connect("dbname=mydb user=postgres")
    cursor = conn.cursor()
    
    try:
        # НАЧАЛО ТРАНЗАКЦИИ
        cursor.execute("BEGIN")
        
        # Проверяем баланс
        cursor.execute(
            "SELECT balance FROM accounts WHERE account_id = %s FOR UPDATE",
            (from_id,)
        )
        balance = cursor.fetchone()[0]
        
        if balance < amount:
            raise ValueError("Недостаточно средств")
        
        # Снимаем со счёта 1
        cursor.execute(
            "UPDATE accounts SET balance = balance - %s WHERE account_id = %s",
            (amount, from_id)
        )
        
        # Добавляем на счёт 2
        cursor.execute(
            "UPDATE accounts SET balance = balance + %s WHERE account_id = %s",
            (amount, to_id)
        )
        
        # КОММИТ
        conn.commit()
        print(f"Транзакция успешна: {amount} переведено")
        return True
        
    except Exception as e:
        # ОТКАТ
        conn.rollback()
        print(f"Ошибка! Откат транзакции: {e}")
        return False
        
    finally:
        cursor.close()
        conn.close()

transfer_money(from_id=1, to_id=2, amount=100.0)

С использованием контекстного менеджера (рекомендуется)

import psycopg2
from contextlib import contextmanager

@contextmanager
def transaction(conn):
    """Контекстный менеджер для транзакций"""
    try:
        yield conn.cursor()
        conn.commit()
    except Exception as e:
        conn.rollback()
        raise e

# Использование
conn = psycopg2.connect("dbname=mydb user=postgres")

try:
    with transaction(conn) as cursor:
        cursor.execute("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
        cursor.execute("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
        # Автоматический коммит при выходе
except Exception as e:
    print(f"Транзакция отменена: {e}")
finally:
    conn.close()

SQLAlchemy (ORM)

from sqlalchemy import create_engine, Column, Integer, Float
from sqlalchemy.orm import Session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Account(Base):
    __tablename__ = 'accounts'
    account_id = Column(Integer, primary_key=True)
    balance = Column(Float)

engine = create_engine("postgresql://user:password@localhost/mydb")
SessionLocal = sessionmaker(bind=engine)

def transfer_sqlalchemy(from_id: int, to_id: int, amount: float) -> bool:
    session = SessionLocal()
    
    try:
        # Получаем счёта с блокировкой
        from_account = session.query(Account).filter(
            Account.account_id == from_id
        ).with_for_update().first()
        
        to_account = session.query(Account).filter(
            Account.account_id == to_id
        ).with_for_update().first()
        
        if not from_account or not to_account:
            raise ValueError("Счёт не найден")
        
        if from_account.balance < amount:
            raise ValueError("Недостаточно средств")
        
        # Изменяем балансы
        from_account.balance -= amount
        to_account.balance += amount
        
        # КОММИТ при выходе из контекста
        session.commit()
        print("Транзакция успешна")
        return True
        
    except Exception as e:
        session.rollback()
        print(f"Откат: {e}")
        return False
        
    finally:
        session.close()

transfer_sqlalchemy(from_id=1, to_id=2, amount=100.0)

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

# 1. READ UNCOMMITTED (грязные чтения)
conn.set_isolation_level(0)  # Самый низкий уровень

# 2. READ COMMITTED (по умолчанию в PostgreSQL)
conn.set_isolation_level(1)

# 3. REPEATABLE READ
conn.set_isolation_level(2)

# 4. SERIALIZABLE (самый строгий)
conn.set_isolation_level(3)

Явно в SQL:

cursor.execute("""
    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
""")

cursor.execute("BEGIN")
# транзакция с повышенной изоляцией
cursor.execute("SELECT * FROM accounts WHERE id = 1")
conn.commit()

Savepoints (контрольные точки)

def complex_transaction():
    conn = psycopg2.connect("dbname=mydb user=postgres")
    cursor = conn.cursor()
    
    try:
        cursor.execute("BEGIN")
        
        # Первая часть операции
        cursor.execute("INSERT INTO logs VALUES (%s)", ("Operation 1",))
        
        # Сохранить контрольную точку
        cursor.execute("SAVEPOINT sp1")
        
        # Вторая часть
        cursor.execute("INSERT INTO data VALUES (%s)", ("Data",))
        
        # Откатить только до контрольной точки
        cursor.execute("ROLLBACK TO sp1")
        
        # Коммит только первой операции
        conn.commit()
        
    except Exception as e:
        conn.rollback()
        print(f"Ошибка: {e}")
    finally:
        cursor.close()
        conn.close()

complex_transaction()

Практический чеклист транзакций

✓ Используй ACID для критических операций ✓ Минимизируй время транзакции ✓ Избегай вложенных транзакций (используй SAVEPOINT) ✓ Блокируй ресурсы (FOR UPDATE) перед изменением ✓ Обрабатывай исключения с откатом ✓ Используй контекстные менеджеры для автоматического коммита/отката ✓ Монитори блокировки: SELECT * FROM pg_locks ✓ Для высоконагруженных систем рассмотри более низкий уровень изоляции