SELECT FOR UPDATE的奇怪死锁PostgreSQL死锁问题
我正在构建一个基于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行存在,它仍然会获得锁定.结果,我遇到了死锁问题.看起来像这样 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并获取记录锁. 在
这可以有一些unpleasant consequences,所以一切都考虑了多余的锁并没有那么糟糕. 可能最简单的解决方法是通过在每次迭代获取后提交来限制锁定持续时间.还有其他各种方法可以防止它持有这种锁(例如SELECT … NOWAIT,在REPGATABLE READ或SERIALIZABLE隔离级别运行,Postgres 9.5中的 我认为使用这种重试循环方法的最干净的实现是完全跳过SELECT,只运行UPDATE … WHERE locked = false,每次提交.通过在调用cur.execute()之后检查cur.rowcount,可以判断是否获得了锁.如果您需要从锁记录中提取其他信息,则可以使用UPDATE … RETURNING语句. 但我不得不同意@Kevin,并说你可能会更好地利用Postgres的内置锁定支持,而不是试图重新发明它.它会为你解决很多问题,例如: >自动检测到死锁 最简单的方法可能是将SELECT作为SELECT FROM my_locks FOR UPDATE实现,只需将其作为COMMIT释放,然后让进程争用行锁.如果您需要更多灵活性(例如阻止/非阻塞调用,事务/会话/自定义范围),advisory locks应该证明是有用的. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |