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

21

我们有一张单独的表格,没有引用任何其他表格。

┬────────────┬─────────────┬───────────────┬───────────────╮
│id_A(bigint)│id_B(bigint) │val_1(varchar) │val_2(varchar) │
╪════════════╪═════════════╪═══════════════╪═══════════════╡
id_Aid_Bvalue
1aold_value_1a
2bold_value_2b
3cold_value_3c
.........
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和表级锁定进行行级锁定),但这些都似乎无法解决死锁问题。(请注意,由于性能影响,我们无法以任何方式在此表上使用访问排他锁定)

除了上述方法,是否有其他方法可以尝试解决这些死锁情况?参考手册中提到:

通常避免死锁的最佳方法是确保所有使用数据库的应用程序都以一致的顺序获取多个对象的锁定。

但在上述情况下,我们如何实现这一点呢?是否有一种可靠的方法来按特定顺序执行批量更新插入操作?

1个回答

34
使用显式的行级锁定在所有竞争查询中的有序子查询中。
(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从不被更新,即使像手册中“注意”框中详细说明的那样的罕见边角情况也是不可能出现的。

在不更新关键列的情况下,您可以使用较弱的锁定模式 FOR NO KEY UPDATE。需要Postgres 9.3或更高版本。


另一个(缓慢而稳定的)选项是使用可序列化隔离级别来处理竞争事务。您需要准备好处理序列化失败的情况,此时您需要重试命令。

感谢您的回复 @Erwin。我已经尝试了您的策略,但仍然遇到死锁问题。我看不出方法存在明显的错误。如果您有更多建议可以尝试,将不胜感激。 - sanjayav
@sanjayav:你分析了日志,看看实际涉及哪些关系和查询了吗?也许你有额外的查询语句忘记适应了?是否有触发器涉及到无序访问的相关表格? - Erwin Brandstetter
2
我一直在为团队面临的死锁问题苦恼,这个解决方案真是太好了,感谢@ErwinBrandstetter! - pgoggijr
在我看来,关键因素是在更新之前按照唯一ID进行排序。假设线程A更新行1、2、3,线程B更新行2、3、4. 线程A可能会锁定第1行和第2行。在这种情况下,线程B将等待A。线程A可能仅锁定第1行。在这种情况下,A将等待线程B。 - Mikhail Ionkin
是的,一致的排序顺序是最便宜的防止死锁的方法。 - Erwin Brandstetter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接