我们的应用程序有一个名为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提示。不过我想知道我试图解决这个问题的方式是不是很好?你有没有想到的不同方法?
答案 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
中执行全部操作。将批量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
解锁行