Проблема, которую мы пытаемся решить, выглядит так.
- У нас есть таблица, полная строк, представляющих карты. Цель транзакции резервирования - назначить карту клиенту.
- Карта не может принадлежать многим клиентам
- Через некоторое время (если он не куплен) карта должна быть возвращена в пул доступных возобновлений.
- Бронирование может выполняться одновременно несколькими клиентами.
- Мы используем базу данных Oracle для хранения данных, поэтому решение должно работать как минимум на Oracle 11
Нашим решением является присвоение статуса карте и сохранение ее даты резервирования. При резервировании карты мы делаем это, используя инструкцию "select for update". Запрос ищет доступные карты и карты, которые были зарезервированы давно.
Однако наш запрос не работает должным образом.
Я подготовил упрощенную ситуацию, чтобы объяснить проблему. У нас есть таблица 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;
Ожидаемое поведение заключается в том, что в обоих сеансах мы получаем одну, другую строку, которая удовлетворяет условиям запроса.
Однако это не работает. В зависимости от того, используем ли мы "пропущенную блокировку" часть запроса или нет - поведенческие изменения:
- без "пропустить блокировку" - второй сеанс заблокирован - ожидание транзакции фиксации или откат в сеансе 1
- с "skip locked" - второй запрос возвращает сразу пустой набор результатов
Итак, после этого длинного введения возникает вопрос.
Возможно ли такое поведение блокировки в Oracle? Если да, то что мы делаем неправильно? Каким будет правильное решение?