Oracle выбирают для обновления поведения - PullRequest
18 голосов
/ 01 мая 2011

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

  • У нас есть таблица, полная строк, которые представляют карты.Цель транзакции бронирования - присвоить клиенту карту
  • Карта не может принадлежать многим клиентам
  • Через некоторое время (если оно не куплено) карту необходимо вернутьпул доступных ресурсов
  • Резервирование может выполняться многими клиентами одновременно
  • Мы используем базу данных Oracle для хранения данных, поэтому решение должно работать как минимум на Oracle 11

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

Однако наш запрос не работает должным образом.

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

-- first, in session 1
set autocommit off;

select id from card_numbers  
where id is not null  
and rownum <= 1  
for update skip locked;

Мы не фиксируем транзакцию здесь, строка должна быть заблокирована.

-- later, in session 2
set autocommit off;

select id from card_numbers  
where id is not null  
and rownum <= 1  
for update skip locked;

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

Однако так не получается.В зависимости от того, используем ли мы часть запроса «пропустить заблокирован» или нет - поведение меняется:

  • без «пропуска заблокирован» - второй сеанс блокируется - ожидание фиксации транзакции или отката в первом сеансе
  • с «пропуском заблокирован» - второй запрос немедленно возвращает пустой набор результатов

Итак, после этого длинного вступления возникает вопрос.

Возможно ли желаемое поведение блокировки в Oracle?Если да, то что мы делаем не так?Какое будет правильное решение?

Ответы [ 3 ]

17 голосов
/ 01 мая 2011

Поведение, с которым вы столкнулись при использовании FOR UPDATE SKIP LOCKED, описано в этой заметке в блоге .Насколько я понимаю, предложение FOR UPDATE оценивается после предложения WHERE.SKIP LOCKED похож на дополнительный фильтр, который гарантирует, что среди строк, которые были бы возвращены, ни одна из них не заблокирована.

Ваш оператор логически эквивалентен: найдите первую строку из card_numbers и верните ее, если онане заблокированОчевидно, это не то, что вам нужно.

Вот небольшой тестовый пример, который воспроизводит описанное вами поведение:

SQL> CREATE TABLE t (ID PRIMARY KEY)
  2  AS SELECT ROWNUM FROM dual CONNECT BY LEVEL <= 1000;

Table created

SESSION1> select id from t where rownum <= 1 for update skip locked;

        ID
----------
         1

SESSION2> select id from t where rownum <= 1 for update skip locked;

        ID
----------

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

SQL> CREATE FUNCTION get_and_lock RETURN NUMBER IS
  2     CURSOR c IS SELECT ID FROM t FOR UPDATE SKIP LOCKED;
  3     l_id NUMBER;
  4  BEGIN
  5     OPEN c;
  6     FETCH c INTO l_id;
  7     CLOSE c;
  8     RETURN l_id;
  9  END;
 10  /

Function created

SESSION1> variable x number;
SESSION1> exec :x := get_and_lock;

PL/SQL procedure successfully completed
x
---------
1

SESSION2> variable x number;
SESSION2> exec :x := get_and_lock;

PL/SQL procedure successfully completed
x
---------
2

Поскольку я явно извлек курсор, будет возвращена только одна строка (и только одна строка будет заблокирована).

6 голосов
/ 07 июня 2011

Хотя другие ответы уже достаточно объяснили, что происходит в вашей базе данных с различными вариантами SELECT .. FOR UPDATE, я думаю, стоит упомянуть, что Oracle не рекомендует использовать FOR UPDATE SKIP LOCKED напрямую и рекомендует использовать Oracle AQ вместо:

http://download.oracle.com/docs/cd/B28359_01/server.111/b28286/statements_10002.htm#i2066346

Мы используем Oracle AQ в нашем приложении, и я могу подтвердить, что после некоторой крутой кривой обучения это может быть довольно удобным способом работы с производителями / потребителями непосредственно в базе данных

3 голосов
/ 02 мая 2011

Не то, чтобы ответ Винсента был неправильным, но я бы разработал его по-другому.

Мой первый инстинкт - выбрать для обновления первую доступную запись и обновить запись с помощью «reserved_date». После того, как пройдет XXX время, и транзакция не будет завершена, обновите параметр reserved_date для записи обратно до нуля, снова освободив запись.

Я стараюсь сделать вещи максимально простыми. Для меня это проще.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...