Фантомное чтение
Фантомное чтение — это феномен в области баз данных, представляющий собой один из трёх стандартных типов аномалий, возникающих при параллельном выполнении транзакций (наряду с «грязным чтением» и «неповторяющимся чтением»). Фантомное чтение заключается в том, что в рамках одной транзакции при повторном выполнении одного и того же запроса на выборку данных (например, по условию) возвращается разное количество строк. При этом ранее отсутствовавшие строки (так называемые «фантомные» записи) могут быть добавлены или, напротив, исчезнуть в результате завершения другой конкурирующей транзакции, работающей с тем же диапазоном данных. Данная аномалия нарушает свойство изолированности транзакций (I — Isolation) в модели ACID.
Определение и сущность явления
Фантомное чтение возникает, когда транзакция читает данные, удовлетворяющие некоторому условию (например, WHERE price > 1000), а затем, после того как другая транзакция вставила, удалила или изменила записи в том же диапазоне, первая транзакция при повторном выполнении запроса получает набор строк, отличный от первоначального. В отличие от неповторяющегося чтения, где изменяется содержимое уже существующих строк, при фантомном чтении изменяется сам состав записей (появляются или исчезают целые строки).
Основные признаки
- Изменение количества строк — ключевой индикатор. При повторном выполнении запроса число возвращаемых строк становится другим.
- Вставка или удаление записей — аномалия обусловлена действиями параллельной транзакции, которая либо добавляет новые записи, соответствующие условию запроса первой транзакции, либо удаляет существующие.
- Невидимость для уровня READ COMMITTED — на уровне изоляции READ COMMITTED фантомное чтение возможно, так как блокировки на диапазон данных не устанавливаются.
Отличие от других аномалий
Для более точного понимания фантомного чтения необходимо различать его с другими аномалиями параллельного доступа:
- Грязное чтение (Dirty Read) — транзакция читает данные, изменённые другой транзакцией, но ещё не зафиксированные. При фантомном чтении обязательно читаются только зафиксированные данные.
- Неповторяющееся чтение (Non-repeatable Read) — при повторном чтении одной и той же строки её содержимое может измениться. При фантомном чтении меняется количество строк, а не значения в одной строке.
- Потерянное обновление (Lost Update) — результат двух параллельных обновлений одной строки, при котором одно из изменений теряется. Фантомное чтение не связано с потерей данных.
Уровни изоляции транзакций в SQL
Стандарт SQL-92 определяет четыре уровня изоляции транзакций, которые предотвращают различные степени аномалий. Отношение к фантомному чтению выглядит следующим образом:
| Уровень изоляции | Грязное чтение | Неповторяющееся чтение | Фантомное чтение |
|---|---|---|---|
| READ UNCOMMITTED | Возможно | Возможно | Возможно |
| READ COMMITTED | Невозможно | Возможно | Возможно |
| REPEATABLE READ | Невозможно | Невозможно | Возможно (в большинстве СУБД) |
| SERIALIZABLE | Невозможно | Невозможно | Невозможно |
Особенности на уровне REPEATABLE READ
Хотя стандарт SQL предполагает, что на уровне REPEATABLE READ фантомное чтение возможно, многие современные СУБД (например, PostgreSQL, MySQL с InnoDB) на этом уровне предотвращают данную аномалию. Это достигается за счёт использования механизмов многоверсионности (MVCC) и блокировок диапазона (gap locks/next-key locks). В таких системах REPEATABLE READ эквивалентен SERIALIZABLE в отношении фантомов, но без полной блокировки табличных операций.
Пример ситуации
Рассмотрим две параллельные транзакции в банковской системе для иллюстрации фантомного чтения:
- Транзакция A начинает работу и выполняет запрос:
SELECT COUNT(*) FROM accounts WHERE balance > 10000;— результат: 5 счетов. - Транзакция B в это же время вставляет новый счёт с балансом 15000 и фиксирует изменения (
COMMIT). - Транзакция A повторяет тот же запрос:
SELECT COUNT(*) FROM accounts WHERE balance > 10000;— результат: 6 счетов.
Причина появления «фантомной» строки (нового счёта) — транзакция A не наложила блокировку на диапазон balance > 10000, поэтому вставка от транзакции B стала видимой.
Причины возникновения
Фантомное чтение возникает из-за отсутствия или недостаточности блокировок на уровне диапазона данных. Базовые механизмы:
- Блокировки строк (Row Locks) — предотвращают изменение существующих записей, но не мешают вставке новых строк в диапазон.
- Блокировки таблицы (Table Locks) — могут предотвратить любые изменения, но сильно снижают производительность системы.
- Многоверсионность (MVCC) — может скрывать от транзакции изменения, сделанные другими транзакциями до момента фиксации, но не защищает от появления новых зафиксированных записей, если не используется дополнительная блокировка диапазона.
Способы предотвращения
1. Уровень изоляции SERIALIZABLE
Наиболее надёжный, но и самый ресурсоёмкий способ. Система гарантирует, что транзакции выполняются так, как если бы они шли последовательно. Для этого часто применяются блокировки диапазона (gap locks) или полные блокировки таблицы.
2. Блокировки диапазона (Gap Locks, Next-Key Locks)
Некоторые СУБД, например MySQL с движком InnoDB, реализуют так называемые next-key locks, которые блокируют не только существующие строки, но и пространство между ними. Это предотвращает вставку новых записей, попадающих в диапазон, на который наложена блокировка.
3. Многоверсионность с моментальными снимками (Snapshot Isolation)
Используется, например, в PostgreSQL и Oracle. Транзакция получает «снимок» базы данных на момент своего начала. Все последующие изменения от других транзакций не видны, даже если они зафиксированы. Однако в некоторых реализациях (в зависимости от механизма) фантомное чтение может сохраняться для запросов, использующих недетерминированные функции или условия, зависящие от времени.
Последствия и влияние на бизнес-логику
Фантомное чтение может иметь серьёзные последствия для целостности данных и бизнес-процессов:
- Некорректные отчёты — агрегатные функции (SUM, COUNT, AVG), выполняемые в рамках одной транзакции, могут давать непоследовательные результаты.
- Нарушение валидации — проверки, такие как «убедиться, что количество заказов меньше 10», могут дать сбой, если между первым и вторым чтением была произведена вставка.
- Ошибки синхронизации — в системах с распределёнными данными фантомное чтение может привести к рассинхронизации состояний между различными узлами.
Реализации в конкретных СУБД
PostgreSQL
- Уровень изоляции по умолчанию — READ COMMITTED.
- На уровне REPEATABLE READ фантомное чтение полностью предотвращается благодаря использованию многоверсионности, которая изолирует снимки данных.
- SERIALIZABLE обеспечивает строжайшую защиту, но может вызывать откаты транзакций при конфликтах.
MySQL (InnoDB)
- Уровень по умолчанию — REPEATABLE READ.
- Для предотвращения фантомов использует next-key locks (блокирует строки и промежутки между ними).
- На уровне READ COMMITTED фантомное чтение возможно, но блокировки на диапазон не устанавливаются.
Microsoft SQL Server
- Уровень по умолчанию — READ COMMITTED.
- На уровне REPEATABLE READ фантомное чтение возможно (стандартное поведение).
- Для предотвращения требуется уровень SERIALIZABLE или использование подсказок блокировок (например,
SERIALIZABLEв запросе).
Oracle
- Уровень по умолчанию — READ COMMITTED.
- Использует многоверсионность (MVCC) на основе системных номеров изменений (SCN).
- На уровне SERIALIZABLE обеспечивает полную защиту от фантомных чтений за счёт изолированных снимков.
Возможные пути решения на уровне приложения
Если база данных не поддерживает SERIALIZABLE или REPEATABLE READ с защитой от фантомов, разработчики могут применить следующие тактики:
- Оптимистическая блокировка — использование версии строк (например, поля
version) для обнаружения конфликтов. Если при повторном чтении версия изменилась, транзакция откатывается или повторяется. - Пессимистическая блокировка диапазона — выполнение запроса с указанием
FOR UPDATEилиFOR SHARE(если это блокирует диапазон), хотя это не всегда предотвращает вставку новых записей. - Ручное управление блокировками — в некоторых СУБД возможно использование блокировок на уровне таблицы или диапазона ключей через явные команды.
- Изоляция на уровне бизнес-логики — разделение операций на атомарные шаги или использование временных меток для проверки идентичности данных.
Критика и ограничения
Хотя фантомное чтение считается аномалией, некоторые исследователи отмечают, что для многих прикладных задач его предотвращение излишне. Системы, работающие с большими объёмами данных и высокими нагрузками, часто сознательно жертвуют строгой изолированностью ради производительности. Кроме того, реализация защиты от фантомов (особенно через блокировки диапазона) может приводить к существенным снижениям пропускной способности СУБД.
Альтернативные модели согласованности (например, «ленивая репликация» (lazy replication) или «событийная согласованность» (eventual consistency)) часто допускают фантомное чтение как допустимое для некритичных операций.
Источники
- Кодд Э. Ф. «Реляционная модель данных для больших банков данных» — 1970.
- Стандарт ISO/IEC 9075-1:2016 «Информационные технологии — Языки баз данных — SQL — Часть 1: Структура».
- Грей Дж., Рейтер А. «Транзакционные системы: Управление параллелизмом и восстановлением» — 1993.
- Бернштейн Ф., Гудман Н. «Управление параллелизмом в распределённых базах данных» — ACM Computing Surveys, 1981.
- Постгрес Документация «Уровни изоляции транзакций» — PostgreSQL Global Development Group.
- InnoDB Locking — MySQL 8.0 Reference Manual.
BFOmetr — база данных и аналитика по компаниям России.
На главную BFOmetr →