- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
我正在构建一个基于 PostgreSQL 的锁定系统,我有两种方法,acquire
和 release
。
对于acquire
,它是这样工作的
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
仅当目标行的 locked
为 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
最佳答案
测试用例是这样的:
SELECT
并获取记录锁。SELECT
并进入锁的等待队列。UPDATE
/COMMIT
并释放锁。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 ,因此考虑到所有因素,多余的锁并没有那么糟糕。
可能最简单的解决方法是通过在每次 acquire
迭代后提交来限制锁定持续时间。有多种其他方法可以防止它持有此锁(例如 SELECT ... NOWAIT
,以 REPEATABLE READ
或 SERIALIZABLE
隔离级别运行, SELECT ... SKIP LOCKED
在 Postgres 9.5 中)。
我认为使用这种重试循环方法的最简洁的实现是完全跳过 SELECT
,只运行 UPDATE ... WHERE locked = false
,提交每一次。您可以通过在调用 cur.execute()
之后检查 cur.rowcount
来判断您是否获得了锁。如果需要从锁定记录中提取其他信息,可以使用 UPDATE ... RETURNING
语句。
但我必须同意 @Kevin ,并说您最好利用 Postgres 的内置锁定支持而不是尝试重新发明它。它会为你解决很多问题,例如:
最简单的方法可能是将 acquire
实现为 SELECT FROM my_locks FOR UPDATE
,将 release
简单地实现为 COMMIT
,并让进程竞争行锁。如果您需要更大的灵 active (例如阻塞/非阻塞调用、事务/ session /自定义范围),advisory locks应该证明是有用的。
关于sql - SELECT FOR UPDATE 的奇怪死锁 PostgreSQL 死锁问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31850567/
我是一名优秀的程序员,十分优秀!