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

Повторяемое чтение

Повторяемое чтение — это один из уровней изоляции транзакций в системах управления базами данных (СУБД), который гарантирует, что в рамках одной транзакции повторные чтения одних и тех же данных всегда возвращают одинаковый результат. Данный уровень обеспечивает более строгую изоляцию, чем «Чтение зафиксированных данных» (Read Committed), но менее строгую, чем «Сериализуемость» (Serializable). Повторяемое чтение предотвращает эффект «неповторяющегося чтения», но допускает фантомное чтение.

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

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

В более поздних версиях стандарта SQL (например, SQL:2003, SQL:2016) концепция уровней изоляции была уточнена, но базовые принципы остались неизменными. Различные СУБД (PostgreSQL, MySQL, Oracle, Microsoft SQL Server) реализуют этот уровень с некоторыми вариациями, которые могут влиять на поведение транзакций.

Аномалии, предотвращаемые и допускаемые

Основное назначение повторяемого чтения — устранение аномалии «неповторяющегося чтения». Эта аномалия возникает, когда в рамках одной транзакции выполняется два одинаковых запроса SELECT, и между ними другая транзакция изменяет (обновляет или удаляет) строки, которые были прочитаны первым запросом. В результате второй запрос возвращает данные, отличающиеся от первого. На уровне «Повторяемое чтение» такая ситуация исключается: транзакция видит «снимок» данных на момент её начала, и любые изменения, внесённые параллельными транзакциями, остаются невидимыми до её завершения.

Однако повторяемое чтение не предотвращает «фантомное чтение». Фантомное чтение — это ситуация, когда в рамках одной транзакции выполняется два одинаковых запроса с условием, и между ними другая транзакция вставляет новые строки, удовлетворяющие этому условию. В результате второй запрос возвращает строки, которых не было в первом результате. Например, транзакция A выполняет запрос SELECT * FROM users WHERE age > 30 и получает 10 строк. Затем транзакция B вставляет новую строку с age = 35 и фиксирует изменения. При повторном выполнении того же запроса транзакция A получит 11 строк, включая новую. Это и есть фантомное чтение.

Реализация в различных СУБД

Хотя стандарт SQL определяет общие принципы, конкретные реализации повторяемого чтения различаются.

PostgreSQL

В PostgreSQL уровень «Повторяемое чтение» реализован с помощью механизма многоверсионного управления параллельным доступом (MVCC). Транзакция видит «снимок» данных на момент её начала. Все изменения, внесённые другими транзакциями после этого момента, не видны, даже если они были зафиксированы. Это предотвращает как неповторяющееся, так и фантомное чтение в большинстве случаев. Однако PostgreSQL не блокирует вставку новых строк, поэтому фантомное чтение теоретически возможно, но на практике оно не проявляется из-за того, что снимок данных фиксируется один раз. В PostgreSQL уровень «Повторяемое чтение» фактически эквивалентен «Сериализуемости» для многих сценариев, но не предотвращает конфликты при параллельном обновлении одних и тех же строк.

MySQL (InnoDB)

В MySQL с движком InnoDB уровень «Повторяемое чтение» является уровнем по умолчанию. Он реализован с помощью MVCC и блокировок на уровне строк. InnoDB использует блокировки для предотвращения неповторяющегося чтения, но не блокирует вставку новых строк, что допускает фантомное чтение. Однако InnoDB также использует механизм «блокировок промежутков» (gap locks), которые блокируют вставку новых строк в диапазон, который был прочитан транзакцией. Это позволяет предотвратить фантомное чтение в некоторых сценариях, но не во всех. Например, если запрос использует SELECT ... FOR UPDATE или SELECT ... LOCK IN SHARE MODE, то InnoDB блокирует диапазон, предотвращая вставку новых строк. Для обычных запросов SELECT фантомное чтение может возникать.

Oracle

В Oracle уровень «Повторяемое чтение» не поддерживается в классическом виде. Вместо этого Oracle предлагает уровни «Чтение зафиксированных данных» (Read Committed) и «Сериализуемость» (Serializable). Для достижения поведения, близкого к повторяемому чтению, в Oracle используется механизм «чтения зафиксированных данных с согласованностью на уровне запроса» (read consistency). Это означает, что каждый запрос видит данные на момент его начала, но не на момент начала транзакции. Таким образом, неповторяющееся чтение возможно, если между запросами прошло время. Для его предотвращения рекомендуется использовать уровень «Сериализуемость».

Microsoft SQL Server

В Microsoft SQL Server уровень «Повторяемое чтение» реализован с помощью блокировок на уровне строк. Когда транзакция читает строку, она устанавливает на неё блокировку, которая удерживается до конца транзакции. Это предотвращает изменение или удаление этой строки другими транзакциями. Однако блокировки не распространяются на новые строки, которые могут быть вставлены в диапазон, поэтому фантомное чтение возможно. Для его предотвращения используется уровень «Сериализуемость», который устанавливает блокировки на диапазоны.

Применение

Уровень «Повторяемое чтение» используется в сценариях, где требуется высокая согласованность данных, но допустимо появление фантомных записей. Типичные примеры включают:

  • Финансовые операции: чтение баланса счета и последующее списание средств. Если между чтением и списанием другая транзакция изменит баланс, это может привести к ошибке. Повторяемое чтение гарантирует, что баланс не изменится в рамках одной транзакции.
  • Отчёты и аналитика: формирование отчётов, где требуется, чтобы данные не менялись в процессе выполнения нескольких запросов. Например, отчёт о продажах за месяц, который включает несколько подзапросов.
  • Системы управления запасами: проверка количества товара на складе и последующее резервирование. Повторяемое чтение предотвращает ситуацию, когда между проверкой и резервированием другой пользователь изменяет количество.

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

Основным недостатком повторяемого чтения является его производительность. Удержание блокировок на прочитанные строки до конца транзакции может привести к увеличению времени ожидания для других транзакций, особенно в системах с высокой конкурентностью. В PostgreSQL, где блокировки не используются для обычных чтений, производительность выше, но это достигается за счёт более сложного механизма MVCC.

Кроме того, повторяемое чтение не решает всех проблем параллелизма. Фантомное чтение может быть критичным в некоторых сценариях, например, при проверке уникальности данных. В таких случаях требуется более высокий уровень изоляции — «Сериализуемость».

Альтернативы

В зависимости от требований к согласованности и производительности, разработчики могут выбирать между различными уровнями изоляции:

  • Чтение зафиксированных данных (Read Committed): более высокая производительность, но возможны неповторяющееся и фантомное чтение.
  • Сериализуемость (Serializable): максимальная согласованность, но низкая производительность из-за блокировок или конфликтов при параллельном доступе.
  • Снимок изоляции (Snapshot Isolation): нестандартный уровень, который предоставляет снимок данных на момент начала транзакции, предотвращая как неповторяющееся, так и фантомное чтение, но не гарантируя сериализуемость. Реализован в PostgreSQL и SQL Server.

Источники

  • ANSI/ISO SQL-92 Standard
  • PostgreSQL Documentation: Transaction Isolation
  • MySQL Documentation: InnoDB Transaction Isolation Levels
  • Oracle Database Concepts: Data Concurrency and Consistency
  • Microsoft SQL Server Documentation: Transaction Isolation Levels

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

На главную BFOmetr →