Как сделать сериализуемым MERGE

При выполнении параллельного MERGE, в то время как каждый сеанс использует другое значение (показано ниже в сноске ниже) для столбца первичного ключа id, все нормально, если я делаю это вручную в двух сеансах терминала.

MERGE
 INTO x
   USING (SELECT *** as id FROM DUAL) MERGE_SRC
     ON (x.id = MERGE_SRC.id)
 WHEN MATCHED THEN UPDATE SET val = val + 1 WHERE id = ***
 WHEN NOT MATCHED THEN INSERT VALUES (***, 99);
COMMIT;

Однако при запуске многопоточного теста нагрузки с тремя или более потоками я буду относительно быстро работать в ORA-08177 with locked table. Почему это? (И почему он не детерминирован в том смысле, что это не всегда происходит, когда транзакции перекрываются?)

Таблица была создана с помощью

create table x (id int primary key, val int);

SQL Server btw никогда не выдает исключения с эквивалентным оператором MERGE, выполняющим тот же эксперимент. Это даже верно при одновременной работе с одной строкой.

Это потому, что, возможно, MERGE не является атомарным, а сериализуемый режим работает оптимистично, так что гонка может отображаться только с достаточным количеством споров? Тем не менее, почему это происходит , даже если одновременно не работает одна и та же строка?

Btw, мои попытки исправить это, используя самый строгий доступный замок, не увенчались успехом. Поэтому любые идеи о том, как сделать этот атом, очень ценятся. Похоже, что ослабление уровня изоляции избавит меня от исключения, но риск несоответствий в случае, если в одной строке окажется 2 обновления (в противном случае, почему бы это не произошло в сериализованном режиме в первую очередь).

Ответ 1

Исключение, которое вы видите, является прямым следствием использования строгой сериализации. Если одновременно активировано несколько транзакций, каждый из них начинается с SET SERACIZABLE LEVEL SERIALIZABLE, когда кто-либо из них совершает другие, получит ORA-08177. Это то, как строгая сериализация выполняется - база данных выдает ORA-08177 в любом сеансе, запущенном с LEOL SERIALIZABLE, если другая транзакция фиксируется в таблице, которую требует сериализуемый сеанс. Итак, в принципе, если вам действительно нужна строгая сериализация, вы должны обрабатывать ORA-08177 разумно, как в следующем:

DECLARE
  bSerializable_trans_complete  BOOLEAN := FALSE;
  excpSerializable              EXCEPTION;
  PRAGMA EXCEPTION_INIT(excpSerializable, -08177);
BEGIN
  <<SERIALIZABLE_LOOP>>
  WHILE NOT bSerializable_trans_complete
  LOOP
    BEGIN
      SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

      MERGE ...; -- or whatever

      COMMIT;

      bSerializable_trans_complete := TRUE;  -- allow SERIALIZABLE_LOOP to exit
    EXCEPTION
      WHEN excpSerializable THEN
        ROLLBACK;
        CONTINUE SERIALIZABLE_LOOP;
    END;
  END LOOP;  -- SERIALIZABLE_LOOP
END;

Сериализация - это не волшебство, и это не "бесплатно" (где "свободный" означает "Я, как разработчик, не должен ничего делать, чтобы заставить его работать правильно" ). Это требует большего планирования и работы со стороны разработчика, чтобы он функционировал должным образом, а не меньше. Поделитесь и наслаждайтесь.

Ответ 2

В Oracle режим SERIALIZABLE работает оптимистично, в отличие от, например, SQL Server, который делает пессимистическую блокировку в этом режиме. Это означает, что в последнем случае вы даже можете изменить одну и ту же строку без использования исключений.

Несмотря на документы:

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

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