Postgres 是否自动锁定查询中的所有行,甚至通过 JOIN 跨不同的表?

Does Postgres lock all rows in a query atomically, even across different tables via JOIN?

我的代码出现死锁错误。问题是这个死锁错误发生在事务的第一个查询上。这个查询连接了两个表,TableATableB 并且应该用 id==table_a_id 锁定 TableA 中的一行,以及 TableB 上的所有具有外部table_a_id.

查询如下所示(我使用的是 SQLAlchemy,此输出来自打印它的等效查询,下面也有它的代码):

SELECT TableB.id AS TableB_id
FROM TableA JOIN TableB ON TableA.id = TableB.table_a_id 
WHERE TableB.id = %(id_1)s FOR UPDATE

查询在 SQLAlchemy 语法中如下所示:

query = (
    database.query(TableB.id)
    .select_from(TableA)
    .filter_by(id=table_a_id)
    .join((TableB, TableA.id == TableB.table_a_id))
    .with_for_update()
)

return query.all()

我的问题是,这个查询会自动锁定两个表中的所有行吗?如果是这样,为什么我会在这个查询上出现死锁,因为它是事务的第一个查询?

查询将在行被选中时一个接一个地锁定它们。确切的顺序将取决于执行计划。也许您可以添加 FOR UPDATE OF table_name 以仅在需要锁定的 table 中锁定行。

我还有两个想法:

  • 重写查询,使其按特定顺序锁定行:

    WITH b AS MATERIALIZED (
       SELECT id, table_a_id
       FROM tableb
       WHERE id = 42
       FOR NO KEY UPDATE
    )
    SELECT b.id
    FROM tablea
    WHERE EXISTS (SELECT 1 FROM b
                  WHERE tablea.id = b.table_a_id)
    ORDER BY tablea.id
    FOR NO KEY UPDATE;
    

    性能可能没有那么好,但如果大家都这样选择,就不会出现死锁。

  • 锁定 tables:

    LOCK TABLE tablea, tableb IN EXCLUSIVE MODE;
    

    该锁将防止并发行锁和数据修改,因此不会发生死锁。

    仅作为 last-ditch 的努力来执行此操作,并且不要经常执行此操作。如果您经常像这样使用高 table 锁,您就会使 autovacuum 远离 运行 并危及数据库的健康。