Открыть сервис

Уровень изолированности Read Committed

Read Committed — это один из уровней изолированности транзакций в системах управления базами данных (СУБД), определяющий правила видимости изменений, внесённых параллельно выполняющимися транзакциями. В соответствии с этим уровнем, транзакция может читать только те данные, которые были зафиксированы (committed) до момента начала её выполнения. Это гарантирует, что чтение «грязных» данных (dirty reads) — то есть незафиксированных изменений другой транзакции — невозможно. Read Committed является стандартным уровнем изолированности по умолчанию во многих реляционных СУБД, включая Microsoft SQL Server, Oracle Database и PostgreSQL (в последнем — при стандартных настройках).

История и стандартизация

Концепция уровней изолированности транзакций была формализована в 1992 году в стандарте SQL-92 (ISO/IEC 9075). В этом стандарте были определены четыре уровня изолированности: Read Uncommitted, Read Committed, Repeatable Read и Serializable. Уровень Read Committed был введён как компромисс между производительностью и согласованностью данных: он предотвращает «грязное» чтение, но допускает другие аномалии, такие как неповторяющееся чтение и фантомное чтение.

До стандартизации различные СУБД реализовывали изоляцию транзакций по-своему. Например, в ранних версиях Oracle (до 1990-х годов) использовалась модель, близкая к Read Committed, но с некоторыми отличиями. Стандарт SQL-92 закрепил Read Committed как один из трёх уровней, допускающих аномалии (в отличие от Serializable, который их полностью исключает).

Принцип работы

Основная идея Read Committed заключается в том, что каждая транзакция видит только те данные, которые были зафиксированы до момента её начала. Если другая транзакция изменяет строку, но не завершает её фиксацией, то для читающей транзакции эта строка остаётся неизменной (видна старая версия). Если же изменяющая транзакция фиксируется, то читающая транзакция, начавшаяся позже, увидит новое значение.

Механизмы реализации

Реализация Read Committed зависит от архитектуры СУБД. Существует два основных подхода:

  1. Блокировки (Locking-based): При чтении данных транзакция устанавливает разделяемые блокировки (shared locks) на читаемые строки, которые удерживаются до завершения операции чтения. Запись данных требует исключительных блокировок (exclusive locks), которые удерживаются до конца транзакции. Это предотвращает чтение незафиксированных данных, так как пишущая транзакция блокирует строки, и читающая транзакция не может получить разделяемую блокировку до фиксации пишущей. Этот подход используется, например, в Microsoft SQL Server (при стандартном поведении).
  1. Многоверсионность (MVCCMulti-Version Concurrency Control): Каждая транзакция работает с версией данных, соответствующей моменту её начала. СУБД хранит несколько версий одной строки (снимки состояния). При чтении транзакция получает снимок данных, зафиксированных на момент её начала. Пишущая транзакция создаёт новую версию строки, которая становится видимой только после её фиксации. Этот подход используется в PostgreSQL, Oracle, MySQL (с движком InnoDB) и многих других СУБД. MVCC позволяет избежать блокировок при чтении, что повышает производительность в системах с высокой конкуренцией.

Аномалии, допускаемые Read Committed

Уровень Read Committed не предотвращает все аномалии, связанные с параллельным выполнением транзакций. Основные из них:

Неповторяющееся чтение (Non-repeatable Read)

Аномалия возникает, когда в рамках одной транзакции один и тот же запрос выполняется дважды, и между этими выполнениями другая транзакция фиксирует изменения (обновление или удаление) данных, которые были прочитаны первым запросом. В результате второй запрос возвращает другой набор данных, чем первый. Например, транзакция A читает баланс счёта (1000 рублей), затем транзакция B фиксирует списание 200 рублей, и при повторном чтении транзакция A видит 800 рублей. Это может привести к некорректным вычислениям, если транзакция A полагается на стабильность данных.

Фантомное чтение (Phantom Read)

Более сложная аномалия, при которой в рамках одной транзакции повторное выполнение запроса с тем же условием (например, WHERE amount > 500) возвращает строки, которые не были видны при первом выполнении. Это происходит, когда другая транзакция фиксирует вставку новых строк, удовлетворяющих условию запроса. Например, транзакция A читает все строки с суммой больше 500 (получает 2 строки), затем транзакция B вставляет новую строку с суммой 600 и фиксирует её, и при повторном чтении транзакция A видит уже 3 строки. Фантомное чтение отличается от неповторяющегося тем, что меняется не значение существующих строк, а их количество.

Отсутствие «грязного» чтения (Dirty Read)

Read Committed гарантирует отсутствие «грязного» чтения — чтения данных, которые ещё не зафиксированы. Если транзакция A изменяет строку, но не фиксирует её, то транзакция B, работающая на уровне Read Committed, не увидит это изменение. Если транзакция A откатится, то транзакция B не будет работать с несуществующими данными.

Сравнение с другими уровнями изолированности

Уровень Read Committed занимает промежуточное положение в иерархии уровней изолированности по стандарту SQL-92.

Уровень изолированности«Грязное» чтениеНеповторяющееся чтениеФантомное чтение
Read UncommittedВозможноВозможноВозможно
Read CommittedНевозможноВозможноВозможно
Repeatable ReadНевозможноНевозможноВозможно
SerializableНевозможноНевозможноНевозможно
  • Read Uncommitted — самый низкий уровень, допускающий все три аномалии. Используется редко, в основном для задач, где точность данных не критична.
  • Repeatable Read — предотвращает неповторяющееся чтение, но допускает фантомное чтение. В некоторых СУБД (например, PostgreSQL) реализация Repeatable Read фактически предотвращает и фантомное чтение, но стандарт этого не гарантирует.
  • Serializable — самый строгий уровень, полностью изолирующий транзакции друг от друга. Он обеспечивает последовательное выполнение транзакций, как если бы они выполнялись одна за другой, но за счёт снижения производительности и увеличения вероятности взаимоблокировок (deadlocks).

Применение в различных СУБД

Реализация Read Committed может различаться в зависимости от СУБД, что влияет на наблюдаемое поведение.

PostgreSQL

В PostgreSQL уровень Read Committed реализован на основе MVCC. Каждая транзакция видит снимок данных, зафиксированных на момент выполнения первого оператора в транзакции (а не на момент её начала). Это означает, что в рамках одной транзакции разные операторы могут видеть разные версии данных, если между ними были зафиксированы изменения другими транзакциями. PostgreSQL не использует блокировки при чтении, что обеспечивает высокую параллельность.

Microsoft SQL Server

В SQL Server уровень Read Committed по умолчанию реализован с помощью блокировок. При чтении строки устанавливается разделяемая блокировка, которая удерживается до завершения операции. Если другая транзакция удерживает исключительную блокировку на той же строке, читающая транзакция ожидает её освобождения. Однако, начиная с SQL Server 2005, можно включить режим READ_COMMITTED_SNAPSHOT, который переводит Read Committed на использование MVCC, аналогично PostgreSQL. В этом режиме чтение не блокируется, но увеличивается нагрузка на хранилище версий строк.

Oracle Database

Oracle использует MVCC для всех уровней изолированности, включая Read Committed. По умолчанию Oracle работает на уровне Read Committed, и каждая транзакция видит данные, зафиксированные на момент её начала. Oracle не поддерживает блокировки при чтении, что делает его устойчивым к блокировкам чтения.

MySQL (InnoDB)

В MySQL с движком InnoDB уровень Read Committed реализован на основе MVCC. Он является уровнем по умолчанию в некоторых версиях MySQL (например, в MySQL 8.0). InnoDB использует версионирование строк и не блокирует чтение. Однако, при использовании Read Committed, InnoDB может применять блокировки на уровне индексов для предотвращения «грязного» чтения в некоторых сценариях.

Критика и ограничения

Уровень Read Committed, несмотря на свою популярность, подвергается критике за недостаточную согласованность данных в некоторых сценариях. Основные проблемы:

  • Непредсказуемость в длительных транзакциях: Если транзакция выполняется долго и содержит несколько запросов, она может увидеть разные состояния данных, что может привести к логическим ошибкам. Например, в финансовых приложениях это может привести к двойному списанию или неправильному расчёту баланса.
  • Отсутствие изоляции для сложных операций: Для задач, требующих атомарности и согласованности (например, перевод средств между счетами), Read Committed может быть недостаточным. В таких случаях требуется более высокий уровень, например, Serializable или использование явных блокировок.
  • Зависимость от реализации: Различия в реализации Read Committed в разных СУБД (блокировки vs MVCC) могут приводить к неожиданному поведению при миграции приложений между системами.

Интересные факты

  • Уровень Read Committed является уровнем по умолчанию в большинстве промышленных СУБД, так как он обеспечивает разумный баланс между производительностью и согласованностью для типичных OLTP-нагрузок (онлайн-обработка транзакций).
  • В некоторых СУБД, таких как IBM Db2, уровень Read Committed называется «Cursor Stability» (устойчивость курсора), что отражает его поведение при работе с курсорами.
  • Стандарт SQL-92 не предписывает конкретный механизм реализации (блокировки или MVCC), что оставляет производителям СУБД свободу выбора.

Источники

  • ISO/IEC 9075:1992 (SQL-92) — стандарт, определяющий уровни изолированности транзакций.
  • Gray, J., & Reuter, A. (1993). Transaction Processing: Concepts and Techniques. Morgan Kaufmann.
  • Ramakrishnan, R., & Gehrke, J. (2003). Database Management Systems (3rd ed.). McGraw-Hill.
  • Документация PostgreSQL: «Transaction Isolation» (Chapter 13).
  • Документация Microsoft SQL Server: «Isolation Levels in the Database Engine».
  • Документация Oracle Database: «Data Concurrency and Consistency».
  • Документация MySQL: «InnoDB Transaction Isolation Levels».

BFOmetr — база данных и аналитика по компаниям России.

На главную BFOmetr →