Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Изоляция (Isolation) в базах данных
Изоляция (Isolation) — это один из четырех ключевых принципов ACID, который гарантирует, что одновременные (конкурирующие) транзакции не влияют друг на друга и видят консистентное состояние данных. Это позволяет множеству пользователей работать с БД одновременно без конфликтов.
Проблема параллелизма
Когда несколько транзакций работают одновременно, возникают проблемы:
Транзакция 1 Транзакция 2
├─ Читает Balance = $100
├─ Снимает $30
│ ├─ Читает Balance = $100
│ ├─ Снимает $20
├─ Записывает Balance = $70
│ ├─ Записывает Balance = $80
│ └─ COMMIT
└─ COMMIT
Результат: $80 вместо $50!
Потеряны $20 (Dirty Read, Lost Update)
Четыре уровня изоляции
ПостСКВЕЛ стандарт определяет 4 уровня возрастающей строгости:
1. READ UNCOMMITTED (читать не закоммиченные данные)
# Самый низкий уровень, почти не обеспечивает изоляцию
# Проблемы:
Тр1: UPDATE account SET balance = 50 WHERE id = 1
(не коммитита)
Тр2: SELECT balance FROM account WHERE id = 1 # Видит 50
Тр1: ROLLBACK # Откатывается
Тр2: уже прочитала несуществующее значение (Dirty Read)
# Когда использовать:
# - Очень редко, только для статистики
# - При необходимости максимальной производительности
import psycopg2
conn = psycopg2.connect()
conn.set_isolation_level(0) # READ UNCOMMITTED
2. READ COMMITTED (читать только закоммиченные данные)
# По умолчанию в большинстве БД
# Защита от:
# ✅ Dirty Read
# Остается уязвимость к:
# ❌ Non-Repeatable Read
# ❌ Phantom Read
# Пример Non-Repeatable Read:
Тр1: SELECT salary FROM employee WHERE id = 5 # $50000
(пауза)
Тр2: UPDATE employee SET salary = 60000 WHERE id = 5
Тр2: COMMIT
Тр1: SELECT salary FROM employee WHERE id = 5 # $60000 (!)
# Одно и то же поле — разные значения
# В PostgreSQL:
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED)
3. REPEATABLE READ (повторяемое чтение)
# Средний уровень изоляции
# Защита от:
# ✅ Dirty Read
# ✅ Non-Repeatable Read
# Остается уязвимость к:
# ❌ Phantom Read (добавление новых строк)
# Пример Phantom Read:
Тр1: SELECT COUNT(*) FROM orders WHERE status = 'pending' # 3
(пауза)
Тр2: INSERT INTO orders (status) VALUES ('pending')
Тр2: COMMIT
Тр1: SELECT COUNT(*) FROM orders WHERE status = 'pending' # 4
# Появилась новая строка (phantom)
# В PostgreSQL:
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_REPEATABLE_READ)
4. SERIALIZABLE (сериализуемость)
# Самый строгий уровень — полная изоляция
# Защита от:
# ✅ Dirty Read
# ✅ Non-Repeatable Read
# ✅ Phantom Read
# Все операции выполняются так, как если бы они были одна за другой
# Но может быть медленнее из-за конфликтов
# Пример: две транзакции конкурируют
Тр1: BEGIN SERIALIZABLE
SELECT balance FROM account WHERE id = 1 # $100
Тр2: BEGIN SERIALIZABLE
UPDATE account SET balance = 50 WHERE id = 1
Тр2: COMMIT # успешно
Тр1: UPDATE account SET balance = 80 WHERE id = 1
Тр1: COMMIT # ОШИБКА: конфликт сериализации!
# В PostgreSQL:
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE)
Таблица сравнения
Уровень Dirty Non-Rep Phantom Скорость
READ UNCOMMITTED ❌ ❌ ❌ Очень быстро
READ COMMITTED ✅ ❌ ❌ Быстро
REPEATABLE READ ✅ ✅ ❌ Медленнее
SERIALIZABLE ✅ ✅ ✅ Медленнее
Пример в SQLAlchemy
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from sqlalchemy.orm.exc import StaleDataError
engine = create_engine(
'postgresql://...',
isolation_level='REPEATABLE READ' # Уровень изоляции
)
with Session(engine) as session:
# Все операции в этой сессии используют REPEATABLE READ
user = session.query(User).filter(User.id == 1).first()
user.balance -= 100
session.commit()
Практический пример: обработка платежей
from sqlalchemy.orm import Session
from sqlalchemy import text
def transfer_money(
session: Session,
from_account_id: int,
to_account_id: int,
amount: float
):
"""
Перевод денег между счетами
Требует высокого уровня изоляции
"""
try:
# Начинаем с SERIALIZABLE для критичных операций
session.execute(
text("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE")
)
# Читаем баланс (в одной критичной операции)
from_account = session.query(Account).filter(
Account.id == from_account_id
).with_for_update().first() # Блокируем строку
to_account = session.query(Account).filter(
Account.id == to_account_id
).with_for_update().first()
# Проверяем и переводим
if from_account.balance < amount:
raise ValueError("Insufficient balance")
from_account.balance -= amount
to_account.balance += amount
# Логируем
session.add(Transaction(
from_account_id=from_account_id,
to_account_id=to_account_id,
amount=amount
))
session.commit()
return True
except StaleDataError:
# Конфликт сериализации
session.rollback()
return False
Блокировки (Locking) — часть изоляции
from sqlalchemy import select
# FOR UPDATE — эксклюзивная блокировка
stmt = select(User).filter(User.id == 1).with_for_update()
user = session.execute(stmt).scalar_one()
# Никто другой не может изменять этого пользователя
# FOR SHARE — общая блокировка (только чтение)
stmt = select(User).filter(User.id == 1).with_for_update(read=True)
user = session.execute(stmt).scalar_one()
# Другие могут читать, но не могут писать
Обработка конфликтов
from sqlalchemy.exc import DBAPIError
from sqlalchemy.orm.exc import StaleDataError
def safe_transaction(session: Session):
max_retries = 3
for attempt in range(max_retries):
try:
# Критичная операция
session.execute(
text("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE")
)
# ... операции ...
session.commit()
return True
except (StaleDataError, DBAPIError) as e:
session.rollback()
if attempt < max_retries - 1:
print(f"Retry {attempt + 1}")
continue
else:
raise
Оптимистичная блокировка (Optimistic Locking)
# Альтернатива пессимистичной блокировке
# Добавляем поле версии
from sqlalchemy import Column, Integer
class Product(Base):
__tablename__ = 'products'
id = Column(Integer, primary_key=True)
name = Column(String)
price = Column(Float)
version = Column(Integer, default=0) # Версия
# При обновлении:
def update_price(session: Session, product_id: int, new_price: float):
product = session.query(Product).filter(
Product.id == product_id
).first()
old_version = product.version
product.price = new_price
product.version = old_version + 1
# Проверяем, что версия не изменилась
result = session.execute(
text("""
UPDATE products
SET price = :price, version = :new_version
WHERE id = :id AND version = :old_version
"""),
{
'price': new_price,
'new_version': old_version + 1,
'id': product_id,
'old_version': old_version
}
)
if result.rowcount == 0:
raise ValueError("Version conflict - concurrent update detected")
Выбор уровня изоляции
Используй READ COMMITTED, если:
- Большинство операций читают данные
- Некритичны малые несогласованности
- Нужна максимальная производительность
Используй REPEATABLE READ, если:
- Операции работают с одними и теми же данными
- Нужна хорошая производительность + безопасность
- Фонаво в production
Используй SERIALIZABLE, если:
- Операции критичны (платежи, транзакции)
- Нужна полная гарантия консистентности
- Готов пожертвовать производительностью
Вывод
Изоляция обеспечивает:
- Независимость одновременных транзакций
- Консистентность данных
- Предсказуемое поведение при параллелизме
- Защиту от потери данных
Выбор уровня изоляции — это баланс между безопасностью и производительностью.