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

Что такое READ COMMITTED?

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

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

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

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

# READ COMMITTED — уровень изоляции транзакций

READ COMMITTED — это уровень изоляции транзакций в базах данных, который гарантирует, что транзакция будет читать только те данные, которые уже были закоммичены другими транзакциями.

Иерархия уровней изоляции

В стандарте SQL есть четыре уровня (от самого слабого к самому строгому):

  1. READ UNCOMMITTED — может читать незакоммиченные данные
  2. READ COMMITTED — может читать только закоммиченные данные ← здесь мы
  3. REPEATABLE READ — блокирует чтение изменяемых строк
  4. 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 или повышать уровень изоляции.