Repeatable Read¶
Repeatable Read — это один из уровней изоляции транзакций в системах управления базами данных (СУБД), который гарантирует, что в рамках одной транзакции все повторные чтения одних и тех же строк данных вернут одинаковый результат. Данный уровень предотвращает эффект «неповторяющегося чтения» (non-repeatable read), но не защищает от «фантомного чтения» (phantom read). Repeatable Read является стандартным уровнем изоляции, определённым в спецификации SQL (ISO/IEC 9075) и реализованным во многих реляционных СУБД, включая PostgreSQL, MySQL (с использованием InnoDB) и Microsoft SQL Server.
¶История и стандартизация
Концепция уровней изоляции транзакций была формализована в 1992 году в стандарте ANSI/ISO SQL-92. В этом стандарте были определены четыре уровня изоляции: Read Uncommitted, Read Committed, Repeatable Read и Serializable. Repeatable Read был введён как промежуточный уровень, устраняющий проблему неповторяющегося чтения, но допускающий фантомное чтение. Впоследствии, с развитием теории транзакций (например, в работах Х. Беренсона и других), были предложены более строгие определения, и некоторые СУБД (например, PostgreSQL) реализуют Repeatable Read таким образом, что он фактически эквивалентен Serializable для большинства сценариев, за исключением случаев, связанных с фантомами.
¶Механизм работы
¶Блокировки и версионирование
Реализация Repeatable Read может различаться в зависимости от СУБД, но общий принцип заключается в том, что чтение данных фиксируется на момент первого запроса в транзакции. Для этого используются два основных подхода:
- Блокировки (Locking): В некоторых СУБД (например, в Microsoft SQL Server при определённых настройках) Repeatable Read реализуется через удержание разделяемых (shared) блокировок на все прочитанные строки до конца транзакции. Это предотвращает их изменение или удаление другими транзакциями. Однако такой подход может приводить к взаимным блокировкам (deadlocks) и снижению параллелизма.
- Многоверсионное управление параллельным доступом (MVCC): Большинство современных СУБД (PostgreSQL, MySQL/InnoDB) используют MVCC. В этом случае транзакция видит снимок данных (snapshot), сделанный на момент её начала или на момент первого запроса. Все последующие чтения в рамках той же транзакции обращаются к этому снимку, игнорируя изменения, внесённые другими транзакциями после создания снимка. Это позволяет избежать блокировок при чтении, но требует хранения нескольких версий строк.
¶Поведение при параллельных транзакциях
Рассмотрим две параллельные транзакции: T1 и T2.
- T1 начинает транзакцию с уровнем Repeatable Read и выполняет запрос
SELECT * FROM accounts WHERE id = 1, получая значение balance = 100. - T2 выполняет
UPDATE accounts SET balance = 200 WHERE id = 1и фиксирует изменения (COMMIT). - T1 повторно выполняет тот же запрос
SELECT * FROM accounts WHERE id = 1. При уровне Repeatable Read T1 снова увидит balance = 100 (из своего снимка). Это и есть защита от неповторяющегося чтения.
Однако, если T1 выполняет запрос SELECT * FROM accounts WHERE balance > 50 и получает 10 строк, а T2 вставляет новую строку с balance = 150 и фиксирует её, то при повторном выполнении того же запроса в T1 новая строка может не появиться (PostgreSQL) или появиться (MySQL/InnoDB), в зависимости от реализации. Это и есть фантомное чтение.
¶Проблемы параллельного доступа
¶Неповторяющееся чтение (Non-repeatable Read)
Это ситуация, когда при повторном чтении одной и той же строки в рамках одной транзакции возвращаются разные данные. Repeatable Read полностью устраняет эту проблему, гарантируя, что прочитанные однажды строки не изменятся в пределах транзакции.
¶Фантомное чтение (Phantom Read)
Это ситуация, когда при повторном выполнении одного и того же запроса с условием (например, WHERE balance > 100) в рамках одной транзакции возвращается разный набор строк (добавляются или удаляются строки, удовлетворяющие условию). Repeatable Read в классическом определении не защищает от фантомов, так как блокировки или снимок данных могут не распространяться на вновь вставляемые строки. Однако в PostgreSQL Repeatable Read реализован настолько строго, что фантомное чтение в нём практически невозможно, так как снимок данных фиксируется на уровне всей транзакции.
¶Грязное чтение (Dirty Read)
Это чтение данных, изменённых, но ещё не зафиксированных другой транзакцией. Repeatable Read, как и Read Committed, полностью предотвращает грязное чтение, так как транзакция видит только зафиксированные данные на момент снимка.
¶Реализации в различных СУБД
¶PostgreSQL
В PostgreSQL уровень Repeatable Read реализован через MVCC. Снимок данных создаётся при первом запросе в транзакции. Все последующие запросы видят данные на момент этого снимка. Это предотвращает как неповторяющееся чтение, так и фантомное чтение. Однако при попытке обновить строку, которая была изменена другой транзакцией после создания снимка, возникает ошибка сериализации (serialization failure). Это защищает от аномалий, но требует от приложения обработки таких ошибок и повторного выполнения транзакции.
¶MySQL (InnoDB)
В MySQL с движком InnoDB Repeatable Read является уровнем изоляции по умолчанию. Он также использует MVCC. Однако в отличие от PostgreSQL, фантомное чтение в InnoDB может возникать при определённых условиях, например, при использовании запросов с SELECT ... FOR UPDATE или LOCK IN SHARE MODE. InnoDB использует блокировки следующего ключа (next-key locking), которые блокируют не только существующие строки, но и диапазоны индексов, что частично предотвращает фантомы, но не полностью.
¶Microsoft SQL Server
В SQL Server Repeatable Read реализуется через блокировки. Разделяемые блокировки на прочитанные строки удерживаются до конца транзакции. Это гарантирует неповторяющееся чтение, но может приводить к взаимным блокировкам (deadlocks). Фантомное чтение возможно, так как блокировки не распространяются на диапазоны индексов (если не используется подсказка SERIALIZABLE).
¶Сравнение с другими уровнями изоляции
| Уровень изоляции | Грязное чтение | Неповторяющееся чтение | Фантомное чтение |
|---|---|---|---|
| Read Uncommitted | Возможно | Возможно | Возможно |
| Read Committed | Невозможно | Возможно | Возможно |
| Repeatable Read | Невозможно | Невозможно | Возможно (в классическом определении) |
| Serializable | Невозможно | Невозможно | Невозможно |
¶Применение
Repeatable Read используется в сценариях, где требуется консистентность данных в рамках одной транзакции, но допустима некоторая гибкость в отношении вставки новых данных. Примеры:
- Финансовые отчёты: Формирование отчёта о балансе счетов на определённый момент времени, где важно, чтобы повторное чтение суммы по одному и тому же счёту давало одинаковый результат.
- Биллинговые системы: Выставление счетов, где необходимо гарантировать, что данные о тарифах и услугах не изменятся в процессе расчёта.
- Аналитические запросы: Выполнение сложных запросов с подзапросами, где требуется согласованное представление данных.
¶Критика и ограничения
Основным недостатком Repeatable Read является потенциальное снижение параллелизма из-за удержания блокировок (в реализации на основе блокировок) или увеличение нагрузки на память из-за хранения множества версий строк (в реализации на основе MVCC). Кроме того, в классическом определении этот уровень не защищает от фантомного чтения, что может быть критично для некоторых приложений (например, систем резервирования). В PostgreSQL, где Repeatable Read фактически эквивалентен Serializable для большинства операций, возникает риск ошибок сериализации, которые требуют дополнительной логики обработки в приложении.
¶Источники
- ISO/IEC 9075-1:2016, Information technology — Database languages — SQL — Part 1: Framework (SQL/Framework).
- Berenson, H., Bernstein, P., Gray, J., Melton, J., O'Neil, E., & O'Neil, P. (1995). A critique of ANSI SQL isolation levels. ACM SIGMOD Record, 24(2), 1-10.
- PostgreSQL Documentation: Chapter 13. Concurrency Control.
- MySQL 8.0 Reference Manual: Chapter 15.7.2.1 Transaction Isolation Levels.
- Microsoft SQL Server Documentation: Transaction Isolation Levels (Transact-SQL).
BFOmetr — база данных и аналитика по компаниям России.
На главную BFOmetr →


