← Назад к вопросам

Как работает Repeatable Read?

2.0 Middle🔥 113 комментариев
#Базы данных

Комментарии (3)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Уровни изоляции транзакций и Repeatable Read

В контексте баз данных Repeatable Read — это один из стандартных уровней изоляции транзакций, определённых в спецификации SQL стандарта ANSI/ISO. Этот уровень гарантирует, что если в течение транзакции одна и та же строка (или набор данных) читается несколько раз, она останется неизменной на протяжении всей транзакции, несмотря на возможные изменения другими параллельными транзакциями. Главная цель — предотвратить "неповторяющееся чтение" (non-repeatable read) — аномалию, когда повторное чтение тех же данных в рамках одной транзакции возвращает разные значения из-за их изменения или удаления другой транзакцией.

Механизм работы Repeatable Read

Блокировки и версионность

В зависимости от СУБД, реализация Repeatable Read различается. Существует два основных подхода:

  1. На основе блокировок (Lock-based) – используется, например, в MySQL/InnoDB до определенных версий или в некоторых сценариях. При первом чтении строки транзакция устанавливает на неё блокировку (обычно shared lock — блокировка на чтение), которая удерживается до конца транзакции (COMMIT или ROLLBACK). Это предотвращает изменение или удаление этих строк другими транзакциями, но может привести к увеличению блокировок и риску взаимоблокировок (deadlocks).

  2. Многовариантное управление параллельным доступом (MVCC — Multi-Version Concurrency Control) – более современный и распространенный подход, применяемый в PostgreSQL, MySQL/InnoDB (по умолчанию), Oracle и других. При этом каждая транзакция в момент начала "видит" согласованный снимок данных (snapshot) на определённый момент времени. Все изменения, сделанные другими транзакциями после этого снимка, не видны до конца текущей транзакции.

Пример на практике (MVCC)

Рассмотрим на примере PostgreSQL, который использует MVCC для реализации Repeatable Read:

-- Транзакция 1
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT balance FROM accounts WHERE id = 1; 
-- Допустим, вернулось 1000

-- Параллельно Транзакция 2 выполняет и завершается:
BEGIN;
UPDATE accounts SET balance = 1500 WHERE id = 1;
COMMIT;

-- В Транзакции 1 снова читаем ту же строку:
SELECT balance FROM accounts WHERE id = 1; 
-- ВСЕ ЕЩЁ вернёт 1000, а не 1500, благодаря снимку данных!
COMMIT;

-- После COMMIT новое чтение покажет обновлённое значение 1500.

Что гарантирует и не гарантирует Repeatable Read

Предотвращаемые аномалии:

  • Грязное чтение (Dirty Read): Чтение незафиксированных данных другой транзакции — невозможно.
  • Неповторяющееся чтение (Non-repeatable Read): Изменение значения строки другой транзакцией между двумя чтениями — невозможно.

Не предотвращаемая аномалия (на стандартном уровне):

  • Фантомное чтение (Phantom Read): Появление новых строк (удовлетворяющих условию запроса), вставленных другой завершённой транзакцией, между двумя одинаковыми запросами в текущей транзакции — ВОЗМОЖНО в классической интерпретации. Однако важно отметить, что многие современные СУБД (как PostgreSQL и MySQL/InnoDB при использовании Repeatable Read) расширяют стандарт и также предотвращают фантомы с помощью блокировок диапазонов (gap locks) или усовершенствованного MVCC.

Ошибка сериализации

В Repeatable Read может возникнуть специфическая ситуация, когда транзакция пытается обновить строку, которая была изменена другой уже завершившейся транзакцией после начала текущей. В этом случае СУБД прервёт текущую транзакцию с ошибкой сериализации (например, ERROR: could not serialize access due to concurrent update в PostgreSQL), требуя от приложения повторить её.

-- Транзакция 1
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT balance FROM accounts WHERE id = 1; -- Читает 1000

-- Транзакция 2
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- Меняет на 900
COMMIT;

-- Транзакция 1 пытается изменить ту же строку
UPDATE accounts SET balance = balance + 200 WHERE id = 1;
-- В PostgreSQL это вызовет ошибку сериализации!
-- В MySQL/InnoDB обновление будет выполнено, основываясь на последнем зафиксированном значении (900),
-- но результат расчёта (1100) будет применён к актуальной версии строки.

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

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

Заключение

Repeatable Read — это баланс между консистентностью данных и производительностью. Он критически важен для финансовых операций, отчётов, формируемых за длительный период, или любых процессов, где многократное чтение одних и тех же данных должно быть абсолютно предсказуемым. Однако разработчикам следует помнить о риске ошибок сериализации и необходимости реализации логики повтора транзакций (retry logic) на уровне приложения, а также учитывать специфические детали реализации в используемой СУБД (особенно в отношении предотвращения фантомного чтения).