Ответ 1
Counter.objects.get_or_create(name = name)
Counter.objects.filter(name = name).update(count = F('count')+1)
или используя выражение F:
counter = Counter.objects.get_or_create(name = name)
counter.count = F('count') +1
counter.save()
Я пытаюсь атомарно увеличивать простой счетчик в Django. Мой код выглядит следующим образом:
from models import Counter
from django.db import transaction
@transaction.commit_on_success
def increment_counter(name):
counter = Counter.objects.get_or_create(name = name)[0]
counter.count += 1
counter.save()
Если я правильно понимаю Django, это должно обернуть функцию в транзакции и сделать прирост атомом. Но это не работает, и в обновлении счетчика есть условие гонки. Как этот код можно сделать потокобезопасным?
Counter.objects.get_or_create(name = name)
Counter.objects.filter(name = name).update(count = F('count')+1)
или используя выражение F:
counter = Counter.objects.get_or_create(name = name)
counter.count = F('count') +1
counter.save()
В Django 1.4 существует поддержка предложений SELECT... FOR UPDATE, используя блокировки базы данных, чтобы гарантировать, что никакие данные не получаются одновременно по ошибке,
Django 1.7
from django.db.models import F
counter, created = Counter.objects.get_or_create(name = name)
counter.count = F('count') +1
counter.save()
Сохраняя это просто и опираясь на ответ @Oduvan:
counter, created = Counter.objects.get_or_create(name = name,
defaults={'count':1})
if not created:
counter.count = F('count') +1
counter.save()
Преимущество в том, что если объект был создан в первом выражении, вам не нужно делать никаких дополнительных обновлений.
Если вам не нужно знать значение счетчика, когда вы его устанавливаете, лучшим ответом, безусловно, будет лучший выбор:
counter = Counter.objects.get_or_create(name = name)
counter.count = F('count') + 1
counter.save()
Это сообщает вашей базе данных добавить 1 к значению count
, который он может сделать отлично, не блокируя другие операции. Недостатком является то, что вы не знаете, что именно count
вы только что установили. Если два потока одновременно попадают в эту функцию, они оба будут видеть одно и то же значение, и оба будут сообщать db о добавлении 1. ДБ в итоге добавит 2, как ожидалось, но вы не узнаете, какой из них был первым.
Если вы сейчас интересуетесь счетом, вы можете использовать опцию select_for_update
, на которую ссылается Эмиль Стэнстром. Вот что это выглядит:
from models import Counter
from django.db import transaction
@transaction.atomic
def increment_counter(name):
counter = (Counter.objects
.select_for_update()
.get_or_create(name=name)[0]
counter.count += 1
counter.save()
Это считывает текущее значение и блокирует совпадающие строки до конца транзакции. Теперь только один рабочий может читать за раз. См. документы для получения дополнительной информации о select_for_update.
Или, если вам просто нужен счетчик, а не постоянный объект, вы можете использовать счетчик itertools, который реализован в C. GIL обеспечит необходимую безопасность.
- Sai