我们注意到在以下情况下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相同的查询(初始批处理操作,其中项目被标记为'PROCESSING')。
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之后但在提交之前开始。但是由于我们在同一行上使用SELECT for UPDATE
获取的write_exclusive元组锁(不受上述任何查询的影响),它会在运行批量更新查询之前等待T1提交。
答案 0 :(得分:0)
当T1提交几百毫秒左右(BB有数百万行)后,多个线程开始新事务(每个线程一个事务)从BB读取项目,进行一些处理并分批更新它们50或所以查询:
这引起了我的并发问题。我认为最好让一个事务读取行并将它们交给工作进程,然后在它们返回时分批更新它们。您的根本问题是这些行有效地处理不确定状态,在事务期间保持行等。您必须单独处理回滚等,因此锁定是一个真正的问题。
现在,如果无法解决这个问题,我会有一个单独的锁定表。在这种情况下,每个线程单独旋转,锁定锁定表,声明一堆行,将记录插入锁定表,并提交。通过这种方式,每个线程都声明了记录。然后他们可以处理他们的记录集,更新它们等等。您可能希望有一个定期清除陈旧锁定的进程。
本质上你的问题是行从状态A - >;处理 - >状态B,可能会回滚。由于其他线程无法知道哪些行正在处理以及哪些线程处理,因此无法安全地分配记录。一种选择是将模型更改为:
状态A - >;声称的状态 - >处理 - >状态B.但是你必须有一些方法来确保有效地分配行,并且线程知道哪些行已经分配给彼此。