Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# READ COMMITTED — уровень изоляции транзакций
READ COMMITTED — это уровень изоляции транзакций в базах данных, который гарантирует, что транзакция будет читать только те данные, которые уже были закоммичены другими транзакциями.
Иерархия уровней изоляции
В стандарте SQL есть четыре уровня (от самого слабого к самому строгому):
- READ UNCOMMITTED — может читать незакоммиченные данные
- READ COMMITTED — может читать только закоммиченные данные ← здесь мы
- REPEATABLE READ — блокирует чтение изменяемых строк
- SERIALIZABLE — полная изоляция, как будто транзакции выполняются одна за другой
Как это работает
Транзакция A | Транзакция B
─────────────────────────────────────
BEGIN |
SELECT * FROM users | BEGIN
(читает: count=100) | UPDATE users SET count=101
| COMMIT ✓ (данные закоммичены)
SELECT * FROM users |
(читает: count=101) ✓ | Транзакция видит новое значение
COMMIT |
Транзакция A прочитала одно и то же поле дважды, получила разные значения. Это возможно на READ COMMITTED.
Гарантии READ COMMITTED
✓ Не читаем Dirty Reads — никогда не прочитаем незакоммиченные данные ✗ Может быть Non-Repeatable Read — одно и то же поле при повторном чтении может измениться ✗ Может быть Phantom Read — количество строк при повторном запросе может измениться
Проблема: Dirty Reads НЕ происходят
# Транзакция A (попытается прочитать)
with transaction.atomic():
time.sleep(0.5)
users = User.objects.all() # Эти данные уже закоммичены!
# Транзакция B (пишет, но ещё не закоммичена)
with transaction.atomic():
user = User.objects.get(id=1)
user.balance -= 100 # Меняем баланс
time.sleep(2) # Медленная операция
user.save() # Ещё не закоммичено
# А не Dirty Reads! Транзакция A не увидит изменение, пока B не сделает COMMIT
Проблема: Non-Repeatable Reads ВОЗМОЖНЫ
# Транзакция A
with transaction.atomic():
user = User.objects.get(id=1)
print(user.balance) # 1000
time.sleep(1) # Даём время для Транзакции B
user.refresh_from_db()
print(user.balance) # 900! Другое значение
# Транзакция B
with transaction.atomic():
time.sleep(0.5) # Даём А прочитать первый раз
user = User.objects.get(id=1)
user.balance -= 100
user.save()
Проблема: Phantom Reads ВОЗМОЖНЫ
# Транзакция A
with transaction.atomic():
count1 = User.objects.filter(city='SPB').count() # 50
time.sleep(1)
count2 = User.objects.filter(city='SPB').count() # 51! Новая строка вставлена
# Транзакция B
with transaction.atomic():
time.sleep(0.5)
User.objects.create(name='Ivan', city='SPB')
В PostgreSQL — особенность
PostgreSQL реализует READ COMMITTED так:
- Каждый statement видит результаты только закоммиченных транзакций
- Внутри одной транзакции последовательные запросы могут видеть разные данные
- Это создаёт Non-Repeatable Reads, но защищает от Dirty Reads
BEGIN;
SELECT balance FROM accounts WHERE id=1; -- Результат: 1000
-- Другая транзакция UPDATE balance = 900 и COMMIT
SELECT balance FROM accounts WHERE id=1; -- Результат: 900 (изменилось!)
COMMIT;
Когда это проблема?
Пример: Перевод денег
def transfer_money(from_account, to_account, amount):
with transaction.atomic():
# Получаем текущий баланс отправителя
sender = Account.objects.get(id=from_account)
print(f"Баланс: {sender.balance}") # 1000
# Кто-то другой снял деньги
# Но мы видим старое значение из своей транзакции
if sender.balance >= amount:
sender.balance -= amount
sender.save()
receiver = Account.objects.get(id=to_account)
receiver.balance += amount
receiver.save()
else:
raise InsufficientFunds()
Здесь может быть race condition! Если два перевода произойдут одновременно, оба могут пройти, хотя баланса не достаточно.
Решение: REPEATABLE READ или SELECT FOR UPDATE
# Способ 1: Повысить уровень изоляции
from django.db import connection
with transaction.atomic():
# PostgreSQL: SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
sender = Account.objects.select_for_update().get(id=from_account)
# Теперь этот объект залочен, никто не сможет изменить
# Способ 2: SELECT FOR UPDATE (работает в READ COMMITTED)
with transaction.atomic():
sender = Account.objects.select_for_update().get(id=from_account)
# Строка залочена эксклюзивно, другие транзакции ждут
sender.balance -= amount
sender.save()
Где используется READ COMMITTED?
- По умолчанию в PostgreSQL, MySQL, Oracle
- Production системы — хороший баланс между производительностью и безопасностью
- Большинство веб-приложений — не требуют serializable уровня
Когда нужен выше уровень?
- Финансовые системы (переводы, платежи)
- Инвентарь (резервирование товаров)
- Конкурентные скидки (чтобы не превышать лимит)
- Консистентность бизнес-правил
Вывод: READ COMMITTED — удачный компромисс между безопасностью и скоростью. Для простых операций читает закоммиченные данные (защита от Dirty Reads). Но для критичных операций нужно использовать SELECT FOR UPDATE или повышать уровень изоляции.