Вопрос 4 · Раздел 11

Что такое Read Committed?

Второй SELECT вернул другое значение, потому что Транзакция Б закоммитила изменения.

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

Read Committed — это уровень изоляции, при котором транзакция видит только те данные, которые были официально подтверждены (COMMIT) другими транзакциями.

Основные свойства

  • Грязное чтение: Запрещено — вы никогда не увидите незакоммиченные данные, потому что СУБД показывает только те версии строк, чья транзакция-автор уже сделала COMMIT.
  • Неповторяющееся чтение: Возможно — один и тот же запрос может вернуть разные результаты
  • Фантомное чтение: Возможно — при повторном запросе могут появиться новые строки

Простой пример

Транзакция А: SELECT balance FROM accounts WHERE id = 1; -- Вернул 100
Транзакция Б: UPDATE accounts SET balance = 200 WHERE id = 1; COMMIT;
Транзакция А: SELECT balance FROM accounts WHERE id = 1; -- Вернул 200!

Второй SELECT вернул другое значение, потому что Транзакция Б закоммитила изменения.

Где используется

Это уровень по умолчанию в PostgreSQL, Oracle и SQL Server. Самый распространённый уровень для обычных приложений.


🟡 Middle Level

Механизм реализации через MVCC

В базах данных с MVCC (PostgreSQL), на уровне Read Committed снимок (Snapshot) данных создаётся перед каждым запросом внутри транзакции.

Ключевое отличие от Repeatable Read:

  • Read Committed: новый snapshot перед каждым оператором
  • Repeatable Read: один snapshot на всю транзакцию

Почему это “Золотой стандарт”

  1. Баланс: Достаточная точность данных для большинства бизнес-процессов при минимальных накладных расходах
  2. Отсутствие блокировок: В MVCC читатели не блокируют писателей и наоборот
  3. Минимум ошибок: На Read Committed СУБД никогда не прерывает транзакцию из-за конфликтов изоляции (в отличие от Serializable). Единственные ошибки — violation constraints или app-level логика.

Аномалии на этом уровне

Non-repeatable Read

-- Транзакция А (Read Committed)
BEGIN;
SELECT price FROM products WHERE id = 1;  -- 100
-- Другая транзакция: UPDATE products SET price = 150 WHERE id = 1; COMMIT;
SELECT price FROM products WHERE id = 1;  -- 150 (изменилось!)
COMMIT;

Почему? На Read Committed каждый SELECT получает свой snapshot. Между первым и вторым SELECT другая транзакция успела закоммитить изменения, и второй snapshot их видит.

Phantom Read

-- Транзакция А
BEGIN;
SELECT COUNT(*) FROM users WHERE age > 20;  -- 5 строк
-- Другая транзакция: INSERT INTO users (age) VALUES (25); COMMIT;
SELECT COUNT(*) FROM users WHERE age > 20;  -- 6 строк (появилась новая!)
COMMIT;

Как избежать аномалий

Если нужна стабильность данных внутри транзакции:

  • SELECT … FOR UPDATE: Блокирует строки до конца транзакции
  • Повысить уровень до Repeatable Read: Для всей транзакции

Особенности в PostgreSQL

На уровне Read Committed каждый оператор видит snapshot базы данных на момент начала выполнения этого оператора, а не на момент начала всей транзакции.


🔴 Senior Level

MVCC Snapshot Mechanics в PostgreSQL

How Snapshots Work Internally

  • Каждый snapshot содержит:
    • xmin: earliest transaction ID that is still running
    • xmax: first transaction ID that has not yet started
    • xip_list: list of in-progress transaction IDs
  • Visibility rule: tuple видна, если:
    • xmin < snapshot.xmin (committed before our snapshot)
    • xmax is not set OR xmax >= snapshot.xmin (not yet deleted)

Read Committed Snapshot Behavior

  • New snapshot created for each statement, not each transaction
  • This means BETWEEN two SELECTs in same transaction, another transaction can commit changes
  • The second SELECT will see those changes because it gets a fresh snapshot

Performance Characteristics

Advantages

  • Zero blocking: Readers never block writers, writers never block readers
  • No serialization failures: Unlike Serializable, no transaction aborts due to conflicts
  • Minimal overhead: Snapshot creation is cheap (just captures current txid state)

Trade-offs

  • Non-repeatable reads: Application must handle potentially different results
  • Phantom reads: Range queries can return different row counts
  • Business logic complexity: Multi-step calculations may need explicit locking

SELECT FOR UPDATE — Deep Dive

SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
  • Acquires exclusive row-level lock
  • Other transactions attempting FOR UPDATE on same row will block
  • Prevents both non-repeatable reads AND lost updates for that row
  • Lock held until transaction ends (COMMIT/ROLLBACK)
  • Can cause deadlocks if not used carefully

When to use:

  • Read-modify-write patterns (balance calculations)
  • When you need Repeatable Read behavior for specific rows only
  • Avoiding lost update without changing isolation level

Production Patterns

Pattern 1: Optimistic approach (default Read Committed)

@Transactional // Uses Read Committed
public void transfer(Long fromId, Long toId, BigDecimal amount) {
    // If conflict occurs, handle at application level
    Account from = repo.findById(fromId);
    Account to = repo.findById(toId);
    // ... logic
}

Pattern 2: Pessimistic approach (FOR UPDATE)

@Query("SELECT a FROM Account a WHERE a.id = :id FOR UPDATE")
Account findByIdForUpdate(@Param("id") Long id);

@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount) {
    Account from = repo.findByIdForUpdate(fromId); // Locked!
    Account to = repo.findByIdForUpdate(toId);     // Locked!
    // Safe to modify
}

Read Committed vs Read Committed Snapshot (SQL Server)

SQL Server has two flavors:

  • Read Committed (locking): Uses shared locks, readers can block writers
  • Read Committed Snapshot (RCSI): Uses row versioning like PostgreSQL MVCC

PostgreSQL always uses the MVCC approach — no locking-based Read Committed exists.

Monitoring and Tuning

  • Track tup_fetched vs tup_returned ratios (sequential vs index scans)
  • Monitor deadlocks — Read Committed has lowest deadlock rate
  • Check temp_files and temp_bytes — complex queries on RC may need more temp space
  • Long-running transactions on RC don’t cause bloat (unlike RR/Serializable)

When NOT to Use Read Committed

  • Financial invariants requiring multi-step verification
  • Reports that must be self-consistent
  • When application logic assumes stable reads
  • Distributed transactions requiring strict consistency

🎯 Шпаргалка для интервью

Обязательно знать:

  • Read Committed — уровень по умолчанию в PostgreSQL, Oracle, SQL Server
  • Запрещает dirty reads, но допускает non-repeatable и phantom reads
  • Каждый SELECT получает новый snapshot — два SELECT в одной транзакции могут вернуть разные данные
  • Реализуется через MVCC — читатели не блокируют писателей
  • SELECT … FOR UPDATE блокирует строки для предотвращения lost updates
  • 90% приложений корректно работают на Read Committed

Частые уточняющие вопросы:

  • Чем Read Committed отличается от Repeatable Read? — RC: snapshot на каждый оператор, RR: один snapshot на транзакцию
  • Как избежать non-repeatable read на RC? — SELECT FOR UPDATE или повысить до RR
  • Почему RC — хороший дефолт? — Баланс защиты и производительности, нет serialization failures
  • Что такое EvalPlanQual в PostgreSQL? — Механизм re-evaluation WHERE clause при конкурентном UPDATE

Красные флаги (НЕ говорить):

  • “Read Committed = полная изоляция” — допускает non-repeatable и phantom reads
  • “Два SELECT в транзакции всегда вернут одинаковый результат” — на RC это не так
  • “RC требует блокировок” — MVCC обходится без блокировок при чтении

Связанные темы:

  • [[2. Какие уровни изоляции транзакций существуют]]
  • [[8. Что такое неповторяющееся чтение (Non-Repeatable Read)]]
  • [[9. Что такое фантомное чтение (Phantom Read)]]
  • [[11. Какой уровень изоляции по умолчанию в PostgreSQL]]