Уровень изоляции Repeatable Read
Repeatable Read (повторяемое чтение) — один из четырёх стандартных уровней изоляции транзакций в системах управления базами данных (СУБД), определённых в стандарте SQL. Он гарантирует, что в рамках одной транзакции все прочитанные данные остаются неизменными, то есть при повторном чтении одних и тех же строк в той же транзакции результат будет идентичен. Этот уровень предотвращает аномалии «грязного чтения» и «неповторяемого чтения», но не защищает от «фантомного чтения».
История и стандартизация
Понятие уровней изоляции было формализовано в 1992 году в стандарте ANSI/ISO SQL-92. Разработчики выделили четыре уровня: Read Uncommitted, Read Committed, Repeatable Read и Serializable. Уровень Repeatable Read был введён как промежуточный компромисс между производительностью и строгостью гарантий. В ранних реляционных СУБД (например, в Oracle до версии 8.0) этот уровень не поддерживался, а вместо него использовался Serializable. С развитием многопользовательских систем и требований к согласованности данных Repeatable Read стал широко применяться в таких СУБД, как PostgreSQL, MySQL (с движком InnoDB), Microsoft SQL Server и других.
Механизмы реализации
Реализация Repeatable Read зависит от конкретной СУБД и используемого механизма управления параллельным доступом. Основные подходы:
Блокировки (Locking)
В системах с пессимистичной блокировкой (например, Microsoft SQL Server, Oracle в некоторых режимах) для обеспечения Repeatable Read применяются блокировки чтения (shared locks) на прочитанные строки. Эти блокировки удерживаются до конца транзакции, предотвращая их изменение или удаление другими транзакциями. Однако такой подход может приводить к взаимоблокировкам (deadlocks) и снижению производительности при высокой конкуренции.
Многоверсионность (MVCC)
Большинство современных СУБД (PostgreSQL, MySQL с InnoDB, Oracle) используют механизм многоверсионного управления параллельным доступом (MVCC, Multi-Version Concurrency Control). При этом каждая транзакция видит «снимок» данных на момент своего начала. Для Repeatable Read в PostgreSQL и MySQL (InnoDB) используется именно этот подход: транзакция получает идентификатор (snapshot ID), и все последующие чтения возвращают данные, соответствующие этому снимку, игнорируя изменения, внесённые другими транзакциями после старта. Это обеспечивает повторяемость чтения без блокировок на чтение.
Аномалии, предотвращаемые и не предотвращаемые
Предотвращаемые аномалии
- Грязное чтение (Dirty Read): чтение данных, изменённых незавершённой (незафиксированной) транзакцией. Repeatable Read исключает эту аномалию, так как транзакция не видит изменений других транзакций до их фиксации.
- Неповторяемое чтение (Non-Repeatable Read): ситуация, когда при повторном чтении одной и той же строки в рамках одной транзакции получаются разные значения. Repeatable Read гарантирует, что при повторном чтении строки её значение не изменится (если только оно не было изменено самой транзакцией).
Не предотвращаемая аномалия
- Фантомное чтение (Phantom Read): при повторном выполнении одного и того же запроса с условием (например,
SELECT * FROM users WHERE age > 30) в рамках одной транзакции могут появляться новые строки, добавленные другими транзакциями, или исчезать существующие. Repeatable Read не блокирует вставку или удаление строк, удовлетворяющих условию запроса, поэтому фантомы возможны. Исключение составляют СУБД, которые дополнительно блокируют диапазоны (gap locks), как это делает MySQL InnoDB в режиме Repeatable Read.
Примеры поведения
Рассмотрим две транзакции, выполняющиеся параллельно:
| Время | Транзакция A | Транзакция B |
|---|---|---|
| T1 | BEGIN; | |
| T2 | BEGIN; | |
| T3 | SELECT * FROM accounts WHERE id = 1; (возвращает balance = 100) | |
| T4 | UPDATE accounts SET balance = 200 WHERE id = 1; COMMIT; | |
| T5 | SELECT * FROM accounts WHERE id = 1; (возвращает balance = 100) | |
| T6 | COMMIT; |
В данном примере транзакция A на уровне Repeatable Read не видит изменения, внесённого транзакцией B, и повторное чтение возвращает исходное значение (100). Если бы уровень был Read Committed, на шаге T5 было бы получено значение 200.
Реализации в популярных СУБД
PostgreSQL
PostgreSQL реализует Repeatable Read через MVCC. Транзакция видит снимок данных на момент первого запроса (или начала транзакции, в зависимости от настройки). Фантомное чтение полностью исключено, так как снимок фиксирует все строки, удовлетворяющие условию, на момент снимка. Однако при попытке обновления строки, изменённой другой транзакцией, возникает ошибка сериализации.
MySQL (InnoDB)
В MySQL с движком InnoDB Repeatable Read является уровнем по умолчанию. Используется MVCC с дополнительными блокировками диапазонов (gap locks), что предотвращает фантомное чтение для большинства запросов. Однако при использовании операторов с блокировками (SELECT ... FOR UPDATE, SELECT ... LOCK IN SHARE MODE) могут возникать взаимоблокировки.
Microsoft SQL Server
SQL Server поддерживает Repeatable Read с помощью блокировок. При чтении строки накладывается разделяемая блокировка, которая удерживается до конца транзакции. Это предотвращает неповторяемое чтение, но может приводить к блокировкам и фантомам. Для защиты от фантомов требуется использование подсказок (например, SERIALIZABLE).
Oracle
Oracle не поддерживает Repeatable Read в классическом виде. Вместо этого используется уровень Serializable, который обеспечивает полную изоляцию, но с более высокими накладными расходами. Для достижения повторяемости чтения в Oracle применяются снимки (snapshot isolation) в режиме Serializable.
Применение
Уровень Repeatable Read используется в сценариях, где требуется согласованность данных при чтении, но допустимо появление фантомов. Типичные примеры:
- Отчёты и аналитика: формирование отчётов, где важно, чтобы данные не менялись во время выполнения запроса.
- Финансовые операции: чтение баланса счёта или остатков на складе, где повторяемость чтения критична для корректности расчётов.
- Пакетная обработка: выполнение серии запросов, где каждый последующий запрос зависит от результатов предыдущего.
Критика и ограничения
- Производительность: в системах с блокировками (SQL Server) Repeatable Read может снижать пропускную способность из-за длительного удержания блокировок. В системах с MVCC (PostgreSQL, MySQL) накладные расходы ниже, но возрастает потребление памяти для хранения версий строк.
- Фантомное чтение: отсутствие защиты от фантомов может приводить к логическим ошибкам в приложениях, которые ожидают полной изоляции. Например, при подсчёте количества записей с условием результат может измениться между запросами.
- Ошибки сериализации: в PostgreSQL при попытке обновить строку, изменённую другой транзакцией, возникает ошибка, что требует повторной попытки транзакции.
Сравнение с другими уровнями изоляции
| Уровень изоляции | Грязное чтение | Неповторяемое чтение | Фантомное чтение |
|---|---|---|---|
| Read Uncommitted | Возможно | Возможно | Возможно |
| Read Committed | Невозможно | Возможно | Возможно |
| Repeatable Read | Невозможно | Невозможно | Возможно |
| Serializable | Невозможно | Невозможно | Невозможно |
Источники
- ANSI/ISO SQL-92 Standard, Section 4.32: SQL Transaction Isolation Levels.
- Gray, J., & Reuter, A. (1993). Transaction Processing: Concepts and Techniques. Morgan Kaufmann.
- PostgreSQL Documentation: Chapter 13. Concurrency Control.
- MySQL 8.0 Reference Manual: Section 15.7.2.1, Transaction Isolation Levels.
- Microsoft SQL Server Documentation: SET TRANSACTION ISOLATION LEVEL (Transact-SQL).
BFOmetr — база данных и аналитика по компаниям России.
На главную BFOmetr →