Счетчик строк в 2 одновременных транзакциях - PullRequest
0 голосов
/ 17 октября 2018

У меня есть следующий код, который обновляет клиента и, если он не существует, он вставляет / создает его.

UPDATE CUSTOMER SET ...
IF SQL%rowcount = 0 THEN
  INSERT INTO customer ..

Проблема заключается в том, что этот код находится в транзакции.На производстве я случайно получаю следующую ошибку:

ORA-00001: unique constraint (..) violated at the line with the INSERT INTO customer .. (see above)

Мой вопрос: как работают транзакции Oracle?У меня есть 2 транзакции одновременно (редко, но может случиться).Скажем, транзакция 1 прошла IF с rowcount = 0 и еще не зафиксирована.Скажем, транзакция 2 прошла IF с rowcount = 0 (потому что транзакция 1 еще ничего не вставила).Затем транзакция 1 фиксируется.Затем транзакция 2 фиксируется, она «сохраняет» rowcount = 0 или снова проверяет IF SQL% rowcount = 0 THEN, чтобы учесть, какая транзакция 1 зафиксирована?

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

Может помочь установить уровень ИЗОЛЯЦИИ для конкретной транзакции?

1 Ответ

0 голосов
/ 21 октября 2018

Да, именно так и происходит.Время должно быть правильным, поэтому оно прерывистое.Вы можете убедиться в этом, если откроете два сеанса sqlplus.

  1. Сессия A: Обновления, строки не изменены, поэтому SQL%ROWCOUNT = 0.
  2. Сессия B: Обновления, строки не изменены, поэтомуSQL%ROWCOUNT = 0.
  3. Сеанс A: проходит условие IF, вставка тоже.
  4. Сессия B: проходит условие IF, вставка тоже.
  5. Сессия A: фиксирует.
  6. Сессия B: фиксирует.К сожалению, есть нарушение ограничения, потому что теперь я вижу изменения сеанса A.

Изменение уровня изоляции вам не поможет.Oracle не имеет никакого уровня изоляции, который позволял бы вам видеть незафиксированные изменения из другого сеанса (и это хорошо).

Первое, что нужно сделать, это поменять UPDATE и INSERT на оператор MERGE.Таким образом, у вас есть только одно утверждение, которое будет успешным или неудачным.Насколько я понимаю, вставка и обновление, разделенные условием, являются антипаттерном.Примерно так:

MERGE INTO customer
USING ( SELECT customer_name FROM wherever ) source
   ON ( source.customer_name = customer.customer_name )
 WHEN NOT MATCHED THEN INSERT VALUES ( source.customer_name )
 WHEN MATCHED THEN UPDATE SET ( customer_name = source.customer_name );

Недостаток MERGE заключается в том, что в нем нет предложения RETURNING INTO, поэтому, если вам это нужно, вы должны использовать команду select / insert.

Во-вторых, чтобы остановить одновременную вставку двух сессий, вам нужно сделать:

LOCK TABLE customer IN SHARE MODE;

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

...