Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Изоляция в ACID
Изоляция (Isolation) — это четвёртый принцип ACID (Atomicity, Consistency, Isolation, Durability), который гарантирует, что одновременно выполняемые транзакции не влияют друг на друга и не видят незавершённые изменения друг друга.
Что такое ACID
ACID — это набор свойств, которые гарантируют надёжность транзакций в базе данных:
- Atomicity (Атомарность) — транзакция либо полностью выполняется, либо полностью откатывается
- Consistency (Консистентность) — база данных переходит из одного согласованного состояния в другое
- Isolation (Изоляция) — одновременные транзакции не влияют друг на друга
- Durability (Долговечность) — завершённая транзакция остаётся в базе даже при сбое
Проблема конкурентности без изоляции
Без правильной изоляции возникают следующие проблемы:
Dirty Read (грязное чтение):
Транзакция 1 | Транзакция 2
---------------------------------
БЕГИН
| НАЧАЛО
УПДАЙТ balance = 100
| SELECT balance
| Видит 100 (незавершённое изменение!)
ROLLBACK |
(откат на 50) | COMMIT
Транзакция 2 прочитала значение, которое было откачено
Non-repeatable Read (неповторяющееся чтение):
Транзакция 1 | Транзакция 2
---------------------------------
БЕГИН
SELECT balance = 100
| UPDATE balance = 200
| COMMIT
SELECT balance = 200
Одна транзакция видит разные значения при двух чтениях
Phantom Read (фантомное чтение):
Транзакция 1 | Транзакция 2
---------------------------------
BEGIN
SELECT * FROM users
WHERE age > 20
(результат: 5 пользователей)
| INSERT INTO users VALUES (...)
| COMMIT
SELECT * FROM users
WHERE age > 20
(результат: 6 пользователей!)
Добавились новые строки, которые соответствуют условию
Уровни изоляции
БД предлагают различные уровни изоляции для баланса между безопасностью и производительностью:
1. READ UNCOMMITTED (Чтение незавершённых данных)
# Наименьший уровень изоляции
# Позволяет dirty reads
# Практически не используется
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import Session
engine = create_engine(
"postgresql://user:password@localhost/db",
isolation_level="READ UNCOMMITTED"
)
# Транзакция может видеть незавершённые изменения других транзакций
2. READ COMMITTED (Чтение завершённых данных)
# Умолчальный уровень в PostgreSQL и Oracle
# Предотвращает dirty reads
# Позволяет non-repeatable reads и phantom reads
engine = create_engine(
"postgresql://user:password@localhost/db",
isolation_level="READ COMMITTED"
)
# Транзакция видит только завершённые изменения
with Session(engine) as session:
user = session.query(User).filter(User.id == 1).first()
# Если другая транзакция обновила user — получим новое значение
3. REPEATABLE READ (Повторяемое чтение)
# Предотвращает dirty reads и non-repeatable reads
# Позволяет phantom reads
engine = create_engine(
"postgresql://user:password@localhost/db",
isolation_level="REPEATABLE READ"
)
with Session(engine) as session:
users = session.query(User).filter(User.age > 20).all()
# Если другая транзакция добавит пользователя — мы его не увидим
# (но новые строки в другом запросе всё ещё могут появиться)
4. SERIALIZABLE (Сериализуемость)
# Наивысший уровень изоляции
# Предотвращает все проблемы: dirty reads, non-repeatable reads, phantom reads
# Самый медленный, так как транзакции выполняются практически последовательно
engine = create_engine(
"postgresql://user:password@localhost/db",
isolation_level="SERIALIZABLE"
)
with Session(engine) as session:
# Гарантирует, что транзакция видит данные такими, как если бы
# все другие транзакции выполнялись либо до, либо после неё
balance = session.query(Account).filter(Account.id == 1).first()
balance.amount -= 100
session.commit()
Таблица уровней изоляции
Уровень изоляции | Dirty Read | Non-rep. Read | Phantom Read
-----------------------------------------------------------------
READ UNCOMMITTED | Да | Да | Да
READ COMMITTED | Нет | Да | Да
REPEATABLE READ | Нет | Нет | Да
SERIALIZABLE | Нет | Нет | Нет
Реализация в PostgreSQL
from sqlalchemy import create_engine, text
from sqlalchemy.orm import Session
engine = create_engine("postgresql://user:password@localhost/db")
# Установка уровня изоляции
with engine.connect() as connection:
# Для текущей сессии
connection.execute(text(
"SET TRANSACTION ISOLATION LEVEL REPEATABLE READ"
))
# Выполнение операции
result = connection.execute(text("SELECT * FROM users"))
data = result.fetchall()
connection.commit()
Пример с использованием SQLAlchemy
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker
from sqlalchemy.exc import SQLAlchemyError
SessionLocal = sessionmaker(bind=engine)
def transfer_money(from_account_id: int, to_account_id: int, amount: float):
"""Перевод денег между счётами с изоляцией SERIALIZABLE"""
session = SessionLocal()
try:
# Устанавливаем уровень изоляции
session.connection().connection.isolation_level = "SERIALIZABLE"
# Получаем счёты
from_account = session.query(Account).filter(
Account.id == from_account_id
).with_for_update().first() # FOR UPDATE блокирует строку
to_account = session.query(Account).filter(
Account.id == to_account_id
).with_for_update().first()
# Проверяем баланс
if from_account.balance < amount:
raise ValueError("Недостаточно средств")
# Выполняем операцию
from_account.balance -= amount
to_account.balance += amount
session.commit()
return True
except SQLAlchemyError as e:
session.rollback()
raise
finally:
session.close()
Блокировки для обеспечения изоляции
Пессимистическая блокировка (FOR UPDATE):
# Блокирует строку для других транзакций
from sqlalchemy import select
user = session.query(User).filter(
User.id == 1
).with_for_update().first() # Блокировка на чтение
user.balance += 100
session.commit()
Оптимистичная блокировка (версионирование):
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
version = Column(Integer, default=1) # Версия для оптимистичной блокировки
def update_name(self, new_name: str, session):
# Проверяем версию перед обновлением
current = session.query(User).filter(User.id == self.id).first()
if current.version != self.version:
raise ValueError("Data was modified")
self.name = new_name
self.version += 1
session.commit()
Best practices
- Выбирай правильный уровень изоляции — SERIALIZABLE медленный, но безопасный
- Используй блокировки FOR UPDATE — для критичных операций (перевод денег)
- Минимизируй время транзакции — чем дольше транзакция, тем больше конфликтов
- Тестируй race conditions — используй concurrent тесты для проверки
- Документируй требования — какой уровень изоляции нужен для каждой операции
Вывод: Изоляция — это ключевое свойство для надёжности данных при одновременной работе. Выбор правильного уровня изоляции критичен для баланса между безопасностью и производительностью.