加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

SELECT FOR UPDATE的奇怪死锁PostgreSQL死锁问题

发布时间:2020-12-13 16:05:08 所属栏目:百科 来源:网络整理
导读:我正在构建一个基于PostgreSQL的锁定系统,我有两种方法,即获取和发布. 对于获取,它的工作原理如下 BEGINwhile True: SELECT id FROM my_locks WHERE locked = false AND id = 'NAME' FOR UPDATE if no rows return: continue UPDATE my_locks SET locked = t
我正在构建一个基于PostgreSQL的锁定系统,我有两种方法,即获取和发布.

对于获取,它的工作原理如下

BEGIN
while True:
    SELECT id FROM my_locks WHERE locked = false AND id = '<NAME>' FOR UPDATE
    if no rows return:
        continue
    UPDATE my_locks SET locked = true WHERE id = '<NAME>'
    COMMIT
    break

并发布

BEGIN
UPDATE my_locks SET locked = false WHERE id = '<NAME>'
COMMIT

这看起来非常简单,但它不起作用.奇怪的是,我想

SELECT id FROM my_locks WHERE locked = false AND id = '<NAME>' FOR UPDATE

仅当目标行的锁定为false时才应获取目标行上的锁定.但实际上,它不是那样的.不知何故,即使没有locked = false行存在,它仍然会获得锁定.结果,我遇到了死锁问题.看起来像这样

Select for update dead lock issue

Release正在等待SELECT FOR UPDATE,并且SELECT FOR UPDATE正在进行无限循环,同时它无缘无故地持有锁.

为了重现这个问题,我在这里写了一个简单的测试

https://gist.github.com/victorlin/d9119dd9dfdd5ac3836b

您可以使用psycopg2和pytest运行它,记得更改数据库设置,然后运行

pip install pytest psycopg2
py.test -sv test_lock.py

解决方法

测试用例如下:

> Thread-1运行SELECT并获取记录锁.
> Thread-2运行SELECT并进入锁的等待队列.
> Thread-1运行UPDATE / COMMIT并释放锁.
> Thread-2获取锁定.检测到记录自SELECT以来已更改,它会根据WHERE条件重新检查数据.检查失败,并从结果集中过滤掉行,但仍保持锁定.

FOR UPDATE documentation中提到了此行为:

…rows that satisfied the query conditions as of the query snapshot will be locked,although they will not be returned if they were updated after the snapshot and no longer satisfy the query conditions.

这可以有一些unpleasant consequences,所以一切都考虑了多余的锁并没有那么糟糕.

可能最简单的解决方法是通过在每次迭代获取后提交来限制锁定持续时间.还有其他各种方法可以防止它持有这种锁(例如SELECT … NOWAIT,在REPGATABLE READ或SERIALIZABLE隔离级别运行,Postgres 9.5中的SELECT ... SKIP LOCKED).

我认为使用这种重试循环方法的最干净的实现是完全跳过SELECT,只运行UPDATE … WHERE locked = false,每次提交.通过在调用cur.execute()之后检查cur.rowcount,可以判断是否获得了锁.如果您需要从锁记录中提取其他信息,则可以使用UPDATE … RETURNING语句.

但我不得不同意@Kevin,并说你可能会更好地利用Postgres的内置锁定支持,而不是试图重新发明它.它会为你解决很多问题,例如:

>自动检测到死锁
>等待进程处于休眠状态,而不必轮询服务器
>锁定请求排队,防止饥饿
>锁(通常)不会比失败的进程寿命长

最简单的方法可能是将SELECT作为SELECT FROM my_locks FOR UPDATE实现,只需将其作为COMMIT释放,然后让进程争用行锁.如果您需要更多灵活性(例如阻止/非阻塞调用,事务/会话/自定义范围),advisory locks应该证明是有用的.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读