Django 的 select_for_update 方法是否与 update 方法一起使用?
Does Django's select_for_update method work with the update method?
我正在使用的 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
查询,锁是否会增加任何针对竞争条件的保护。 (我进入这个思路是因为我正在重构更新单行两列值时使用锁的代码。)
As far as I'm aware update just performs an UPDATE ... WHERE query, with no SELECT before it
是的,没错。您可以通过查看实际查询来确认这一点。以规范的 django 教程“投票”应用程序为例:
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'}
#]
A secondary question is whether a lock even adds any protection against race conditions if one makes a single UPDATE query against the locked rows
总的来说,是的。在特定情况下是否有必要取决于您担心哪种竞争条件。例如,锁将防止另一个事务可能尝试更新同一行的竞争条件。
竞争条件也可以在没有锁的情况下避免,这取决于 update/race 条件的性质。有时交易就足够了,有时则不然。您还可以使用在数据库服务器端评估的表达式来防止竞争条件(例如使用 Django's F()
expressions)。
还有其他注意事项,例如您的数据库方言、隔离级别等等。
关于竞争条件思想的额外参考:PostgreSQL anti-patterns: read-modify-write cycles (archive)
我正在使用的 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
查询,锁是否会增加任何针对竞争条件的保护。 (我进入这个思路是因为我正在重构更新单行两列值时使用锁的代码。)
As far as I'm aware update just performs an UPDATE ... WHERE query, with no SELECT before it
是的,没错。您可以通过查看实际查询来确认这一点。以规范的 django 教程“投票”应用程序为例:
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'}
#]
A secondary question is whether a lock even adds any protection against race conditions if one makes a single UPDATE query against the locked rows
总的来说,是的。在特定情况下是否有必要取决于您担心哪种竞争条件。例如,锁将防止另一个事务可能尝试更新同一行的竞争条件。
竞争条件也可以在没有锁的情况下避免,这取决于 update/race 条件的性质。有时交易就足够了,有时则不然。您还可以使用在数据库服务器端评估的表达式来防止竞争条件(例如使用 Django's F()
expressions)。
还有其他注意事项,例如您的数据库方言、隔离级别等等。
关于竞争条件思想的额外参考:PostgreSQL anti-patterns: read-modify-write cycles (archive)