我想设计一个类似于stackoverflow 审核 功能的系统。也就是说:
有n
个任务,应该分配给用户(用户数量未知)。同时,一个任务应该分配给最多一个用户,不应该为不同的用户分配相同的任务。
例如,n = 8,如果一个用户进入系统默认值,则为他分配3个任务。
假设我使用Database来存储任务信息。
我的解决方案是,当将任务1,2,3分配给Tom时,从DB中删除3条记录并将其存储到内存中。然后其他人将无法获得3条记录。当Tom离开系统时,将已完成的任务和未完成的任务再次插入DB(任务状态"已完成"或"未完成")。
虽然缺点是存储记录到内存不是100%安全,但如果系统崩溃可能导致数据丢失问题。
有人知道stackoverflow如何设计 审核 功能吗?或分享其他解决方案?我想知道SELECT ... FOR UPDATE
在这个用例中是否有用。
答案 0 :(得分:1)
您需要实现的是FIFO堆栈或简单队列。在Oracle中,最好的事情(除非你想用AQ实现一个实际的队列)就是SELECT ... FOR UPDATE
和SKIP LOCKED
子句。 SKIP LOCKED
允许我们轻松操作多个用户的堆栈。
这是一个简单的界面:
create or replace package task_mgmt is
function get_next_task return tasks.id%type;
procedure complete_task (p_id in tasks.id%type);
procedure release_task (p_id in tasks.id%type);
end task_mgmt;
/
这是一个简单的实现:
create or replace package body task_mgmt is
function get_next_task return tasks.id%type
is
return_value tasks.id%type;
cursor c_tsk is
select id
from tasks
where status = 'open'
order by date_created, id
for update skip locked;
begin
open c_tsk;
fetch c_tsk into return_value;
update tasks
set status = 'progress'
, assigned = user
where current of c_tsk;
close c_tsk;
return return_value;
end get_next_task;
procedure complete_task (p_id in tasks.id%type)
is
begin
update tasks
set status = 'complete'
, date_completed = sysdate
where id = p_id;
commit;
end complete_task;
procedure release_task (p_id in tasks.id%type)
is
begin
rollback;
end ;
end task_mgmt;
/
当用户弹出堆栈时更新状态会创建锁定。由于SKIP LOCKED
子句,下一个用户将看不到该任务。这比删除和重新插入记录更清晰。
以下是一些数据:
create table tasks (
id number not null
, descr varchar2(30) not null
, date_created date default sysdate not null
, status varchar2(10) default 'open' not null
, assigned varchar2(30)
, date_completed date
, constraint task_pk primary key (id)
)
/
insert into tasks (id, descr, date_created) values (1000, 'Do something', date '2015-05-28')
/
insert into tasks (id, descr, date_created) values (1010, 'Look busy', date '2015-05-28')
/
insert into tasks (id, descr, date_created) values (1020, 'Get coffee', date '2015-06-12')
/
让我们流行吧!这是第一节:
SQL> var tsk1 number;
SQL> exec :tsk1 := task_mgmt.get_next_task ;
PL/SQL procedure successfully completed.
SQL> print :tsk1
TSK1
----------
1000
SQL>
同时在第二场:
SQL> var tsk2 number;
SQL> exec :tsk2 := task_mgmt.get_next_task ;
PL/SQL procedure successfully completed.
SQL> print :tsk2
TSK2
----------
1010
SQL>
返回第一节:
SQL> exec task_mgmt.complete_task (:tsk1);
PL/SQL procedure successfully completed.
SQL> exec :tsk1 := task_mgmt.get_next_task ;
PL/SQL procedure successfully completed.
SQL> print :tsk1
TSK
----------
1020
SQL>
这种方法的主要缺点是它要求用户在处理任务时维护有状态会话。事实并非如此,您需要一个API,其中get_next_task()
是一个离散的事务,而忘记了锁定。
顺便说一下,让用户抓住任务而不是通过登录触发器分配任务(或者通过“Tom进入系统并获取任务1,2,3”时的任何想法)可能更好。拉取任务是SO Review队列的工作方式。
另外,一次只分配一个任务。这样你就可以有效地分配工作。你想避免汤姆在他的盘子上有三个任务的情况,其中一个他不会完成,鲍勃无所事事。也就是说,除非你是鲍勃。