Решение, опубликованное Гэри Мейерсом, - это почти все, что я могу придумать, кроме использования AQ, которое делает все это для вас и многое другое.
Если вы действительно хотите избежать PLSQL, вы должны иметь возможностьперевести PLSQL в вызовы Java JDBC.Все, что вам нужно сделать, это подготовить один и тот же оператор SQL, выполнить его, а затем продолжать выполнять выборки из одной строки (или N выборок строк).
Документация Oracle по http://download.oracle.com/docs/cd/B10501_01/java.920/a96654/resltset.htm#1023642 дает некоторое представление о том, какчтобы сделать это на уровне оператора:
Чтобы установить размер выборки для запроса, вызовите setFetchSize () для объекта оператора перед выполнением запроса.Если вы установите размер выборки равным N, тогда N строк будут выбираться при каждой поездке в базу данных.
Таким образом, вы можете кодировать что-то в Java, что-то похожее (в псевдокоде):
stmt = Prepare('SELECT /*+FIRST_ROWS_1*/ qt.ID
FROM QueueTest qt
WHERE Locked IS NULL
ORDER BY PRIORITY
FOR UPDATE SKIP LOCKED');
stmt.setFetchSize(10);
stmt.execute();
batch := stmt.fetch();
foreach row in batch {
-- process row
}
commit (to free the locks from the update)
stmt.close;
ОБНОВЛЕНИЕ
На основе комментариев ниже было предложено использовать ROWNUM для ограничения полученных результатов, но в этом случае это не сработает.Рассмотрим пример:
create table lock_test (c1 integer);
begin
for i in 1..10 loop
insert into lock_test values (11 - i);
end loop;
commit;
end;
/
Теперь у нас есть таблица с 10 строками.Обратите внимание, что я аккуратно вставил строки в обратном порядке, сначала строка, содержащая 10, затем 9 и т. Д.
Скажем, вы хотите, чтобы первые 5 строк были упорядочены по возрастанию - то есть от 1 до 5. Ваша первая попытка заключается в следующем:
select *
from lock_test
where rownum <= 5
order by c1 asc;
Что дает результаты:
C1
--
6
7
8
9
10
Это явно неверно и является ошибкой, которую допускают почти все!Посмотрите на план объяснения для запроса:
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 65 | 4 (25)| 00:00:01 |
| 1 | SORT ORDER BY | | 5 | 65 | 4 (25)| 00:00:01 |
|* 2 | COUNT STOPKEY | | | | | |
| 3 | TABLE ACCESS FULL| LOCK_TEST | 10 | 130 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter(ROWNUM<=5)
Oracle выполняет план снизу вверх - обратите внимание, что фильтр для rownum выполняется перед сортировкой, Oracle принимает строки в порядкеон находит их (порядок, в который они были вставлены здесь {10, 9, 8, 7, 6}), останавливается после получения 5 строк, а затем сортирует этот набор.
Итак, чтобы получить правильные первые 5сначала нужно выполнить сортировку, а затем упорядочить, используя встроенное представление:
select * from
(
select *
from lock_test
order by c1 asc
)
where rownum <= 5;
C1
--
1
2
3
4
5
Теперь, чтобы наконец добраться до сути - можете ли вы поставить для обновления пропуск, заблокированный в нужном месте?
select * from
(
select *
from lock_test
order by c1 asc
)
where rownum <= 5
for update skip locked;
Это выдает ошибку:
ORA-02014: cannot select FOR UPDATE from view with DISTINCT, GROUP BY, etc
Попытка переместить для обновления в представление приводит к синтаксической ошибке:
select * from
(
select *
from lock_test
order by c1 asc
for update skip locked
)
where rownum <= 5;
Единственное, что будет работать, этоследующее, которое ДАЕТ НЕПРАВИЛЬНЫЙ РЕЗУЛЬТАТ :
select *
from lock_test
where rownum <= 5
order by c1 asc
for update skip locked;
Infact, если вы запустите этот запрос в сеансе 1, а затем снова запустите его во втором сеансе, сеанс два даст ноль строк, что на самом деле действительно неправильно!
Так что вы можете сделать?Откройте курсор и извлеките из него желаемое количество строк:
set serveroutput on
declare
v_row lock_test%rowtype;
cursor c_lock_test
is
select c1
from lock_test
order by c1
for update skip locked;
begin
open c_lock_test;
fetch c_lock_test into v_row;
dbms_output.put_line(v_row.c1);
close c_lock_test;
end;
/
Если вы запустите этот блок в сеансе 1, он выведет «1», поскольку заблокировал первую строку.Затем запустите его снова в сеансе 2, и он напечатает '2', пропустив строку 1 и получив следующую свободную.
Этот пример в PLSQL, но используя setFetchSize в Java, вы сможете получитьточно такое же поведение.