5

我有一个使用 Devart 和实体框架访问的 Oracle 数据库。

有一个名为IMPORTJOBScolumn的表STATUS

我也有多个进程同时运行。IMPORTJOBS他们每个人都读取具有状态的第一行'REGISTERED',将其置于状态'EXECUTING',如果完成则将其置于状态'EXECUTED'

现在因为这些进程是并行运行的,我相信可能会发生以下情况:

  • 进程 A 读取具有状态的第 10 行REGISTERED
  • 进程 B 还读取第 10 行,它仍然有状态REGISTERED
  • 进程 A 将第 10 行更新为 status EXECUTING

进程 B 不应该能够读取第 10 行,因为进程 A 已经读取了它并且将要更新其状态。

我应该如何解决这个问题?将读取和更新放入事务中?还是我应该使用一些版本控制方法或其他方法?

谢谢!

编辑:感谢接受的答案,我得到了它的工作并在此处记录:http: //ludwigstuyck.wordpress.com/2013/02/28/concurrent-reading-and-writing-in-an-oracle-database

4

3 回答 3

2

SELECT ... FOR UPDATE每个进程在读取行时都可以发出一个来锁定行。在这种情况下,进程 A 将读取并锁定该行,进程 B 将尝试读取该行并阻塞,直到进程 A 通过提交(或回滚)其事务来释放锁定。然后,Oracle 将确定该行是否仍符合 B 的标准,并且在您的示例中,不会将该行返回给 B。这有效,但这意味着您的多线程进程现在可能是有效的单线程,具体取决于您的事务控制方式需要工作。

提高可扩展性的可能方法

  • 消费者解决此问题的一种相对常见的方法是拥有一个协调线程,该线程从表中读取数据,将工作分配给不同的线程,并适当地更新表(包括如果线程知道如何重新分配工作)被分配的它已经死了)。
  • 如果您使用的是 Oracle 11.1 或更高版本,您可以在您的上使用该SKIP LOCKED子句FOR UPDATE,以便每个会话取回满足其条件且未锁定的第一行(该子句存在于早期版本中但未记录,因此它可能无法正常工作)。
  • ImportJobs您可以使用具有多个消费者的队列,而不是使用表。这将允许 Oracle 将消息分发到每个进程,而无需构建任何额外的锁定(Oracle 队列在幕后完成这一切)。
于 2013-02-27T16:01:37.833 回答
2

您应该使用数据库的内置锁定机制。不要重新发明轮子,尤其是因为 RDBMS旨在处理并发性和一致性。

在 Oracle 11g 中,我建议您使用该SKIP LOCKED功能。例如,每个进程都可以调用这样的函数(假设id是数字):

CREATE OR REPLACE TYPE tab_number IS TABLE OF NUMBER;

CREATE OR REPLACE FUNCTION reserve_jobs RETURN tab_number IS
   CURSOR c IS 
      SELECT id FROM IMPORTJOBS WHERE STATUS = 'REGISTERED'
      FOR UPDATE SKIP LOCKED;
   l_result tab_number := tab_number();
   l_id number;
BEGIN
   OPEN c;
   FOR i IN 1..10 LOOP
      FETCH c INTO l_id;
      EXIT WHEN c%NOTFOUND;
      l_result.extend;
      l_result(l_result.size) := l_id;
   END LOOP;
   CLOSE c;
   RETURN l_result;
END;

这将返回未锁定的 10 行(如果可能)。这些行将被锁定,会话不会相互阻塞。

在 10g 及之前的版本中,由于 Oracle 返回一致的结果,FOR UPDATE请明智地使用,您应该不会遇到您描述的问题。例如考虑以下内容SELECT

SELECT *
  FROM IMPORTJOBS 
 WHERE STATUS = 'REGISTERED'
   AND rownum <= 10
FOR UPDATE;

如果所有进程都用这个 SELECT 保留它们的行,会发生什么?这将如何影响您的方案:

  1. 会话 A 获得 10 行未处理。
  2. 会话 B 将获得相同的 10 行,被阻塞并等待会话 A。
  3. 会话 A 更新选定行的状态并提交其事务。
  4. Oracle 现在将(自动)从头开始重新运行会话 B 的选择,因为数据已被修改并且我们已指定FOR UPDATE(此子句强制 Oracle 获取块的最后一个版本)。
    这意味着会话 B 将获得 10 个新行

所以在这种情况下,你没有一致性问题。此外,假设请求一行并更改其状态的事务很快,并发影响将很小。

于 2013-02-27T16:10:38.870 回答
1

使用版本控制和乐观并发

IMPORTJOBS表应该有一个时间戳列,您在模型中将其标记为ConcurrencyMode= Fixed。现在,当 EF 尝试进行更新时,时间戳列将合并到更新语句中:WHERE timestamp = xxxxx.

对于B,时间戳同时发生了变化,因此引发了并发异常,在这种情况下,您可以通过跳过更新来处理。

我来自 SQL 服务器背景,我不知道时间戳(或行版本)的 Oracle 等效项,但我的想法是它是一个在对记录进行更新时自动更新的字段。

于 2013-02-27T16:02:19.370 回答