Перейти к содержимому

Уровни изоляции

Уровни изоляции транзакций — баланс между целостностью данных при параллельном доступе и производительностью. READ UNCOMMITTED (самый слабый): допускает грязное чтение (чтение незафиксированных изменений), максимальная скорость. READ COMMITTED (умолчание в PostgreSQL, Oracle, SQL Server): защищает от грязного чтения, допускает неповторяющееся чтение и фантомы. REPEATABLE READ (умолчание в MySQL InnoDB): защищает от грязного чтения и неповторяющегося чтения, фантомы возможны (кроме PostgreSQL и MySQL InnoDB, где защищают). SERIALIZABLE (самый строгий): защищает от всех аномалий (грязное чтение, неповторяющееся чтение, фантомы), требует повторных попыток транзакций при конфликтах (SSI в PostgreSQL). Сравнение уровней, особенности в разных СУБД (PostgreSQL, MySQL InnoDB, SQL Server, Oracle), примеры, типичные ошибки (использование READ UNCOMMITTED по умолчанию, игнорирование ошибок сериализации).

Введение: Проблема параллельного доступа

Представьте, что вы и ваш друг одновременно пытаетесь забронировать последнее место в кинотеатре через сайт. Вы открываете страницу, видите, что место свободно, и нажимаете “Забронировать”. Друг делает то же самое в ту же секунду. Кто получит место?

Если система работает правильно, место достанется только одному из вас. Но если система не управляет параллельным доступом должным образом, возможны разные сценарии: вы оба увидите, что место свободно, оба нажмете “Забронировать”, и система либо дважды продаст одно место (плохо), либо откажет одному из вас с непонятной ошибкой (тоже плохо, но лучше чем двойная продажа).

В базах данных параллельные транзакции создают похожие проблемы. Транзакции могут читать данные, которые другая транзакция еще не зафиксировала. Могут видеть разные версии одних и тех же данных в рамках одного запроса. Могут терять изменения, сделанные параллельно.

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

Что такое уровень изоляции

Уровень изоляции (Isolation Level) — это набор правил, определяющих, насколько изменения, сделанные одной транзакцией, видны другим транзакциям до ее фиксации.

Уровень изоляции отвечает на вопросы:

  • Может ли транзакция видеть незафиксированные изменения другой транзакции?
  • Если транзакция читает одни и те же данные дважды, гарантированно ли она увидит одинаковые значения?
  • Могут ли появиться новые строки в диапазоне, который транзакция уже прочитала?

Стандарт SQL определяет четыре уровня изоляции (от самого слабого к самому сильному):

Уровень изоляцииЧто защищаетКакие аномалии допускает
READ UNCOMMITTEDПочти ничегоГрязное чтение, неповторяющееся чтение, фантомы
READ COMMITTEDГрязное чтениеНеповторяющееся чтение, фантомы
REPEATABLE READГрязное чтение, неповторяющееся чтениеФантомы
SERIALIZABLEВсе перечисленные аномалииНикаких

Эти уровни образуют иерархию: каждый следующий уровень включает защиту предыдущего. SERIALIZABLE — самый строгий, READ UNCOMMITTED — самый слабый.

Но важно понимать: стандарт SQL определяет минимальные гарантии для каждого уровня. Конкретные СУБД могут предоставлять более сильные гарантии, чем требует стандарт. Например, PostgreSQL на уровне REPEATABLE READ защищает и от фантомов, хотя стандарт этого не требует.

Грязное чтение (Dirty Read)

Чтобы понять уровни изоляции, нужно сначала понять аномалии, от которых они защищают. Первая и самая опасная аномалия — грязное чтение. (Остальные аномалии подробно раскрываются в следующем документе темы “Транзакции”, здесь дается минимальное определение.)

Грязное чтение — это ситуация, когда одна транзакция читает данные, которые были изменены другой транзакцией, но еще не зафиксированы.

Пример:

ВремяТранзакция A (перевод)Транзакция B (отчет)
t1BEGIN
t2UPDATE accounts SET balance = balance - 100 WHERE id = 1
t3SELECT balance FROM accounts WHERE id = 1
t4→ Видит сумму уже после списания (но транзакция A еще не зафиксирована!)
t5ROLLBACK (ошибка, отмена перевода)
t6Отчет использовал данные, которых никогда не существовало

Транзакция B прочитала сумму, которую транзакция A в итоге откатила. Отчет оказался неверным, основанным на данных-призраках.

Грязное чтение — это самая грубая аномалия, которую допускает только уровень READ UNCOMMITTED. Все остальные уровни защищают от нее.

READ UNCOMMITTED: Самый быстрый, но самый опасный

Что это такое

READ UNCOMMITTED — самый низкий (самый слабый) уровень изоляции. Транзакция на этом уровне может читать незафиксированные изменения других транзакций. Ее еще называют “грязное чтение” (dirty read) по имени основной аномалии.

Какие гарантии дает

READ UNCOMMITTED дает почти никаких гарантий:

  • Допускает грязное чтение.
  • Допускает неповторяющееся чтение.
  • Допускает фантомы.
  • Не ставит блокировок при чтении.

Когда используется

READ UNCOMMITTED используется крайне редко в production-системах. Его единственное преимущество — максимальная скорость чтения, потому что транзакция не ждет блокировок и не тратит ресурсы на контроль версий.

Типичные сценарии:

СценарийПрименимость
Приблизительные отчетыОтчет, где небольшая неточность допустима, а скорость критична
Мониторинг и наблюдениеСлежение за ходом выполнения длительных операций
Очень большие базы данных для чтенияГде загрузка настолько высока, что блокировки недопустимы
Тестирование и отладкаЧтобы увидеть, что делает параллельная транзакция

Важно: В PostgreSQL READ UNCOMMITTED не поддерживается. Если вы попытаетесь его установить, PostgreSQL будет вести себя как READ COMMITTED. Это сделано намеренно — разработчики PostgreSQL считают грязное чтение слишком опасной аномалией, чтобы разрешать ее даже по запросу.

Пример работы

-- Сессия 1 (транзакция A)
BEGIN;
UPDATE products SET price = 0 WHERE id = 1; -- Цена стала 0, но еще не зафиксировано

-- Сессия 2 (транзакция B)
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
SELECT price FROM products WHERE id = 1; -- Вернет 0 (грязное чтение!)

-- Сессия 1
ROLLBACK; -- Откатываем изменение, цена была 100 и осталась 100

-- Транзакция B использовала данные, которых никогда не существовало

READ COMMITTED: Золотая середина по умолчанию

Что это такое

READ COMMITTED — самый распространенный уровень изоляции. Он используется по умолчанию в PostgreSQL, Oracle, SQL Server (в режиме READ_COMMITTED_SNAPSHOT = OFF) и многих других СУБД.

Транзакция на этом уровне видит только зафиксированные данные. Грязное чтение исключено. Но другие аномалии (неповторяющееся чтение, фантомы) возможны.

Какие гарантии дает

READ COMMITTED гарантирует:

  • Нет грязного чтения. Вы никогда не увидите незафиксированные изменения другой транзакции.

READ COMMITTED НЕ гарантирует:

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

Как работает под капотом

В большинстве СУБД READ COMMITTED реализуется через комбинацию блокировок и многоверсионности (MVCC).

В СУБД с MVCC (PostgreSQL, Oracle):

  • Каждый запрос видит снимок данных (snapshot) на момент начала выполнения этого запроса.
  • Разные запросы внутри одной транзакции могут видеть разные снимки, если между ними были изменения от других транзакций.

В СУБД с блокировками (SQL Server без SNAPSHOT):

  • При чтении ставятся кратковременные блокировки, которые снимаются сразу после чтения.
  • Другие транзакции могут изменять данные между чтениями.

Пример неповторяющегося чтения

-- Сессия 1 (транзакция A)
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
SELECT balance FROM accounts WHERE id = 1; -- Видит 1000

-- Сессия 2 (транзакция B)
BEGIN;
UPDATE accounts SET balance = 900 WHERE id = 1;
COMMIT; -- Изменение зафиксировано

-- Сессия 1
SELECT balance FROM accounts WHERE id = 1; -- Видит 900 (неповторяющееся чтение!)
COMMIT;

Транзакция A дважды прочитала одну и ту же строку и получила разные значения. Для многих приложений это приемлемо, но не для всех.

Когда используется READ COMMITTED

READ COMMITTED — выбор по умолчанию для большинства OLTP-систем.

СценарийПочему подходит
Большинство веб-приложенийВысокая производительность, достаточная защита от грязных чтений
Системы, где неповторяющееся чтение не критичноНапример, если вы не делаете несколько чтений одной строки в транзакции
Системы с высокой конкуренциейМеньше блокировок, чем на более высоких уровнях

READ COMMITTED в разных СУБД

СУБДОсобенности READ COMMITTED
PostgreSQLREAD COMMITTED по умолчанию. Каждый запрос видит снимок на момент начала запроса.
OracleREAD COMMITTED по умолчанию. Аналогично PostgreSQL, использует версионирование.
MySQL (InnoDB)REPEATABLE READ по умолчанию, но READ COMMITTED поддерживается.
SQL ServerREAD COMMITTED по умолчанию, но есть два режима: с блокировками (по умолчанию) и SNAPSHOT.

REPEATABLE READ: Стабильный взгляд на данные

Что это такое

REPEATABLE READ — уровень изоляции, который гарантирует, что если транзакция прочитала строку один раз, повторное чтение той же строки в рамках той же транзакции вернет то же самое значение. Неповторяющееся чтение исключено.

Этот уровень используется по умолчанию в MySQL (InnoDB).

Какие гарантии дает

REPEATABLE READ гарантирует:

  • Нет грязного чтения.
  • Нет неповторяющегося чтения. Если вы прочитали строку, никто не сможет изменить ее так, чтобы вы увидели другое значение при повторном чтении.

REPEATABLE READ НЕ гарантирует (по стандарту SQL):

  • Фантомы. Другая транзакция может вставить новые строки, удовлетворяющие условию вашей выборки. При повторной выборке эти строки появятся.

Важное исключение: Некоторые СУБД (например, PostgreSQL, MySQL InnoDB в режиме REPEATABLE READ) защищают и от фантомов на практике, хотя стандарт этого не требует.

Как работает под капотом

REPEATABLE READ обычно реализуется через многоверсионность (MVCC) с более строгими правилами, чем READ COMMITTED.

В PostgreSQL и Oracle:

  • Транзакция видит снимок данных на момент начала транзакции, а не на момент каждого запроса.
  • Все запросы внутри транзакции видят одну и ту же версию данных.

В MySQL InnoDB:

  • Используется многоверсионность с gap-блокировками для защиты от фантомов.
  • Gap-блокировки блокируют не только существующие строки, но и промежутки между ними.

Пример (отсутствие неповторяющегося чтения)

-- Сессия 1 (транзакция A)
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT balance FROM accounts WHERE id = 1; -- Видит 1000

-- Сессия 2 (транзакция B)
BEGIN;
UPDATE accounts SET balance = 900 WHERE id = 1;
COMMIT; -- Изменение зафиксировано

-- Сессия 1
SELECT balance FROM accounts WHERE id = 1; -- Все еще видит 1000 (защита от неповторяющегося чтения!)
COMMIT;

Транзакция A продолжает видеть старую версию данных, даже после того как транзакция B зафиксировала изменение.

Когда используется REPEATABLE READ

СценарийПочему подходит
Системы, где важна согласованность в рамках транзакцииНапример, формирование отчета на основе нескольких запросов
Балансы и финансовые операцииПовторное чтение должно давать тот же результат
MySQL-приложенияПоскольку это уровень по умолчанию в InnoDB

SERIALIZABLE: Полная изоляция

Что это такое

SERIALIZABLE — самый высокий (самый строгий) уровень изоляции. Он гарантирует, что параллельно выполняющиеся транзакции дают тот же результат, как если бы они выполнялись последовательно, одна за другой.

SERIALIZABLE защищает от всех аномалий: грязного чтения, неповторяющегося чтения, фантомов и других.

Какие гарантии дает

SERIALIZABLE гарантирует:

  • Нет грязного чтения.
  • Нет неповторяющегося чтения.
  • Нет фантомов.
  • Линеаризуемость — транзакции упорядочены так, как будто они выполнялись последовательно.

Как работает под капотом

SERIALIZABLE может быть реализован разными способами:

Через блокировки (ранние СУБД):

  • Ставятся блокировки на все читаемые и изменяемые данные.
  • Блокировки диапазонов (range locks) защищают от фантомов.
  • Высокий риск взаимных блокировок (deadlocks) и низкая производительность.

Через многоверсионность с проверкой конфликтов (PostgreSQL, современные СУБД):

  • Транзакции работают со своими версиями данных (как в REPEATABLE READ).
  • При фиксации (COMMIT) проверяется, не было ли конфликтов с другими транзакциями.
  • Если конфликт обнаружен (например, одна транзакция изменила данные, которые читала другая), транзакция откатывается с ошибкой “cannot serialize”.

Этот подход называется Serializable Snapshot Isolation (SSI). Он дает лучшую производительность, чем блокировки, но требует, чтобы приложение было готово повторять транзакции, которые откатились из-за конфликта.

Пример (защита от фантомов)

-- Сессия 1 (транзакция A)
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT COUNT(*) FROM products WHERE category = 'books'; -- Получили 10

-- Сессия 2 (транзакция B)
BEGIN;
INSERT INTO products (category) VALUES ('books');
COMMIT;

-- Сессия 1
SELECT COUNT(*) FROM products WHERE category = 'books'; -- Все еще 10 (защита от фантомов)
COMMIT;

Если бы уровень изоляции был REPEATABLE READ, транзакция A могла бы увидеть новую книгу при повторном чтении (фантом). SERIALIZABLE этого не допускает.

Цена SERIALIZABLE

SERIALIZABLE — самый дорогой уровень изоляции с точки зрения производительности.

В системах с блокировками:

  • Высокая вероятность взаимных блокировок (deadlocks).
  • Низкая пропускная способность.
  • Длинные очереди ожидания.

В системах с SSI (PostgreSQL):

  • Более высокая пропускная способность, чем с блокировками.
  • Но транзакции могут откатываться с ошибкой “could not serialize access” при конфликтах.
  • Приложение должно уметь повторять такие транзакции.

Когда используется SERIALIZABLE

SERIALIZABLE используется редко, только когда целостность данных критичнее производительности.

СценарийПочему нужен SERIALIZABLE
Финансовые проводки с жесткими требованиямиНикакие аномалии недопустимы
Системы бронирования уникальных ресурсовДвойное бронирование недопустимо
Инвентаризация и складской учетТочность важнее скорости
Аналитические запросы, требующие абсолютной согласованностиОтчет должен видеть непротиворечивый снимок

Сравнение уровней изоляции в таблице

ХарактеристикаREAD UNCOMMITTEDREAD COMMITTEDREPEATABLE READSERIALIZABLE
Грязное чтениеВозможноНевозможноНевозможноНевозможно
Неповторяющееся чтениеВозможноВозможноНевозможноНевозможно
ФантомыВозможныВозможныВозможны (кроме некоторых СУБД)Невозможны
Скорость чтенияМаксимальнаяВысокаяСредняяНизкая
Скорость записиВысокаяСредняяСредняяНизкая
Вероятность блокировокНизкаяСредняяСредняяВысокая
Вероятность deadlockНизкаяСредняяСредняяВысокая
Требует повтор транзакцийНетНетНетДа (при SSI)
Умолчание в PostgreSQLНе поддерживаетсяДаНетНет
Умолчание в MySQL InnoDBНетНетДаНет
Умолчание в OracleНетДаНетНет
Умолчание в SQL ServerНетДаНетНет

Уровни изоляции в разных СУБД: Нюансы и отличия

Стандарт SQL определяет минимальные требования к каждому уровню изоляции. Но конкретные СУБД часто предоставляют более сильные гарантии, чем требует стандарт.

PostgreSQL

УровеньОсобенности в PostgreSQL
READ UNCOMMITTEDНе поддерживается, ведет себя как READ COMMITTED
READ COMMITTEDУровень по умолчанию. Каждый запрос видит снимок на момент начала запроса
REPEATABLE READЗащищает от фантомов (сильнее, чем требует стандарт). Транзакция видит снимок на момент начала транзакции
SERIALIZABLEРеализован через SSI. Может откатывать транзакции с ошибкой сериализации

MySQL (InnoDB)

УровеньОсобенности в MySQL InnoDB
READ UNCOMMITTEDПоддерживается, но не рекомендуется
READ COMMITTEDКаждый запрос видит зафиксированные изменения. Возможны неповторяющиеся чтения
REPEATABLE READУровень по умолчанию. Защищает от фантомов через gap-блокировки
SERIALIZABLEБлокирует все читаемые строки (SELECT … LOCK IN SHARE MODE). Очень низкая производительность

SQL Server

УровеньОсобенности в SQL Server
READ UNCOMMITTEDПоддерживается, также есть NOLOCK hint
READ COMMITTEDУровень по умолчанию. Может работать в двух режимах: с блокировками (по умолчанию) и READ COMMITTED SNAPSHOT (включение версионирования)
REPEATABLE READБлокирует все прочитанные строки до конца транзакции
SERIALIZABLEБлокирует диапазоны, защищая от фантомов

Oracle

УровеньОсобенности в Oracle
READ UNCOMMITTEDНе поддерживается
READ COMMITTEDУровень по умолчанию. Каждый запрос видит только зафиксированные данные
REPEATABLE READЯвно не поддерживается. Достигается через READ ONLY транзакции
SERIALIZABLEПоддерживается. Транзакция видит снимок на момент начала

Как выбрать уровень изоляции. Примеры

Если ваш сценарий…Рекомендуемый уровень
Большинство веб-приложений, интернет-магазиныREAD COMMITTED
Приложения на MySQLREPEATABLE READ (это умолчание)
Финансовые транзакции с высокими требованиямиREPEATABLE READ или SERIALIZABLE
Приблизительные отчеты, мониторингREAD COMMITTED (или даже READ UNCOMMITTED, если очень критична скорость)
Аналитические запросы к OLTP-базеREAD COMMITTED
Бронирование уникальных ресурсов (билеты, места)REPEATABLE READ или SERIALIZABLE
Системы, где любая аномалия недопустимаSERIALIZABLE

Когда можно использовать READ UNCOMMITTED

Почти никогда в production-системах с критичными данными. Единственные сценарии:

  • Мониторинг и наблюдение за ходом выполнения длительных операций.
  • Очень большие отчеты, где небольшая неточность допустима.
  • Тестовые среды.

Предупреждение: READ UNCOMMITTED может читать данные, которые будут откачены. Если вы используете его для принятия бизнес-решений, вы рискуете.

Когда стоит подумать о повышении уровня изоляции

Признаки того, что READ COMMITTED может быть недостаточно:

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

Когда SERIALIZABLE избыточен

SERIALIZABLE — это тяжелая артиллерия. Не используйте его, если:

  • Ваше приложение может работать с неповторяющимися чтениями.
  • У вас высокая нагрузка на запись.
  • Вы не готовы обрабатывать ошибки сериализации (повтор транзакций).

Типичные ошибки при работе с уровнями изоляции

Ошибка 1: Использовать READ UNCOMMITTED по умолчанию

Некоторые разработчики думают: “Мне нужна максимальная скорость, поставлю READ UNCOMMITTED”. Это может привести к тому, что приложение будет читать данные, которые никогда не существовали.

Как исправить: Используйте READ UNCOMMITTED только тогда, когда вы полностью осознаете риски и готовы их принять.

Ошибка 2: Думать, что более высокий уровень изоляции всегда лучше

SERIALIZABLE — это не “лучше”, это “строже”. Он решает одни проблемы, но создает другие (блокировки, deadlocks, ошибки сериализации).

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

Ошибка 3: Не учитывать поведение конкретной СУБД

“REPEATABLE READ в MySQL защищает от фантомов, а в PostgreSQL — тоже защищает, хотя стандарт этого не требует”. Хорошо. Но в других СУБД может быть иначе.

Как исправить: Знайте, как ваша СУБД реализует каждый уровень изоляции. Не полагайтесь только на стандарт.

Ошибка 4: Не обрабатывать ошибки сериализации

При использовании SERIALIZABLE в PostgreSQL (и других СУБД с SSI) транзакции могут откатываться с ошибкой “could not serialize access”. Если приложение не умеет повторять такие транзакции, пользователи будут видеть необъяснимые ошибки.

Как исправить: В коде, использующем SERIALIZABLE, всегда предусматривайте повтор транзакции при ошибке сериализации.

Ошибка 5: Путать уровень изоляции с механизмом изоляции

Уровень изоляции (READ COMMITTED, REPEATABLE READ) — это “что”. Механизм изоляции (блокировки, MVCC, SSI) — это “как”. Одно и то же уровень может быть реализован по-разному в разных СУБД.

Как исправить: Понимайте разницу. При анализе производительности важно знать не только уровень, но и механизм.

Резюме для системного аналитика

  1. Уровень изоляции определяет баланс между целостностью данных при параллельном доступе и производительностью. Чем выше уровень, тем больше защиты, но ниже производительность.

  2. READ UNCOMMITTED — самый слабый уровень. Допускает грязное чтение. Используется крайне редко, только когда скорость важнее точности.

  3. READ COMMITTED — самый распространенный уровень, умолчание в PostgreSQL, Oracle, SQL Server. Защищает от грязного чтения, но допускает неповторяющееся чтение и фантомы.

  4. REPEATABLE READ — уровень, который гарантирует стабильность прочитанных строк. Защищает от грязного чтения и неповторяющегося чтения. Умолчание в MySQL InnoDB.

  5. SERIALIZABLE — самый строгий уровень. Защищает от всех аномалий, включая фантомы. Требует повторной попытки транзакций при конфликтах (в современных реализациях).

  6. Одна и та же СУБД может реализовывать уровни изоляции по-разному. Знайте особенности вашей СУБД. Не полагайтесь только на стандарт.

Проверка знаний

Вопрос 1 из 4
Зачем нужны уровни изоляции?
Какой уровень изоляции обычно самый строгий?
Что обычно даёт Read Committed?
Почему аналитик должен понимать уровни изоляции?

Вопросы, где были ошибки