Що таке 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 Troubleshooting): Коли база даних заблокована довгими транзакціями, адміністратор виконує швидкий
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-блокування на запис беруться завжди в усіх СУБД).
- Небезпека
NOLOCKу SQL Server: Читання без Shared Locks може призводити до дублювання або пропуску рядків під час розщеплення сторінок (Page Splits). - Production правило: Не використовувати в бізнес-логіці та транзакційних сервісах.
Червоні прапорці (чого уникати)
- Твердження, що Read Uncommitted суттєво прискорює запити в MVCC-базах (у Postgres його немає, а в MySQL різниця незначна).
- Використання Read Uncommitted у бізнес-логіці, фінансових розрахунках або замовленнях.
- Незнання того, що брудний запис (Dirty Write) заборонений на всіх рівнях ізоляції без винятку.
- Твердження, що
WITH (NOLOCK)у SQL Server абсолютно безпечний для звичайних SELECT-запитів.