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

Какие знаешь способы изоляции транзакции?

2.3 Middle🔥 151 комментариев
#Базы данных (SQL)

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

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

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

Уровни изоляции транзакций (Transaction Isolation Levels)

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

Проблемы конкурентности

Эти проблемы решаются разными уровнями изоляции:

Dirty Read (грязное чтение)

Одна транзакция читает незафиксированные изменения другой транзакции:

# Транзакция A
BEGIN;
UPDATE users SET balance = balance - 100 WHERE id = 1;
# (баланс = 900, но не зафиксирована)

# Параллельно транзакция B (Dirty Read)
BEGIN;
SELECT balance FROM users WHERE id = 1;  # Видит 900
ROLLBACK;  # Откатывается
# Транзакция B видела несуществующие данные!
COMMIT;  # A откатывается

Non-Repeatable Read (неповторяющееся чтение)

Одна транзакция читает одни и те же данные несколько раз, и они изменяются между чтениями:

# Транзакция A
BEGIN;
SELECT balance FROM users WHERE id = 1;  # 1000

# Параллельно транзакция B
BEGIN;
UPDATE users SET balance = 500 WHERE id = 1;
COMMIT;

# Назад в транзакцию A
SELECT balance FROM users WHERE id = 1;  # 500 (изменилось!)
COMMIT;

Phantom Read (фантомное чтение)

Между двумя чтениями в одной транзакции другая транзакция добавляет/удаляет строки:

# Транзакция A
BEGIN;
SELECT COUNT(*) FROM orders WHERE status = "pending";  # 5

# Параллельно транзакция B
BEGIN;
INSERT INTO orders VALUES (6, "pending", ...);
COMMIT;

# Назад в транзакцию A
SELECT COUNT(*) FROM orders WHERE status = "pending";  # 6
COMMIT;  # Кол-во строк изменилось!

Уровни изоляции

1. READ UNCOMMITTED (самый слабый)

Одна транзакция может читать незафиксированные изменения другой.

Проблемы: Dirty Read, Non-Repeatable Read, Phantom Read

from sqlalchemy.pool import StaticPool
from sqlalchemy import create_engine

engine = create_engine(
    "sqlite:///:memory:",
    connect_args={"isolation_level": "READ UNCOMMITTED"},
    poolclass=StaticPool,
)

Когда использовать: Редко, только когда производительность критичнее корректности (отчёты, аналитика).

2. READ COMMITTED (по умолчанию в большинстве БД)

Транзакция видит только зафиксированные изменения.

Проблемы: Non-Repeatable Read, Phantom Read (но НЕТ Dirty Read)

engine = create_engine(
    "postgresql://user:pass@localhost/db",
    connect_args={"isolation_level": "READ COMMITTED"},
)

Когда использовать: Для большинства приложений. Балан между производительностью и корректностью.

3. REPEATABLE READ

Транзакция видит снимок (snapshot) данных на момент её начала. Одни и те же данные всегда читаются одинаково.

Проблемы: Phantom Read (но НЕТ Dirty Read и Non-Repeatable Read)

engine = create_engine(
    "postgresql://user:pass@localhost/db",
    connect_args={"isolation_level": "REPEATABLE READ"},
)
# Пример: REPEATABLE READ
BEGIN ISOLATION LEVEL REPEATABLE READ;

SELECT balance FROM users WHERE id = 1;  # 1000

# Другая транзакция меняет balance на 500 и коммитит
# Но эта транзакция по-прежнему видит 1000

SELECT balance FROM users WHERE id = 1;  # Всё ещё 1000
COMMIT;

4. SERIALIZABLE (самый строгий)

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

Нет проблем: Ни Dirty Read, ни Non-Repeatable Read, ни Phantom Read

engine = create_engine(
    "postgresql://user:pass@localhost/db",
    connect_args={"isolation_level": "SERIALIZABLE"},
)

Когда использовать: Критичные операции (финансы, инвентарь), где нужна абсолютная корректность.

Минус: Низкая производительность при высокой нагрузке.

Механизмы реализации

Блокировки (Locking)

Постгресль использует блокировки для изоляции:

BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;  # Блокирует строку
-- Другие транзакции не могут менять эту строку
COMMIT;

MVCC (Multi-Version Concurrency Control)

Это современный подход (PostgreSQL, MySQL с InnoDB). Вместо блокировок каждая транзакция видит свой снимок данных:

# PostgreSQL использует MVCC
# Две транзакции могут видеть разные версии одной строки
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT * FROM users;  # Видит снимок на момент BEGIN
COMMIT;

Практические примеры с Python + SQLAlchemy

Безопасный банковский перевод

from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession

async def transfer_money(
    session: AsyncSession,
    from_user_id: int,
    to_user_id: int,
    amount: float,
):
    # SERIALIZABLE для критичной операции
    async with session.begin(
        isolation_level="SERIALIZABLE"
    ):
        # Блокируем обе строки
        await session.execute(
            text("SELECT 1 FROM users WHERE id = :id FOR UPDATE"),
            {"id": from_user_id}
        )
        await session.execute(
            text("SELECT 1 FROM users WHERE id = :id FOR UPDATE"),
            {"id": to_user_id}
        )
        
        # Проверяем баланс
        from_balance = await session.scalar(
            text("SELECT balance FROM users WHERE id = :id"),
            {"id": from_user_id}
        )
        
        if from_balance < amount:
            raise ValueError("Insufficient funds")
        
        # Переводим
        await session.execute(
            text("UPDATE users SET balance = balance - :amount WHERE id = :id"),
            {"amount": amount, "id": from_user_id}
        )
        await session.execute(
            text("UPDATE users SET balance = balance + :amount WHERE id = :id"),
            {"amount": amount, "id": to_user_id}
        )

Отчёты (READ UNCOMMITTED)

async def generate_report(session: AsyncSession):
    async with session.begin(
        isolation_level="READ UNCOMMITTED"
    ):
        # Читаем даже незафиксированные данные
        # Быстро, но может быть неточный отчёт
        result = await session.execute(
            text("SELECT * FROM large_table")
        )
        return result.fetchall()

Таблица сравнения

УровеньDirty ReadNon-Repeatable ReadPhantom ReadПроизводительность
READ UNCOMMITTEDОтличная
READ COMMITTEDХорошая
REPEATABLE READПриемлемая
SERIALIZABLEНизкая

Заключение

Выбор уровня изоляции — это компромисс между корректностью и производительностью. Для большинства приложений READ COMMITTED оптимален. Для критичных операций используй SERIALIZABLE с блокировками (SELECT FOR UPDATE). Для отчётов и аналитики — READ UNCOMMITTED.

Какие знаешь способы изоляции транзакции? | PrepBro