2015-10-05 3 views
2

У меня есть модель Django с полем определено:Проверка ограничений в Django F Выражение

contacts = models.PositiveIntegerField(default=0)

... Дальше я пытаюсь сделать декремент на поле, используя F() выражение:

self.contacts = models.F("contacts") - quantity

Как проверить contacts - quantity не идет отрицательным, не вводя условия гонки?

+0

AFAIK нет никакого способа определения такого ограничения, но вы могли бы сделать проверка транзакции ('@ transaction.atomic'). Существует также [эта библиотека] (https://code.google.com/p/django-check-constraints/wiki/Features), но она действительно старая. – Ivan

+0

@Ivan, Спасибо за это. Если я использовал декоратор '@ transaction.atomic', тогда мне не нужно было бы использовать выражение' F() ', правильно? –

+0

Вы уже все извлекли, так что нет. – Ivan

ответ

1

Вы хотите избежать условий гонки. F() объекты - это только первый шаг в этом.

Первое, что вы хотели бы сделать в своей транзакции, - это получить блокировку в строке, которую вы меняете. Это предотвращает чтение устаревшего значения, когда вы пишете в базу данных. Это может быть сделано с обновлением ряда, который заблокирует строку из дальнейших обновлений, пока транзакция не будет зафиксирована или откат:

with transaction.atomic(): 
    obj.contacts = F('contacts') - quantity 

Если у вас есть замок, и сделал обновление, проверьте целостность данных (т. е. количество контактов не ниже 0). Если, по-прежнему, в противном случае, откатить транзакцию, вызывая исключение:

obj.refresh_from_db() 
if obj.contacts < 0: 
    raise ValueError("Capacity exceeded") 

Если есть осталось достаточно контактов, вы будете выйти из atomic() блок в этой точке, то сделка совершается, и другие запросы могут приобретите блокировку и попробуйте обновить значение. Если контактов недостаточно, транзакция откатывается назад, а другие запросы никогда не узнают, что значение изменилось, поскольку они ожидали получить блокировку.

Теперь, чтобы поставить все это вместе:

from django.db import transaction 
from django.db.models import F 

def update_contacts(obj, quantity): 
    with transaction.atomic(): 
     obj.contacts = F('contacts') - quantity 
     obj.save() 
     obj.refresh_from_db() 
     if obj.contacts < 0: 
      raise ValueError("Not enough contacts.") 

(Примечание:. obj.refresh_from_db() требует 1,8, в противном случае просто используйте MyModel.objects.get(pk=obj.pk))

+0

Разве вы не попадаете в db как минимум 3 раза? Вам действительно нужно только два - для извлечения и сохранения, если все проверки в порядке. – Ivan

+0

Я предполагаю, что вы также извлекаете 'obj'? – Ivan

+0

Если 2 запроса одновременно возвращают объект до того, как другой обновит его и получит блокировку, они могут как индивидуально пройти проверку. Когда вы обновляете объект в обоих запросах, количество контактов может упасть ниже 0. Если вы хотите избежать всех условий гонки, вам нужно как минимум 3 обращения к базе данных. – knbk

Смежные вопросы