Питання 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]]