Фантомная аномалия чтения в Oracle и PostgreSQL не откатывает транзакцию

я заметил следующее явление как в Oracle, так и в PostgreSQL.

учитывая, что у нас есть следующая схема базы данных:

create table post (
    id int8 not null, 
    title varchar(255), 
    version int4 not null, 
    primary key (id));    

create table post_comment (
    id int8 not null, 
    review varchar(255), 
    version int4 not null, 
    post_id int8, 
    primary key (id));

alter table post_comment 
    add constraint FKna4y825fdc5hw8aow65ijexm0 
    foreign key (post_id) references post;  

со следующими данными:

insert into post (title, version, id) values ('Transactions', 0, 1);
insert into post_comment (post_id, review, version, id) 
    values (1, 'Post comment 1', 459, 0);
insert into post_comment (post_id, review, version, id) 
    values (1, 'Post comment 2', 537, 1);
insert into post_comment (post_id, review, version, id) 
    values (1, 'Post comment 3', 689, 2); 

если я открою две отдельные консоли SQL и выполню следующие инструкции:

TX1: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;

TX2: BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;

TX1: SELECT COUNT(*) FROM post_comment where post_id = 1;

TX1: > 3

TX1: UPDATE post_comment SET version = 100 WHERE post_id = 1;

TX2: INSERT INTO post_comment (post_id, review, version, id) VALUES (1, 'Phantom', 0, 1000);

TX2: COMMIT;

TX1: SELECT COUNT(*) FROM post_comment where post_id = 1;

TX1: > 3

TX1: COMMIT;

TX3: SELECT * from post_comment;

     > 0;"Post comment 0";100;1
       1;"Post comment 1";100;1
       2;"Post comment 2";100;1
       1000;"Phantom";0;1

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

из-за модели MVCC в Oracle и PostgreSQL TX2 разрешено вставлять новую запись и фиксировать.

почему TX1 разрешено совершать? Поскольку это фантомная аномалия чтения, я ожидал увидеть, что TX1 будет откатываться с "исключением сбоя сериализации" или чем-то подобным.

Сериализуемая модель MVCC в PostgreSQL и Oracle предлагает только гарантию изоляции моментальных снимков, но не фантомную аномалию чтения обнаружение?

обновление

я даже изменил Tx1, чтобы выпустить инструкцию обновления, которая изменяет для всех post_comment записи, принадлежащие тому же post.

таким образом, Tx2 создает новую запись, и Tx1 собирается совершить, не зная, что добавлена новая запись, удовлетворяющая критериям фильтрации обновлений.

на самом деле, единственный способ сделать его сбой на PostgreSQL - это выполнить следующий запрос COUNT в TX2, ранее чтобы вставить фантомную запись:

Tx2: SELECT COUNT(*) FROM post_comment where post_id = 1 and version = 0

TX2: INSERT INTO post_comment (post_id, review, version, id) VALUES (1, 'Phantom', 0, 1000);

TX2: COMMIT;

затем Tx1 будет откат с:

org.postgresql.util.PSQLException: ERROR: could not serialize access due to read/write dependencies among transactions
  Detail: Reason code: Canceled on identification as a pivot, during conflict out checking.
  Hint: The transaction might succeed if retried.

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

интересно, что Oracle, похоже, не беспокоит эта аномалия, и поэтому Tx1 просто успешно фиксируется. Поскольку Oracle не предотвращает перекос записи, Tx1 фиксирует juts отлично.

кстати, вы можете запустить все эти примеры сами, так как они находятся на GitHub.

4 ответов


то, что вы наблюдаете-это не фантомное чтение. Это было бы, если бы новая строка появилась, когда запрос выдается во второй раз (фантомы появляются неожиданно).

вы защищены от фантомных чтений как в Oracle, так и в PostgreSQL с помощью SERIALIZABLE изоляции.

разница между Oracle и PostgreSQL заключается в том, что SERIALIZABLE уровень изоляции в Oracle предлагает только изоляцию моментальных снимков (что достаточно хорошо, чтобы фантомы не появлялись), в то время как в PostgreSQL гарантирует истинную сериализуемость (т. е. всегда существует сериализация операторов SQL, которая приводит к тем же результатам). Если вы хотите получить то же самое в Oracle и PostgreSQL, используйте REPEATABLE READ изоляция в PostgreSQL.


мне нравится этот вопрос, потому что он демонстрирует, что фантомное определение чтения в стандарт SQL только изображения эффекта без указания основной причины этой аномалии данных:

P3 ("Phantom"): SQL-транзакция T1 считывает набор строк N это удовлетворяет некоторых . SQL-транзакция T2 тогда выполняет SQL-операторы, которые генерируют одну или несколько строк, удовлетворяйте используемой SQL-транзакцией T1. Если SQL-транзакция T1 затем повторяет начальное чтение с тем же , он получает другую коллекцию строк.

в газете 1995, критика уровней изоляции ANSI SQL, Джим Грей и ко, описал Фантома так:

P3: r1[P]...П2[г р]...(c1 или a1) (Фантом)

важно отметить, что ANSI SQL P3 запрещает только вставки (и обновления, согласно некоторым интерпретациям) к предикат тогда как определение P3 выше запрещает любую запись, удовлетворяющую предикату как только предикат был прочитан-запись может быть вставкой, обновить или удалить.

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

в исходном SQL Реализация сервера 2PL (двухфазная блокировка), возвращающая тот же результат для блокировки предикатов запроса.

изоляция моментальных снимков MVCC (Multi-Version Concurrency Control) (ошибочно названная Сериализуемой в Oracle) фактически не запрещает другим транзакциям вставлять/удалять строки, соответствующие тем же критериям фильтрации, с запросом, который уже выполнен и вернул результирующий набор в нашей текущей текущей транзакции.

по этой причине, мы можем представить себе следующий сценарий, в котором мы хотим применить повышение ко всем сотрудникам:

  1. Tx1:SELECT SUM(salary) FROM employee where company_id = 1;
  2. Tx2:INSERT INTO employee (id, name, company_id, salary) VALUES (100, 'John Doe', 1, 100000);
  3. Tx1:UPDATE employee SET salary = salary * 1.1;
  4. Tx2:COMMIT;
  5. Tx1:COMMIT:

в этом сценарии генеральный директор запускает первую транзакцию (Tx1), поэтому:

  1. сначала она проверяет сумму всех зарплат в своей компании.
  2. между тем, отдел кадров выполняет вторую транзакцию (Tx2) поскольку им только что удалось нанять Джона Доу и дать ему зарплату в 100 тысяч долларов.
  3. генеральный директор решает, что повышение на 10% возможно с учетом общей суммы зарплат, не зная, что сумма зарплаты выросла со 100 тыс.
  4. между тем, транзакция HR TX2 зафиксирована.
  5. совершена транзакция CEO Tx1.

бум! Генеральный директор принял решение о старом снимке, давая повышение, которое может не поддерживаться текущий обновленный бюджет заработной платы.

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

это фантомное чтение или Написать Skew?

по данным Джим Грей и co, это фантомное чтение, так как перекос записи определяется как:

A5b написать косо предположим, что T1 читает x и y, которые согласуются с C (), а затем T2 читает x и y, записывает x и фиксирует. Тогда Т1 пишет y. Если бы было ограничение между x и y, это могло бы быть нарушаться. В терминах истории:

A5B: r1[x]...r2[y]...w1[y]...w2[x]...(происходят С1 и С2)

в Oracle диспетчер транзакций может или не может обнаружить аномалию выше, потому что он не использует блокировки предикатов или диапазон индексов замков (следующие ключевые замки), как MySQL.

PostgreSQL удается поймать эту аномалию, только если Боб выдает считывание против таблицы employee, в противном случае явление не предотвращается.

обновление

Первоначально я предполагал, что Сериализуемость также будет подразумевать порядок времени. Однако, как очень хорошо объяснено Питером Бейлисом, заказ настенных часов или Линеаризуемость предполагается только для строгой сериализации.

поэтому мои предположения были сделаны для строгой Сериализуемой системы. Но это не то, что Serializable должен предложение. Сериализуемая модель изоляции не гарантирует время, и операции могут быть переупорядочены, если они эквивалентны некоторые последовательного выполнения.

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


документация Postgres определяет фантомное чтение as:

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

поскольку select возвращает одно и то же значение как до, так и после совершения другой транзакции, оно не соответствует критериям Фантома читать.


Я просто хотел указать, что ответ Влада Михальчи совершенно неверен.

это фантомное чтение или перекос записи?

ни один из них -- здесь нет аномалии, транзакции сериализуются как Tx1 - > Tx2.

стандартные состояния SQL: "Сериализуемое выполнение определяется как выполнение операций одновременного выполнения SQL-транзакций, которые производят тот же эффект, что и некоторые серийное исполнение тех те же SQL-транзакции."

PostgreSQL удается поймать эту аномалию только в том случае, если Боб выдает чтение против таблицы employee, иначе это явление не предотвращается.

поведение PostgreSQL здесь на 100% правильное, оно просто "переворачивает" очевидный порядок транзакций.