原子选择和更新

时间:2011-04-13 16:51:48

标签: php oracle oracle10g

如果我的数据如下:

ID STATUS     DATE_ADDED
== ========== ==========
 1 Processing 2011-04-01
 2 New        2011-04-02 
 3 New        2011-04-03
 4 Processing 2011-04-03
 5 Done       2011-04-06
 6 New        2011-04-06
 7 New        2011-04-14
 8 Done       2011-04-14
 ...

...建议选择状态为“新建”的10条最旧记录,并将其状态设置为“正在处理”,同时确保任何其他并发进程不能对相同记录执行相同操作?

这是一个在Windows Server 2003下运行在PHP / 5.2.6上的Web应用程序,它通过ODBC(Oracle的驱动程序,而不是Microsoft的)连接到远程Oracle 10g服务器。

5 个答案:

答案 0 :(得分:3)

这在Oracle 10g中很难做到。在11g中,SELECT FOR UPDATE ... SKIP LOCKED语法使其变得简单。

一个简单的UPDATE语句将序列化。和SELECT FOR UPDATE一样。当然,两个竞争过程不会得到相同的行;问题是他们最多会序列化,最糟糕的是,他们可以陷入僵局。

推荐的方法是使用Oracle Advanced Queuing(或您选择的排队实现)将要排队的ID排入队列,并允许排队实现来管理值的争用。

-

SQL将起作用,但是如果第二个用户针对相同的偏移量运行它而某人将该范围锁定,则会因ORA-00054而失败。这可以通过将select包装在循环中,捕获ORA-00054错误并使用它来增加偏移量来缓解。

select * from my_table
 where rowid in 
       (select row_id 
          from (select rowid as row_id, rownum as rn 
                  from mytable where some_condition 
                 order by deterministic_sort_order)
         where rn between :low_rn and :hi_rn
       )
 for update nowait;

排序表达式需要是确定性的(简单地说,包括主键作为排序表达式的结尾)以防止冲突。

答案 1 :(得分:2)

使用交易来做到这一点。使用隔离级别" serializable"对于事务,将阻止任何其他进程在您的事务处理它们时访问/修改行。

  

如果可序列化事务尝试执行修改已由未提交事务修改的任何表的SQL数据操作语句,则该语句将失败。

您可能想要使用:

set transaction isolation level serializable;

答案 2 :(得分:1)

您可以创建一个新表并在其中放入一行。然后,您的程序可以使用更新锁定行,或者在执行原始表之前选择更新。如果所有程序使用相同的过程来标记表“处理”,这将起作用。

create table Lock_Table (
  app_catagory  varchar2(20) primary key,
  usage_ts      timestamp(6)
 );

插入一行:insert into Lock_Table (app_category) values 'APP1'并提交。这是一次插入。

然后锁定其他会话:update Lock_Table set usage_ts = current_timestamp where app_category = 'CAT1'

您不需要usage_ts列,可以使用select for update

只要您在“选择最早的10个”查询之前执行上述更新,您就可以保证您的结果。我建议将所有内容放在一个程序中(或程序包中的一个程序),以便应用程序员轻松“做正确的事情。”

答案 3 :(得分:1)

解决这个问题的一个重要方法是锁定表,以便其他会话无法更新它:

lock your_table in exclusive mode

不幸的是,在释放锁之前,其他会话将无法插入新行,因此这可以真正降低应用程序的一致性。

答案 4 :(得分:-1)

仅供注意

此外,乐观锁定策略可用于解决此问题