1

我们注意到 Postgresql 9.2 服务器在以下情况下很少发生死锁:

T1 开始批量操作:

UPDATE BB bb SET status = 'PROCESSING', chunk_id = 0 WHERE bb.status ='PENDING' 
AND bb.bulk_id = 1 AND bb.user_id IN (SELECT user_id FROM BB WHERE bulk_id = 1 
AND chunk_id IS NULL AND status ='PENDING' LIMIT 2000)

当 T1 在几百毫秒左右后提交时(BB 有数百万行),多个线程开始新的事务(每个线程一个事务)从 BB 读取项目,进行一些处理并以 50 左右的批次更新它们查询:

对于选择:

SELECT *, RANK() as rno OVER(ORDER BY user_id) FROM BB WHERE status = 'PROCESSING' AND bulk_id = 1 and rno = $1

和更新:

UPDATE BB set datetime=$1, status='DONE', message_id=$2 WHERE bulk_id=1 AND user_id=$3

(user_id、bulk_id 有一个 UNIQUE 约束)。

由于外部情况问题,另一个事务 T2 在 T1 提交后几乎立即与 T1 执行相同的查询(项目被标记为“正在处理”的初始批处理操作)。

UPDATE BB bb SET status = 'PROCESSING', chunk_id = 0 WHERE bb.status ='PENDING' 
AND bb.bulk_id = 1 AND bb.user_id IN (SELECT user_id FROM BB WHERE bulk_id = 1 
AND chunk_id IS NULL AND status ='PENDING' LIMIT 2000)

然而,尽管这些项目被标记为“正在处理”,但此查询会因工作线程中的一些更新(如我所说的那样分批完成)而死锁。据我了解,我们使用的 READ_COMMITTED 隔离级别(默认)不应该发生这种情况。我确信 T1 已经提交,因为工作线程在它完成之后执行。

编辑:我应该澄清的一件事是 T2 在 T1 之后但在它提交之前开始。但是,由于我们在同一行上使用 a 获取 write_exclusive 元组锁SELECT for UPDATE(不受上述任何查询的影响),它在运行批量更新查询之前等待 T1 提交。

4

1 回答 1

0

当 T1 在几百毫秒左右后提交时(BB 有数百万行),多个线程开始新的事务(每个线程一个事务)从 BB 读取项目,进行一些处理并以 50 左右的批次更新它们查询:

这让我觉得是一个并发问题。我认为你最好让一个事务读取行并将它们交给工作进程,然后在它们返回时批量更新它们。您的基本问题将是这些行有效地处理不确定状态,在事务期间保持行等。您必须单独处理回滚等,因此锁定是一个真正的问题。

现在,如果该解决方案不可行,我将有一个单独的锁定表。在这种情况下,每个线程单独启动,锁定锁定表,声明一堆行,将记录插入锁定表,然后提交。这样,每个线程都声明了记录。然后他们可以处理他们的记录集,更新它们等。您可能希望有一个定期清除过时锁的过程。

从本质上讲,您的问题是行从状态 A -> 处理 -> 状态 B 并且可能会回滚。由于其他线程无法知道正在处理哪些行以及由哪些线程处理,因此您无法安全地分配记录。一种选择是将模型更改为:

状态 A -> 声明状态 -> 处理 -> 状态 B。但是,您必须有某种方法来确保有效分配行并且线程知道已将哪些行分配给彼此。

于 2013-09-01T03:33:52.130 回答