我目前正在开发一个需要将数据库用作队列的项目。出于性能原因,我需要能够启动多个服务,以便可以横向扩展。最初我在mysql中使用了一个表,使用类似下面的示例的查询使这个过程变得非常容易。
SELECT * FROM work_orders WHERE state = 'available' LIMIT 1 FOR UPDATE
然后在我的Java代码中,我执行状态列的更新,将其从可用更改为排队。这可以防止任何其他待处理的更新选择对其进行操作,因为状态从可用状态更改为排队。
但是范围已更改,现在需要在Oracle 11.2中作为存储过程运行。我发现在Oracle中实现上述内容有点复杂。我的解决方案基于Stack Exchange问题的示例。
How to solve ORA-02014: cannot select FOR UPDATE from view with DISTINCT, GROUP BY
运行几个简单的java测试服务,显示每个测试服务都按预期获得一个独特的工作项。我对我的解决方案的关注是,我对选择更新锁定行没有完全的信心。正如您在下面的代码中看到的那样,内部最多选择没有锁定,因为锁定发生在初始选择时。我的问题是如何在幕后处理这个问题?
CREATE OR REPLACE PROCEDURE getOneAtomicWorkOrder(
calling_service_name IN LOCKING_TEST.process%TYPE,
out_job_id OUT LOCKING_TEST.JOB_ID%TYPE,
out_application OUT LOCKING_TEST.APPLICATION%TYPE,
out_old_state OUT LOCKING_TEST.STATE%TYPE)
IS
BEGIN
SELECT
job_id, application, state
INTO out_job_id, out_application, out_old_state
FROM
LOCKING_TEST WHERE job_id IN
(
SELECT job_id FROM ( SELECT * FROM LOCKING_TEST WHERE state = 'available' ) WHERE ROWNUM <=1
)
FOR UPDATE;
UPDATE LOCKING_TEST SET state = 'queued', process = calling_service_name WHERE job_id=out_job_id;
COMMIT;
EXCEPTION
WHEN no_data_found
THEN
out_job_id := 0;
out_application := NULL;
out_old_state := NULL;
ROLLBACK;
END;
编辑:这个基于Shannon输入的新sql脚本更加清晰。
CREATE OR REPLACE PROCEDURE getOneAtomicWorkOrder(
calling_service_name IN LOCKING_TEST.process%TYPE,
out_job_id OUT LOCKING_TEST.JOB_ID%TYPE,
out_application OUT LOCKING_TEST.APPLICATION%TYPE,
out_old_state OUT LOCKING_TEST.STATE%TYPE)
IS
BEGIN
SELECT job_id,
application,
state
INTO out_job_id,
out_application,
out_old_state
FROM LOCKING_TEST
WHERE state = 'available' and rownum = 1 ORDER BY job_id for update;
UPDATE LOCKING_TEST SET state = 'queued', process = calling_service_name WHERE job_id=out_job_id;
COMMIT;
EXCEPTION
WHEN no_data_found
THEN
out_job_id := 0;
out_application := NULL;
out_old_state := NULL;
ROLLBACK;
END;