执行批量更新和删除操作时避免PostgreSQL死锁

时间:2014-11-19 01:06:00

标签: database postgresql concurrency deadlock database-deadlocks

我们有一个表没有对任何其他表的引用。

┬────────────┬─────────────┬───────────────┬───────────────╮
│id_A(bigint)│id_B(bigint) │val_1(varchar) │val_2(varchar) │
╪════════════╪═════════════╪═══════════════╪═══════════════╡

表的主键是id_A和id_B的组合。

这个表的读写是高度并发的,表有数百万行。 我们有几个存储过程可以进行批量更新和删除。这些存储过程主要由触发器和应用程序代码同时调用。

这些操作通常如下所示,它可以匹配数千条记录来更新/删除:

DELETE FROM table_name 
WHERE id_A = ANY(array_of_id_A)
AND id_B = ANY(array_of_id_B)

UPDATE table_name
SET val_1 = 'some value', val_2 = 'some value'
WHERE id_A = ANY(array_of_id_A)
AND id_B = ANY(array_of_id_B)

我们遇到了死锁,我们尝试使用锁(使用SELECT FOR UPDATE和表级锁定的行级别)执行操作似乎无法解决这些死锁问题。 (请注意,由于性能影响,我们不能以任何方式在此表上使用访问独占锁定)

还有其他方法可以尝试解决这些死锁情况吗? The reference manual says

  

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

但是我们怎样才能在上述场景中实现这一目标。是否有保证以特定顺序执行批量更新插入操作的方法?

1 个答案:

答案 0 :(得分:17)

所有竞争查询中的有序子查询中使用显式行级锁定。 (简单SELECT不参与竞争。)

DELETE

DELETE FROM table_name t
USING (
   SELECT id_A, id_B
   FROM   table_name 
   WHERE  id_A = ANY(array_of_id_A)
   AND    id_B = ANY(array_of_id_B)
   ORDER  BY id_A, id_B
   FOR    UPDATE
   ) del
WHERE  t.id_A = del.id_A
AND    t.id_B = del.id_B;

更新

UPDATE table_name t
SET    val_1 = 'some value'
     , val_2 = 'some value'
FROM (
   SELECT id_A, id_B
   FROM   table_name 
   WHERE  id_A = ANY(array_of_id_A)
   AND    id_B = ANY(array_of_id_B)
   ORDER  BY id_A, id_B
   FOR    NO KEY UPDATE  -- Postgres 9.3+
-- FOR    UPDATE         -- for older versions or updates on key columns
   ) upd
WHERE  t.id_A = upd.id_A
AND    t.id_B = upd.id_B;

这样,按照手册中的建议,行按照一致的顺序锁定。

假设id_Aid_B永远不会更新,即使是detailed in the "Caution" box in the manual等罕见的角落并发症,也是不可能的。

虽然不更新键列,但您可以使用较弱的lock mode FOR NO KEY UPDATE。需要Postgres 9.3或更高版本。


另一个(且肯定)选项是使用Serializable Isolation Level进行竞争交易。您必须为序列化失败做好准备,在这种情况下您必须重试该命令。