Что такое Read Uncommitted
На этом уровне транзакция имеет возможность читать изменения, которые были внесены другой параллельной транзакцией, но еще не были зафиксированы командой COMMIT.
🟢 Junior Level
Read Uncommitted (Чтение незафиксированных данных) — это самый низкий и наименее строгий уровень изоляции транзакций в стандарте SQL.
На этом уровне транзакция имеет возможность читать изменения, которые были внесены другой параллельной транзакцией, но еще не были зафиксированы командой COMMIT.
Главная проблема: Грязное чтение (Dirty Read)
Если транзакция прочитала незафиксированные данные, а транзакция-автор затем отменила свои изменения (ROLLBACK), прочитанные данные оказываются ложными («фантомными»), так как в базе данных их больше не существует.
Время Транзакция А (Списание) Транзакция Б (Read Uncommitted)
| ----------------------- -------------------------------
| T1: BEGIN;
| T2: UPDATE accounts
| SET balance = 500 WHERE id = 1;
| T3: SELECT balance FROM accounts WHERE id = 1;
| -- Результат: 500 (ГРЯЗНОЕ ЧТЕНИЕ!)
| T4: ROLLBACK; -- Откат изменений!
| T5: Транзакция Б уверена, что баланс 500,
v хотя фактически он остался равным 100!
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AnalyticsService {
// В Spring уровень изоляции можно объявить явно:
@Transactional(isolation = Isolation.READ_UNCOMMITTED, readOnly = true)
public long getApproximateActiveUsersCount() {
// Допустимо только для приблизительной фоновой телеметрии,
// где погрешность в несколько процентов не играет роли.
return userRepository.countActiveUsers();
}
}
Аналогия: Представьте черновик отчета на столе у коллеги. Вы подсмотрели цифру в черновике, пока он вышел на обед, и включили ее в презентацию клиенту. Вернувшись с обеда, коллега перечеркнул черновик и выбросил его в урну. Вы презентовали клиенту выдуманные данные.
🟡 Middle Level
Особенности поведения в различных СУБД
- PostgreSQL и Oracle:
В этих СУБД уровень
Read Uncommittedфизически отсутствует. Если в PostgreSQL выполнить:SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;СУБД примет команду без синтаксической ошибки (ради совместимости со стандартом SQL), но внутренне выполнит ее на уровне Read Committed. Механизм MVCC в PostgreSQL архитектурно отсекает незакоммиченные кортежи по карте статусов транзакций (
pg_xact). - MySQL (InnoDB):
Реально поддерживает
Read Uncommitted. В этом режиме InnoDB читает последнюю версию строки прямо из буферного пула оперативной памяти, не утруждая себя переходом по цепочкам Undo Log (откатных сегментов). - Microsoft SQL Server:
Поведение
Read Uncommittedэквивалентно использованию знаменитого табличного хинтаWITH (NOLOCK):SELECT * FROM Orders WITH (NOLOCK);Запрос читает страницы без запроса разделяемых блокировок (Shared Locks / S-locks), игнорируя наложенные другими потоками монопольные блокировки (Exclusive Locks / X-locks).
Допускаемые аномалии
| Аномалия | Допускается на Read Uncommitted? |
|---|---|
| Грязное чтение (Dirty Read) | ⚠️ Да |
| Неповторяющееся чтение (Non-Repeatable Read) | ⚠️ Да |
| Фантомное чтение (Phantom Read) | ⚠️ Да |
| Потерянное обновление (Lost Update) | ⚠️ Да |
| Грязная запись (Dirty Write) | ❌ НЕТ (Запрещена всегда) |
🔴 Senior Level
Почему Dirty Write ($G_0$) запрещен даже на Read Uncommitted?
Стандарт ANSI SQL допускает на уровне Read Uncommitted все виды аномалий чтения, но категорически запрещает аномалию Dirty Write (Грязная запись):
Если бы СУБД позволяла транзакции $T_2$ перезаписать данные, измененные незакоммиченной транзакцией $T_1$, при последующем откате $T_1$ механизм Undo Log не смог бы понять, какое значение восстанавливать, что привело бы к физическому повреждению базы данных (Database Corruption).
Поэтому во всех СУБД (включая MySQL и SQL Server) любые операции модификации (UPDATE, DELETE, INSERT) всегда накладывают монопольные блокировки (Exclusive X-Locks) до конца транзакции, делая одновременную несинхронизированную запись в одну строку невозможной даже на Read Uncommitted.
Аппаратные аномалии SQL Server: WITH (NOLOCK) и Page Splitting
В дисковых страничных движках с блокировками (SQL Server) чтение на уровне Read Uncommitted / NOLOCK чревато не просто логическими ошибками бизнес-данных, но и структурными аномалиями выборки (Allocation Order Scans):
- Пропуск строк (Skipped Rows): Пока транзакция-читатель сканирует страницу по порядку физического размещения на диске, параллельная транзакция вставляет запись. Происходит расщепление страницы (B-Tree Page Split): половина строк переносится на новую страницу, которая уже осталась позади указателя сканирования. Читатель пропускает часть существующих строк.
- Дублирование строк (Duplicate Rows): Если при Page Split данные переносятся на страницу, до которой сканер еще не дошел, читатель вернет одну и ту же запись дважды.
- Ошибка 601 (Index Corruption Exception): Если параллельная транзакция инициирует удаление или реорганизацию страниц индекса, сканирующий запрос аварийно завершится с системной ошибкой доступа к страницам памяти.
4 Tricky Questions
1. Почему в PostgreSQL установка SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; не позволяет прочитать «грязные» данные?
Ответ:
В движке PostgreSQL механизм изоляции полностью основан на MVCC (Multi-Version Concurrency Control).
Каждая строка (Tuple) в файле таблицы хранит заголовок с полями xmin (ID создавшей транзакции) и xmax (ID удалившей транзакции).
При любом чтении PostgreSQL сверяет xmin строки с битовой картой статусов транзакций в оперативной памяти (pg_xact). Если транзакция xmin имеет статус IN_PROGRESS (еще не закоммичена) или ABORTED, ядро СУБД аппаратно признает эту версию строки невидимой.
Чтобы реализовать грязное чтение, разработчикам PostgreSQL пришлось бы намеренно усложнить код проверки видимости ради сомнительного антипаттерна. Поэтому в PostgreSQL уровень Read Uncommitted синтаксически принимается, но работает строго как Read Committed.
2. Дает ли READ UNCOMMITTED ощутимый выигрыш в скорости в MySQL InnoDB по сравнению с READ COMMITTED?
Ответ:
Практически нет.
В MySQL InnoDB чтение на уровне Read Committed не требует установки shared-блокировок — оно также неблокирующее и обращается к кэшу памяти (Buffer Pool).
Единственная микроскопическая разница: на уровне Read Committed движок при обнаружении незакоммиченной строки разматывает цепочку Undo Log до последней закоммиченной версии (создает Read View), а на Read Uncommitted читает текущие байты страницы без размотки Undo-лога. На современных многоядерных серверах разница в производительности составляет доли процента, что абсолютно не оправдывает колоссальный риск порчи бизнес-логики.
3. Разрешает ли JPA / Hibernate использовать уровень изоляции Read Uncommitted в методах репозитория?
Ответ:
Да, аннотация @Transactional(isolation = Isolation.READ_UNCOMMITTED) поддерживается Spring и транслируется в JDBC-драйвер через вызов connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED).
Однако использование этого уровня в приложениях с Hibernate влечет фатальную опасность для кэша первого уровня (Persistence Context). Если Hibernate прочитает «грязную» сущность, которая затем будет откачена в БД, этот грязный объект осядет в кэше первого уровня текущей сессии EntityManager. Любые последующие запросы или автоматический flush в этой сессии сохранят некорректные данные обратно в базу данных.
4. В каких редких production-сценариях использование READ UNCOMMITTED (или NOLOCK) все еще считается оправданным?
Ответ: Существует всего три легитимных сценария:
- Приблизительные мониторинговые дашборды (Telemetry): Сбор метрик типа «сколько примерно сообщений находится в очереди брокера» или «примерное число активных пользователей онлайн за сутки», где допустима погрешность в $\pm 5\%$, но критически важно не заблокировать пишущие транзакции на старых СУБД с блокировочником (SQL Server).
- Экстренная диагностика на проде (DBA Trouble-shooting): Когда база данных заблокирована длинными транзакциями, администратор выполняет быстрый
SELECT ... WITH (NOLOCK)для выяснения состояния зависших процессов, чтобы сам диагностический запрос не встал в конец очереди на блокировку. - Логи и аудит-архивы: Аналитические отчеты по таблицам аудита (Append-Only), где операции
UPDATEиDELETEзапрещены триггерами, и новые строки только добавляются.
🎯 Шпаргалка для интервью
- Суть Read Uncommitted: Самый слабый уровень; разрешает чтение незафиксированных данных других транзакций (Dirty Read).
- Где отсутствует: В PostgreSQL и Oracle этого уровня нет (автоматически повышается до
Read Committedбез предупреждений). - Аномалии: Разрешены Dirty Read, Non-Repeatable Read, Phantom Read, Lost Update.
- Dirty Write ($G_0$): Строго запрещен даже на Read Uncommitted (монопольные X-блокировки на запись берутся всегда во всех СУБД).
- SQL Server
NOLOCKопасность: Чтение без Shared Locks может приводить к дублированию или пропуску строк при расщеплении страниц (Page Splits). - Production правило: Не использовать в бизнес-логике и транзакционных сервисах.