Какие знаешь способы изоляции транзакции?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Уровни изоляции транзакций (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 Read | Non-Repeatable Read | Phantom Read | Производительность |
|---|---|---|---|---|
| READ UNCOMMITTED | ✓ | ✓ | ✓ | Отличная |
| READ COMMITTED | ✗ | ✓ | ✓ | Хорошая |
| REPEATABLE READ | ✗ | ✗ | ✓ | Приемлемая |
| SERIALIZABLE | ✗ | ✗ | ✗ | Низкая |
Заключение
Выбор уровня изоляции — это компромисс между корректностью и производительностью. Для большинства приложений READ COMMITTED оптимален. Для критичных операций используй SERIALIZABLE с блокировками (SELECT FOR UPDATE). Для отчётов и аналитики — READ UNCOMMITTED.