对于获取,它的工作原理如下
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 = true WHERE ID = '<name>' COMMIT break
并发布
BEGINUPDATE 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 psycopg2py.test -sv test_lock.py解决方法@H_301_46@ 测试用例如下:
> 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应该证明是有用的.
总结以上是内存溢出为你收集整理的SELECT FOR UPDATE的奇怪死锁PostgreSQL死锁问题全部内容,希望文章能够帮你解决SELECT FOR UPDATE的奇怪死锁PostgreSQL死锁问题所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)