Ответ
Фантомное чтение — это аномалия параллельного доступа, при которой одна транзакция при повторном выполнении одного и того же запроса видит новые строки («фантомы»), которые были добавлены другой, уже завершённой транзакцией в промежутке между этими чтениями.
Отличие от других аномалий:
- Неповторяющееся чтение (Non-repeatable read): Изменяются данные в существующих строках.
- Фантомное чтение: Появляются совершенно новые строки, удовлетворяющие условию запроса.
Пример сценария:
-- Уровень изоляции: READ COMMITTED (или ниже)
-- Транзакция A | -- Транзакция B
BEGIN TRANSACTION; |
SELECT * FROM users WHERE age > 25; |
-- Результат: 3 строки |
| BEGIN TRANSACTION;
| INSERT INTO users (name, age) VALUES ('Carl', 30);
| COMMIT; -- Добавлена новая строка с age=30
SELECT * FROM users WHERE age > 25; |
-- Результат: 4 строки! Появился "фантом" |
COMMIT; |
Как предотвратить:
- Уровень изоляции
SERIALIZABLE– Гарантированно предотвращает фантомные чтения, блокируя диапазоны строк. - Использование диапазонных блокировок – Например,
SELECT ... FOR UPDATEс условием, которое блокирует вставку в защищаемый диапазон (зависит от СУБД).
В Spring (Java):
@Transactional(isolation = Isolation.SERIALIZABLE)
public void someCriticalMethod() {
// ... логика, чувствительная к фантомному чтению
}
Почему это проблема: Фантомные чтения могут нарушить бизнес-логику, основанную на агрегации или проверке существования данных (например, подсчёт суммы по условию или проверка уникальности).
Ответ 18+ 🔞
Да ты посмотри, какая дичь может твориться в базе, если за ней не следить! Есть такая штука — фантомное чтение. Это когда ты в одной транзакции два раза подряд одно и то же спрашиваешь, а тебе во второй раз уже другие данные подсовывают, будто призраки материализовались! Новые строки, которых в первый раз на хуй не было, вдруг появляются, потому что другая транзакция успела их впендюрить и закрыться.
Чем это не похоже на другую ерунду:
- Неповторяющееся чтение: Это когда данные в старых строках меняются, как погода в Питере.
- Фантомное чтение: А это когда совершенно новые строки, сука, из ниоткуда возникают, под твоё условие подходят.
Смотри, как это выглядит в жизни:
-- Уровень изоляции: READ COMMITTED (или ниже)
-- Транзакция A | -- Транзакция B
BEGIN TRANSACTION; |
SELECT * FROM users WHERE age > 25; |
-- Результат: 3 строки |
| BEGIN TRANSACTION;
| INSERT INTO users (name, age) VALUES ('Carl', 30);
| COMMIT; -- Добавлена новая строка с age=30
SELECT * FROM users WHERE age > 25; |
-- Результат: 4 строки! Появился "фантом" |
COMMIT; |
Вот тебе и Карл, ебаный вротберунчик! Сидел ты, считал своих старичков, а тут бац — и новый дед материализовался между двумя твоими запросами. Чистая магия, только вот бизнес-логике от такой магии — пиздец.
Как этой херни избежать:
- Взять уровень изоляции
SERIALIZABLE— это как надеть смирительную рубашку на всю базу. Он гарантированно не пропустит ни одного фантома, потому что будет блокировать целые диапазоны, чтобы никто новенького не всунул. - Ручные блокировки диапазонов — типа
SELECT ... FOR UPDATE. Скажешь базе: «Смотри, я тут эти возрастные рамки охраняю, не вставляй сюда ни хуя!». Но это уже от конкретной СУБД зависит, насколько она послушная.
Если ты на Spring:
@Transactional(isolation = Isolation.SERIALIZABLE)
public void someCriticalMethod() {
// ... тут твоя сверхважная логика, где появление призраков недопустимо
}
А проблема-то в чём, спросишь? Да в том, что вся твоя хитрая логика, которая на подсчётах или проверках «а был ли мальчик?» построена, может накрыться медным тазом. Представь, ты сумму по условию посчитал, решил, что всё ок, а в момент между проверкой и действием уже влез какой-нибудь фантом и всё испохабил. Короче, доверия к таким данным — ноль ебать.