Работает ли метод select_for_update от Django с методом обновления?

#python #django #postgresql #locking #django-orm

Вопрос:

Документация для 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 запрос к заблокированным строкам. (Я ввел этот ход мыслей, потому что я рефакторингую код, который использует блокировку при обновлении значений двух столбцов одной строки.)

Комментарии:

1. Где бы вы предпочли другой подход, вы рассматривали bulk_update ?

2. @markwalker_ спасибо, это хорошее предложение., Я полагаю bulk_update , что это, вероятно, будет более эффективным, так как он будет использовать только один запрос на обновление, а не по одному на строку.

3. В случае использования я думал о способах обновления одной строки и блокировки ее с помощью select_for_update. В то время, когда я писал вопрос, я не понимал, что qs.get(foo="bar") это приведет qs к оценке набора запросов, основанного на, что означает, что, по крайней мере, мне не придется повторять набор запросов, содержащий один результат.

Ответ №1:

Насколько мне известно, обновление просто выполняет ОБНОВЛЕНИЕ … ГДЕ запрос, без выбора перед ним

Да, это верно. Вы можете подтвердить это, посмотрев на фактические сделанные запросы. Использование канонического учебника по джанго «опросы» в качестве примера:

 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 .

Тем не менее, убедиться, что блокировка получена, было бы так же просто, как сделать что-либо, чтобы заставить queryset быть оцененным.

 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'}
#]
 

Вторичный вопрос заключается в том, добавляет ли блокировка вообще какую-либо защиту от условий гонки, если сделать один запрос на обновление для заблокированных строк

В общем, да. Необходимо ли это в конкретной ситуации, зависит от того, о каком состоянии гонки вы беспокоитесь. Блокировка предотвратит условия гонки, когда, например, другая транзакция может попытаться обновить ту же строку.

Условий гонки также можно избежать без блокировок, в зависимости от характера обновления/состояния гонки. Иногда транзакции достаточно, иногда нет. Вы также можете использовать выражения, которые вычисляются на стороне сервера в БД, чтобы предотвратить условия гонки (например, используя выражения Django F() ).

Существуют также другие соображения, такие как ваш диалект базы данных, уровни изоляции и многое другое.

Дополнительная ссылка на мысли о состоянии гонки: анти-шаблоны PostgreSQL: циклы чтения-изменения-записи (архив)

Комментарии:

1. Спасибо за подробный ответ. По какой-то причине мне не пришло в голову взглянуть на SQL, испускаемый Django.