如何在并发访问中标记表中的某些nr行

时间:2014-12-12 15:10:05

标签: sql postgresql locking sql-update

我们的应用程序有一个名为cargo_items的表。它可以看作是稍后处理这些项的队列。最初有一个单一的工作,它接受了3000个条目并一个接一个地处理它们。后来,有人决定开始另外3个同一工作的实例。发生的事情非常明显,许多物品都被处理了两次。

我的工作是在同时运行许多实例的同时使这些进程正常工作。我现在要解决的问题是使用job_id标记数据库中的3000个条目,然后获取所有这些实体并将其与其他进程隔离开来。

我目前标记此行的方法如下:

UPDATE cargo_item item
SET job_id = 'SOME_UUID', job_ts = now()
FROM  ( 
   SELECT id
   FROM   cargo_item
   WHERE  state='NEW' AND job_id is null 
   LIMIT  3000
   FOR UPDATE
   ) sub
WHERE  item.id = sub.id;

这种方法基本上会锁定3000行以进行更新。我不确定这是否是一个好方法。

在另一个thread上,我读到了有关在此方案中使用建议锁定的信息。

您如何看待当前的方法并使用咨询锁?

更新

正如所建议的那样,我会像这样调整更新语句:

UPDATE cargo_item item
SET job_id = 'SOME_UUID', job_ts = now()
FROM  ( 
   SELECT id
   FROM   cargo_item
   WHERE  state='NEW' AND job_id is null 
   ORDER  BY id
   LIMIT  3000
   FOR UPDATE
   ) sub
WHERE  item.id = sub.id;

Thx Erwin和Tometzky提示。不过我想知道我试图解决这个问题的方式是不是很好?你有没有想到的不同方法?

3 个答案:

答案 0 :(得分:1)

这种方法会导致死锁。您可以通过在子查询中使用order by id来避免它们。

但它会阻止任何并发运行此查询,因为并发查询将始终首先尝试标记最低的空闲ID,并阻塞直到第一个客户端将提交。如果你的处理速度低于每秒一批,我不认为这是一个问题。

您不需要咨询锁。如果可以,请避免使用它们。

答案 1 :(得分:1)

在相关回答中,您指的是:

目标是一次锁定 一个 行。这可以在有或没有建议锁的情况下正常工作,因为没有机会发生死锁 - 只要您不尝试在同一事务中锁定更多行。

您的示例不同,您希望一次锁定 3000行 可能会出现死锁,除非所有并发写入操作以相同的一致顺序锁定行。 Per documentation:

  

防止死锁的最佳方法通常是避免死亡   确定使用数据库的所有应用程序都获得锁定   多个对象按照一致的顺序。

在子查询中使用ORDER BY实现它。

UPDATE cargo_item item
SET job_id = 'SOME_UUID', job_ts = now()
FROM  ( 
   SELECT id
   FROM   cargo_item
   WHERE  state='NEW' AND job_id is null 
   ORDER  BY id
   LIMIT  3000
   FOR UPDATE
   ) sub
WHERE  item.id = sub.id;

这是安全可靠的,只要所有事务以相同的顺序获取锁,并且不会期望订购列的并发更新。 (Read the yellow "CAUTION" box at the end of this chapter in the manual。)因此,在您的情况下这应该是安全的,因为您不会更新id列。

有效地,一次只有一个客户端可以通过这种方式操作行。并发事务将尝试锁定相同(已锁定)的行并等待第一个事务完成。

如果您有许多或很长时间运行的并发事务,那么

咨询锁非常有用(您似乎没有这样做)。只有少数几个,只需使用上面的查询并让并发事务等待轮到它们会更便宜。

全部在一个UPDATE

在您的设置中,似乎并发访问本身并不是问题。并发性是您当前解决方案创建的问题。

相反,在单个UPDATE 中执行全部操作。将批量n个数字(示例中为3000)分配给每个UUID并一次更新所有数据。应该是最快的。

UPDATE cargo_item c
SET    job_id = u.uuid_col
     , job_ts = now()
FROM  (
   SELECT row_number() OVER () AS rn, uuid_col
   FROM   uuid_tbl WHERE  <some_criteria>  -- or see below
   ) u
JOIN (
   SELECT (row_number() OVER () / 3000) + 1 AS rn, item.id 
   FROM   cargo_item
   WHERE  state = 'NEW' AND job_id IS NULL
   FOR    UPDATE   -- just to be sure
   ) c2 USING (rn)
WHERE  c2.item_id = c.item_id;

重点

  • 整数除法截断。前3000行获得1,接下来3000行获得2。等

  • 我随意选择行,您可以在ORDER BY的窗口中应用row_number()来指定某些行。

  • 如果您没有要分派的UUID表(uuid_tbl),请使用VALUES表达式来提供它们。 Example.

  • 您获得了3000行的批次。如果您没有找到要分配的3000的倍数,则最后一批将缺少3000。

答案 2 :(得分:0)

您需要的是咨询锁。

   SELECT id
   FROM   cargo_item
   WHERE  pg_try_advisory_lock(id)
   LIMIT  3000
   FOR UPDATE
如果在where中使用了相同的pg_try_advisory_lock(id)函数,

将对行放置一个建议锁定,其他进程将看不到行。请记住使用pg_advisory_unlock

解锁行