django select_for_update 获取关系锁

Posted

技术标签:

【中文标题】django select_for_update 获取关系锁【英文标题】:django select_for_update acquires relation lock 【发布时间】:2021-12-20 09:45:23 【问题描述】:

我有这个代码示例应该在 postgres 中使用行(元组)锁定,但它似乎改为使用表(关系)锁定:

with transaction.Atomic(savepoint=True, durable=False):
    record = MyModel.objects.select_for_update().filter(pk='1234')
    record.delete()
    time.sleep(5)
    raise Exception

通过在事务期间查看 pg_locks 我可以看到:

select locktype, database, relation::regclass, pid, mode, granted from pg_locks where pid <> pg_backend_pid();

据我所知,我应该在 locktype 中看到“元组”,因为我只锁定特定行而不是整个表

【问题讨论】:

【参考方案1】:

首要任务

您实际上没有执行SELECT FOR UPDATE 查询。

record = MyModel.objects.select_for_update().filter(pk='1234') 返回QuerySet,不执行查询。 record.delete() 只执行DELETE 命令。 SELECT FOR UPDATE 查询将获得relation RowShareLock。 您可以通过执行QuerySet.first() 来验证这一点,即record = MyModel.objects.select_for_update().filter(pk='1234').first()。 您可以通过log all sql queries 验证这一点。

行级(元组)锁

获取了行级FOR UPDATE 锁,但未显示在您的pg_locks 视图中(它也未显示在我的视图中)。相反,我们看到的是transactionidExclusiveLock(和virtualxidExclusiveLock)。

来自https://www.postgresql.org/docs/9.3/view-pg-locks.html

虽然元组是一种可锁定类型的对象,但有关行级锁的信息存储在磁盘上,而不是内存中,因此行级锁通常不会出现在此视图中。如果事务正在等待行级锁,它通常会在视图中显示为等待该行锁当前持有者的永久事务 ID。

来自https://www.postgresql.org/docs/9.4/explicit-locking.html:

FOR UPDATE

...

FOR UPDATE 锁定模式也被任何DELETE 连续获取...

您可以通过在 psql 终端中运行来凭经验验证这一点:

之前record.delete() SELECT FROM mymodel WHERE id='1' FOR UPDATE; 有效。 SELECT FROM mymodel WHERE id='1234' FOR UPDATE; 有效。 在record.delete()之后 SELECT FROM mymodel WHERE id='1' FOR UPDATE; 有效。 SELECT FROM mymodel WHERE id='1234' FOR UPDATE; 不起作用。

表级(关系)锁

    relation AccessShareLock 似乎是为您未在代码示例中显示的 SELECT 查询获取的,例如MyModel.objects.filter(pk='1234').first()relation RowExclusiveLock 是为 DELETE 命令获取的。

虽然这些是表级锁,但它们只与EXCLUSIVE 和/或ACCESS EXCLUSIVE 锁发生冲突,大多数其他 DQL(数据查询语言)和 DML(数据操作语言)命令都不会获取这些锁。

来自https://www.postgresql.org/docs/9.4/explicit-locking.html:

ACCESS SHARE

仅与ACCESS EXCLUSIVE 锁定模式冲突。

SELECT 命令在引用的表上获取此模式的锁。一般来说,任何只读取表而不修改表的查询都会获取这种锁定模式。

ROW EXCLUSIVE

EXCLUSIVEACCESS EXCLUSIVE 锁定模式冲突。

命令UPDATEDELETEINSERT 在目标表上获取此锁定模式(除了ACCESS SHARE 对任何其他引用表的锁定)。一般情况下,任何修改表中数据的命令都会获得这种锁模式。

【讨论】:

感谢@aaron 的解释。事实上,我更改了 sn-p,因此您看不到 AccessShareLock。试想一下,我有 get() 而不是 filter()... 总而言之,根据您的写作,我实际上同时有两个不同的锁级别。第一个在行上(我在 pg_locks 中看不到),而第二个在表上(作为“RowExclusiveLock”)? 是的,没错。

以上是关于django select_for_update 获取关系锁的主要内容,如果未能解决你的问题,请参考以下文章

django select_for_update 获取关系锁

九Django的锁事务ajax

为啥不支持它的数据库可以简单地忽略 select_for_update?

从原子块调用的“select_for_update”仍然是 TransactionManagementError

Django的锁和事务

通过 select_for_update 记录中的两个 ForeignKey 优化访问