Перебор большого набора запросов Django, в то время как данные меняются в другом месте - PullRequest
3 голосов
/ 20 января 2010

Итерация по набору запросов, например так:

class Book(models.Model):
    # <snip some other stuff>
    activity = models.PositiveIntegerField(default=0)
    views = models.PositiveIntegerField(default=0)

    def calculate_statistics():
        self.activity = book.views * 4
        book.save()

def cron_job_calculate_all_book_statistics():
    for book in Book.objects.all():
        book.calculate_statistics()

... работает просто отлично. Тем не менее, это задача cron. book.views увеличивается, пока это происходит. Если book.views изменено во время выполнения этого cronjob, оно отменяется.

Теперь book.views не изменяется cronjob, но он кэшируется во время вызова набора запросов .all(). Когда book.save(), я чувствую, что он использует старое значение book.views.

Есть ли способ убедиться, что обновлено только поле activity? Или, скажем, есть 100 000 книг. Это займет довольно много времени, чтобы бежать. Но book.views будет с того момента, когда изначально запускается набор запросов. Это решение просто использовать .iterator()?

ОБНОВЛЕНИЕ: Вот то, что я делаю эффективно. Если у вас есть идеи о том, как сделать эту работу хорошо встроенной, тогда я полностью за нее.

def calculate_statistics(self):
    self.activity = self.views + self.hearts.count() * 2
    # Can't do self.comments.count with a comments GenericRelation, because Comment uses
    # a TextField for object_pk, and that breaks the whole system. Lame.
    self.activity += Comment.objects.for_model(self).count() * 4
    self.save()

Ответы [ 3 ]

3 голосов
/ 21 января 2010

В дополнение к тому, что говорили другие, если вы перебираете большой набор запросов, вы должны использовать iterator ():

Book.objects.filter(stuff).order_by(stuff).iterator()

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

3 голосов
/ 20 января 2010

Следующее выполнит работу за вас в Django 1.1, цикл не требуется:

from django.db.models import F
Book.objects.all().update(activity=F('views')*4)

Вы можете сделать более сложный расчет:

for book in Book.objects.all().iterator():
    Book.objects.filter(pk=book.pk).update(activity=book.calculate_activity())

Оба эти варианта могут оставить поле активности не синхронизированным с остальными, но я предполагаю, что вы согласны с этим, учитывая, что вы вычисляете его в задании cron.

1 голос
/ 21 января 2010

Независимо от того, как вы решаете эту проблему, остерегайтесь проблем, связанных с транзакциями. Например. уровень изоляции транзакции по умолчанию установлен на REPEATABLE READ, по крайней мере для бэкэнда MySQL. Это, плюс тот факт, что и Django, и db backend работают в определенном режиме автоматической фиксации с текущей транзакцией, означает, что даже если вы используете (очень приятно) whrde предложение, значение `views 'больше не будет действительный. Я могу ошибаться, но чувствую себя предупрежденным.

...