Django 的 select_for_update 方法是不是与 update 方法一起使用?
Posted
技术标签:
【中文标题】Django 的 select_for_update 方法是不是与 update 方法一起使用?【英文标题】:Does Django's select_for_update method work with the update method?Django 的 select_for_update 方法是否与 update 方法一起使用? 【发布时间】:2021-09-30 15:20:54 【问题描述】:我正在使用的documentation for 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
查询,锁定是否甚至会增加任何针对竞争条件的保护。 (我进入这个思路是因为我正在重构在更新单行的两列的值时使用锁的代码。)
【问题讨论】:
如果您更喜欢不同的方法,您是否考虑过bulk_update? @markwalker_ 谢谢,这是个好建议。我想bulk_update
可能会更有效,因为它只使用一个 UPDATE 查询而不是每行一个。
用例我正在考虑更新单行并使用 select_for_update 锁定它的方法。在我写这个问题的时候,我没有意识到qs.get(foo="bar")
会导致基于qs
的查询集被评估,这意味着至少我不必遍历包含单个结果的查询集。跨度>
【参考方案1】:
据我所知,update 只是执行一个 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's F()
expressions)。
还有其他注意事项,例如您的数据库方言、隔离级别等。
关于比赛条件想法的其他参考:PostgreSQL anti-patterns: read-modify-write cycles (archive)
【讨论】:
感谢您的详细回答。出于某种原因,我没有想到查看 Django 发出的 SQL。以上是关于Django 的 select_for_update 方法是不是与 update 方法一起使用?的主要内容,如果未能解决你的问题,请参考以下文章