Что такое 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]]