防止双重预订

时间:2011-10-21 05:02:38

标签: sql database oracle locking

让我们说如果我有以下内容:

create table jobs
(
  staff_id number,
  job_name varchar2(1000) not null,
  start_date date not null,
  end_date date not null
);

这只是一张列出员工各种工作的表格。

现在员工一次只能完成一项工作,所以我使用以下PL / SQL语句插入作业。如果找到一个冲突的工作,没有添加任何工作(我应该在这里报告错误,但是对于这个简化的例子,我忽略了这一点):

create or replace procedure add_job
(
  p_staff_id number, 
  p_job_name varchar2, 
  p_start_date date, 
  p_end_date date
) 
as
begin
  insert into jobs 
  (
    select p_staff_id, p_job_name, p_start_date, p_end_date from dual 
    where not exists
    (
      select 1 from jobs 
      where staff_id = p_staff_id
        and (end_date > p_start_date)
        and (start_date < p_end_date)
    )
  );
end;

问题是,如果我在两个不同的会话中添加两个不同的作业,然后提交,我可以加倍预订。即以下内容:

-- Session 1:
add_job(1, 'Help Alice', to_date('2011/08/21 11:00:00', 'YYYY/MM/DD HH24/MI/SS'), to_date('2011/08/21 13:00:00', 'YYYY/MM/DD HH24/MI/SS'));

-- Session 2:
add_job(1, 'Help Bob',   to_date('2011/08/21 12:00:00', 'YYYY/MM/DD HH24/MI/SS'), to_date('2011/08/21 14:00:00', 'YYYY/MM/DD HH24/MI/SS'));

-- Session 1:
commit;

-- Session 2:
commit;

以上,staff_id 1将在12:00至13:00之间重复预订。

似乎添加到我的程序的开头:

lock table jobs in exclusive mode;

做了这个伎俩,我觉得这太宽了。有没有办法强迫oracle做一些更细粒度的事情。如果可能的话,我宁愿不与dbms_lock混在一起。这个page暗示select ... for update可以解决问题,但它没有提供详细信息。

有没有办法在没有完整的桌锁或dbms_lock锁定的情况下停止双重预订? (如果有所不同,我正在使用Oracle 10g。)

5 个答案:

答案 0 :(得分:2)

你显然需要锁定以防止双重预订。由于您需要锁定特定的工作人员,我的建议是在插入语句之前在工作人员中锁定一行:

select id from staff where id = p_staff_id for update;

这样,锁只影响一个工作人员(假设你有行级别锁)。

答案 1 :(得分:0)

您可以使用数据库trigger来强制执行员工职位的单一性。

如果找到单个工作人员的重叠工作,那么它将不允许插入,因此您无法获得重叠的工作。

在您的问题指定的情况下,无论哪个作业被提交,第二个都会因触发器检查插入而失败。

BTW,锁定ENTIRE表不是一个好的解决方案。此外,尝试使用SELECT FOR UPDATE也不会阻止两次插入。

答案 2 :(得分:0)

您可以考虑使用具有检查约束的物化视图的Onw方式。请参阅this old blog post of mine哪里有例子“1)规则:员工不能重叠项目分配”与您的非常相似。

警告:维护实体化视图会影响性能,因此如果没有正确的性能测试,请不要实现。

答案 3 :(得分:0)

只要工作分配在相当大的时间段内,1小时或30分钟(1/24/60 * 30)

你可以在staff_id&amp;上创建一个带有primay键的新表scheduled_time_slot。时间 (或日期和插槽编号)

如果为每个使用的时段插入一行: (以下示例在开始和结束时间之间获取每30分钟的时间段并存储它)

INSERT INTO booked_time_slot
  SELECT p_staff_id,TO_DATE('2011/08/21 11:00:00', 'YYYY/MM/DD HH24/MI/SS') + 
      ((ROWNUM-1)*(1/24/60*30))  
  FROM DUAL
  CONNECT BY TO_DATE('2011/08/21 11:00:00', 'YYYY/MM/DD HH24/MI/SS') + 
      ((ROWNUM-1)*(1/24/60*30)) <  
          TO_DATE('2011/08/21 12:00:00', 'YYYY/MM/DD HH24/MI/SS')

主键将停止任何重复。

答案 4 :(得分:-2)

如果staff_id应该是唯一的,您应该按顺序生成它。

当你进行插入时,你就是这样做的:

insert into jobs 
(
    select seq_staff_id.nextval, p_job_name, p_start_date, p_end_date from dual 
)