Могу ли я сделать атомарный MERGE в Oracle? - PullRequest
17 голосов
/ 19 ноября 2010

У меня есть пара экземпляров приложения J2EE, работающего в одном кластере WebLogic.

В какой-то момент эти приложения выполняют MERGE для вставки или обновления записи в серверной базе данных Oracle.MERGE проверяет, есть ли строка с указанным первичным ключом или нет.Если это там, обновите.Если нет, вставьте.

Теперь предположим, что два экземпляра приложения хотят вставить или обновить строку с первичным ключом = 100. Предположим, что строка не существует.На этапе «проверки» слияния они оба видят, что строк нет, поэтому они оба пытаются вставить.Затем я получаю уникальное нарушение ограничения ключа.

У меня такой вопрос: есть ли в Oracle атомарный MERGE?Я ищу что-то, что имеет эффект, аналогичный INSERT ... FOR UPDATE в PL / SQL, за исключением того, что я могу выполнять SQL только из своих приложений.

РЕДАКТИРОВАТЬ: Мне было неясно.Я использую инструкцию MERGE, пока эта ошибка все еще происходит.Дело в том, что только «модифицирующая» часть является атомарной, а не целое слияние.

Ответы [ 4 ]

15 голосов
/ 20 ноября 2010

Это не проблема с MERGE как таковой.Скорее проблема заключается в вашем приложении.Рассмотрим эту хранимую процедуру:

create or replace procedure upsert_t23 
    ( p_id in t23.id%type
      , p_name in t23.name%type )
is
    cursor c is
        select null 
        from t23
        where id = p_id;
    dummy varchar2(1);
begin
    open c;
    fetch c into dummy;
    if c%notfound then
        insert into t23 
            values (p_id, p_name);
    else
        update t23
             set name = p_name
             where id = p_id;
    end if;
 end;

Итак, это PL / SQL-эквивалент MERGE на T23.Что произойдет, если две сессии будут вызывать его одновременно?

SSN1>  exec upsert_t23(100, 'FOX IN SOCKS')

SSN2>  exec upsert_t23(100, 'MR KNOX')

SSN1 сначала попадает туда, не находит подходящей записи и вставляет запись.SSN2 становится там вторым, но до того, как SSN1 фиксирует, не находит записи, вставляет запись и вешает , потому что SSN1 имеет блокировку на уникальном индексном узле на 100. Когда SSN1 фиксирует SSN2, будет возникать нарушение DUP_VAL_ON_INDEX.

Оператор MERGE работает точно так же.Обе сессии проверят on (t23.id = 100), не найдут его и пойдут вниз по ветке INSERT.Первый сеанс будет успешным, а второй - ORA-00001.

Один из способов справиться с этим - ввести пессимистическую блокировку.В начале процедуры UPSERT_T23 мы блокируем таблицу:

...
lock table t23 in row shared mode nowait;
open c;
...

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

Нет синтаксиса для INSERT, который эквивалентен SELECT ... FOR UPDATE,потому что нечего выбирать.И поэтому для MERGE такого синтаксиса тоже нет.Вам нужно включить оператор LOCK TABLE в программный модуль, который выдает MERGE.Возможно ли это для вас, зависит от используемой вами платформы.

6 голосов
/ 22 ноября 2010

Оператор MERGE во втором сеансе не может "видеть" вставку, которую сделал первый сеанс, пока этот сеанс не завершится.Если вы уменьшите размер транзакций, вероятность того, что это произойдет, будет уменьшена.

Или вы можете отсортировать или разбить ваши данные так, чтобы все записи данного первичного ключа были переданы одному и тому же сеансу.Простая функция, такая как «первичный ключ мод N», должна равномерно распределяться между сеансами N.

Кстати, если две записи имеют одинаковый первичный ключ, вторая будет перезаписывать первую.Звучит немного странно.

3 голосов
/ 19 ноября 2010

Да, и он называется .... MERGE

РЕДАКТИРОВАТЬ : Единственный способ сделать это водонепроницаемым - это вставить, перехватить исключение dup_val_on_index иобработать его соответствующим образом (обновить или вставить другую запись, возможно).Это легко сделать с помощью PL / SQL, но вы не можете это использовать.

Вы также ищете обходные пути.Можете ли вы перехватить dup_val_on_index в Java и снова выполнить дополнительное ОБНОВЛЕНИЕ?

В псевдокоде:

try {
  // MERGE
}
catch (dup_val_on_index) {
  // UPDATE
}
2 голосов
/ 19 ноября 2010

Я удивлен, что MERGE будет вести себя так, как вы описываете, но я недостаточно использовал его, чтобы сказать, следует или нет.

В любом случае у вас могут быть транзакции, которые вы хотите выполнитьобъединение установило их уровень изоляции в SERIALIZABLE.Я думаю, что это может решить вашу проблему.

...