Що таке Read Committed?
Другий SELECT повернув інше значення, тому що Транзакція Б закомітила зміни.
🟢 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 на всю транзакцію
Чому це “Золотий стандарт”
- Баланс: Достатня точність даних для більшості бізнес-процесів при мінімальних накладних витратах
- Відсутність блокувань: У MVCC читачі не блокують писателів і навпаки
- Мінімум помилок: На 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 runningxmax: first transaction ID that has not yet startedxip_list: list of in-progress transaction IDs
- Visibility rule: tuple видно, якщо:
xmin< snapshot.xmin (committed before our snapshot)xmaxis not set ORxmax>= 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 UPDATEon 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_fetchedvstup_returnedratios (sequential vs index scans) - Monitor
deadlocks— Read Committed has lowest deadlock rate - Check
temp_filesandtemp_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]]