首页 > 解决方案 > Django 的 select_for_update 方法是否与 update 方法一起使用?

问题描述

我正在使用的 Django 2.2的文档提供了以下示例用法select_for_update

from django.db import transaction

entries = Entry.objects.select_for_update().filter(author=request.user)
with transaction.atomic():
    for entry in entries:
        ...

使用这种方法,可能会改变分配给的模型实例entry并调用save它们。

在某些情况下,我更喜欢下面的替代方法,但我不确定它是否会与select_for_update.

with transaction.atomic():
    Entry.objects.select_for_update().filter(author=request.user).update(foo="bar", wobble="wibble")

该文档指出,在评估查询集时会创建锁,因此我怀疑该update方法是否可行。据我所知update,只是执行一个UPDATE ... WHERE查询,SELECT之前没有。但是,如果对 Django ORM 的这方面更有经验的人能证实这一点,我将不胜感激。

第二个问题是,如果一个人对锁定的行进行单个UPDATE查询,那么锁是否甚至增加了针对竞争条件的任何保护。(我进入这个思路是因为我正在重构在更新单行的两列的值时使用锁的代码。)

标签: pythondjangopostgresqllockingdjango-orm

解决方案


据我所知,更新只是执行一个 UPDATE ... WHERE 查询,之前没有 SELECT

对,那是正确的。您可以通过查看实际查询来确认这一点。以规范的 django 教程“polls”应用为例:

with transaction.atomic():
    qs = polls.models.Question.objects.select_for_update().all()
    qs.update(question_text='test')

print(connection.queries)
# {'sql': 'UPDATE "polls_question" SET "question_text" = \'test\'', 'time': '0.008'}

因此,正如您所料,没有SELECT.

不过,确保获得锁就像做任何事情来评估查询集一样简单。

with transaction.atomic():
    qs = polls.models.Question.objects.select_for_update().all()
    list(qs) # cause evaluation, locking the selected rows
    qs.update(question_text='test')

print(connection.queries)
#[...
# {'sql': 'SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" FOR UPDATE', 'time': '0.003'},
# {'sql': 'UPDATE "polls_question" SET "question_text" = \'test\'', 'time': '0.001'}
#]

第二个问题是,如果一个人对锁定的行进行单个 UPDATE 查询,那么锁是否甚至增加了针对竞争条件的任何保护

一般来说,是的。在特定情况下是否有必要取决于您担心哪种竞争条件。例如,锁将防止另一个事务可能尝试更新同一行的竞争条件。

根据更新/竞争条件的性质,也可以在没有锁的情况下避免竞争条件。有时一笔交易就足够了,有时则不然。您还可以使用在数据库服务器端评估的表达式来防止竞争条件(例如使用Django 的F()表达式)。

还有其他考虑因素,例如您的数据库方言、隔离级别等。

关于竞争条件思想的附加参考:PostgreSQL 反模式:读-修改-写周期存档


推荐阅读