使用“ SELECT FOR UPDATE”时出现死锁

时间:2019-01-22 23:07:14

标签: sql postgresql sqlalchemy

我注意到同时执行简单且相同的查询:

BEGIN; 
SELECT items.item_id AS items_item_id
FROM items
WHERE items.item_id = 1 FOR UPDATE;
COMMIT;

导致死锁,这使我感到惊讶,因为看起来这样的查询不应创建死锁。

此测试用例的表和数据定义为:

CREATE TABLE items (
    item_id serial PRIMARY KEY
);
insert into items  Values (1);

通过SQLAlchemy执行上述SQL语句的Python代码为:

item = DB.query(Item).filter( Item.item_id == 1).with_for_update().one()
time.sleep(0.001)
DB.commit()

SQLALchemy Item表定义:

class Item(Base):
    __tablename__ = 'items'
    item_id = Column(Integer, primary_key=True)

在这种死锁情况下,如果我运行:

SELECT blocked_locks.pid     AS blocked_pid,
 blocking_locks.pid     AS blocking_pid,
 blocked_activity.query    AS blocked_statement,
 blocking_activity.query   AS current_statement_in_blocking_process
FROM  pg_catalog.pg_locks         blocked_locks
JOIN pg_catalog.pg_stat_activity blocked_activity  ON blocked_activity.pid = blocked_locks.pid
JOIN pg_catalog.pg_locks         blocking_locks 
ON blocking_locks.locktype = blocked_locks.locktype
AND blocking_locks.DATABASE IS NOT DISTINCT FROM blocked_locks.DATABASE
AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
AND blocking_locks.pid != blocked_locks.pid

JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.GRANTED;

当同一条SELECT语句彼此依赖时,我将得到循环依赖。这是典型的输出(请注意2486924868相互依赖):

 blocked_pid | blocking_pid |           blocked_statement            | current_statement_in_blocking_process
-------------+--------------+----------------------------------------+----------------------------------------
       24867 |        24865 | SELECT items.item_id AS items_item_id +| SELECT items.item_id AS items_item_id +
             |              | FROM items                            +| FROM items                            +
             |              | WHERE items.item_id = 1               +| WHERE items.item_id = 1               +
             |              |  LIMIT 1 FOR UPDATE                    |  LIMIT 1 FOR UPDATE
       24868 |        24867 | SELECT items.item_id AS items_item_id +| SELECT items.item_id AS items_item_id +
             |              | FROM items                            +| FROM items                            +
             |              | WHERE items.item_id = 1               +| WHERE items.item_id = 1               +
             |              |  LIMIT 1 FOR UPDATE                    |  LIMIT 1 FOR UPDATE
       24870 |        24867 | SELECT items.item_id AS items_item_id +| SELECT items.item_id AS items_item_id +
             |              | FROM items                            +| FROM items                            +
             |              | WHERE items.item_id = 1               +| WHERE items.item_id = 1               +
             |              |  LIMIT 1 FOR UPDATE                    |  LIMIT 1 FOR UPDATE
       24871 |        24867 | SELECT items.item_id AS items_item_id +| SELECT items.item_id AS items_item_id +
             |              | FROM items                            +| FROM items                            +
             |              | WHERE items.item_id = 1               +| WHERE items.item_id = 1               +
             |              |  LIMIT 1 FOR UPDATE                    |  LIMIT 1 FOR UPDATE
       24869 |        24867 | SELECT items.item_id AS items_item_id +| SELECT items.item_id AS items_item_id +
             |              | FROM items                            +| FROM items                            +
             |              | WHERE items.item_id = 1               +| WHERE items.item_id = 1               +
             |              |  LIMIT 1 FOR UPDATE                    |  LIMIT 1 FOR UPDATE
       24870 |        24868 | SELECT items.item_id AS items_item_id +| SELECT items.item_id AS items_item_id +
             |              | FROM items                            +| FROM items                            +
             |              | WHERE items.item_id = 1               +| WHERE items.item_id = 1               +
             |              |  LIMIT 1 FOR UPDATE                    |  LIMIT 1 FOR UPDATE
       24871 |        24868 | SELECT items.item_id AS items_item_id +| SELECT items.item_id AS items_item_id +
             |              | FROM items                            +| FROM items                            +
             |              | WHERE items.item_id = 1               +| WHERE items.item_id = 1               +
             |              |  LIMIT 1 FOR UPDATE                    |  LIMIT 1 FOR UPDATE
       24869 |        24868 | SELECT items.item_id AS items_item_id +| SELECT items.item_id AS items_item_id +
             |              | FROM items                            +| FROM items                            +
             |              | WHERE items.item_id = 1               +| WHERE items.item_id = 1               +
             |              |  LIMIT 1 FOR UPDATE                    |  LIMIT 1 FOR UPDATE
       24868 |        24869 | SELECT items.item_id AS items_item_id +| SELECT items.item_id AS items_item_id +
             |              | FROM items                            +| FROM items                            +
             |              | WHERE items.item_id = 1               +| WHERE items.item_id = 1               +
             |              |  LIMIT 1 FOR UPDATE                    |  LIMIT 1 FOR UPDATE
       24870 |        24869 | SELECT items.item_id AS items_item_id +| SELECT items.item_id AS items_item_id +
             |              | FROM items                            +| FROM items                            +
             |              | WHERE items.item_id = 1               +| WHERE items.item_id = 1               +
             |              |  LIMIT 1 FOR UPDATE                    |  LIMIT 1 FOR UPDATE
       24871 |        24869 | SELECT items.item_id AS items_item_id +| SELECT items.item_id AS items_item_id +
             |              | FROM items                            +| FROM items                            +
             |              | WHERE items.item_id = 1               +| WHERE items.item_id = 1               +
             |              |  LIMIT 1 FOR UPDATE                    |  LIMIT 1 FOR UPDATE
       24868 |        24870 | SELECT items.item_id AS items_item_id +| SELECT items.item_id AS items_item_id +
             |              | FROM items                            +| FROM items                            +
             |              | WHERE items.item_id = 1               +| WHERE items.item_id = 1               +
             |              |  LIMIT 1 FOR UPDATE                    |  LIMIT 1 FOR UPDATE
       24871 |        24870 | SELECT items.item_id AS items_item_id +| SELECT items.item_id AS items_item_id +
             |              | FROM items                            +| FROM items                            +
             |              | WHERE items.item_id = 1               +| WHERE items.item_id = 1               +
             |              |  LIMIT 1 FOR UPDATE                    |  LIMIT 1 FOR UPDATE
       24869 |        24870 | SELECT items.item_id AS items_item_id +| SELECT items.item_id AS items_item_id +
             |              | FROM items                            +| FROM items                            +
             |              | WHERE items.item_id = 1               +| WHERE items.item_id = 1               +
             |              |  LIMIT 1 FOR UPDATE                    |  LIMIT 1 FOR UPDATE
       24868 |        24871 | SELECT items.item_id AS items_item_id +| SELECT items.item_id AS items_item_id +
             |              | FROM items                            +| FROM items                            +
             |              | WHERE items.item_id = 1               +| WHERE items.item_id = 1               +
             |              |  LIMIT 1 FOR UPDATE                    |  LIMIT 1 FOR UPDATE
       24870 |        24871 | SELECT items.item_id AS items_item_id +| SELECT items.item_id AS items_item_id +
             |              | FROM items                            +| FROM items                            +
             |              | WHERE items.item_id = 1               +| WHERE items.item_id = 1               +
             |              |  LIMIT 1 FOR UPDATE                    |  LIMIT 1 FOR UPDATE
       24869 |        24871 | SELECT items.item_id AS items_item_id +| SELECT items.item_id AS items_item_id +
             |              | FROM items                            +| FROM items                            +
             |              | WHERE items.item_id = 1               +| WHERE items.item_id = 1               +
             |              |  LIMIT 1 FOR UPDATE                    |  LIMIT 1 FOR UPDATE
(17 rows)

所以我的问题是:

  • 这样的行为是否正常,应该可以预料,或者我的代码中有错误(或者可能是SQLAlchemy / PostgreSQL)?

  • 如果这种行为是正常的和预期的:为什么会发生这种情况?特别是,我的印象是FOR UPDATE应该是原子的并且不能相互依赖(这个正确的假设吗?)

  • 是否可以在不使用NO WAIT中的SKIP LOCKEDFOR UPDATE的情况下以某种方式修改应用程序代码以避免此类死锁?

编辑:将错过的BEGIN; ... COMMIT;添加到代码中

1 个答案:

答案 0 :(得分:0)

如果我对您的理解正确,那么您正在执行两个并发查询,您在SELECT FOR UPDATE处对项目表中的第一项进行了查询?是的,那绝对会导致死锁。从文档中:

  

FOR UPDATE导致SELECT语句检索的行被锁定,就像更新一样。 [...]   也就是说,尝试对这些行进行UPDATE,DELETE或SELECT FOR UPDATE的其他事务将被阻止,直到当前事务结束为止。

请参见另一个问题here,我相信它涵盖的问题与您的问题类似,当然还要减去子查询的问题。如果执行两个查询以选择并显式锁定同一行,则第一个查询可能成功选择并锁定了该行,第二个查询被阻塞,直到解除锁定为止,然后当执行第一个查询时,第二个查询中的块抬起,然后选择并锁定该行。请记住,查询未序列化。