Что делать, если я удаляю строки из набора запросов Django, а затем снова фильтрую? - PullRequest
0 голосов
/ 07 января 2019

Рассмотрим следующий код:

questions = Question.objects.only('id', 'pqa_id', 'retain')
del_questions = questions.filter(retain=False)
# Some computations on del_questions
del_questions.delete()
add_questions = questions.filter(pqa_id=None)

Не будет add_questions не содержать вопросов с retain=False? То есть questions объект переоценивается, когда мы запускаем delete() в его подмножестве del_questions?

1 Ответ

0 голосов
/ 08 января 2019

Краткий ответ : здесь вы используете разные QuerySet с, поэтому вы, создав копию, сделаете еще один запрос. Если вы используете тот же QuerySet, Django удалит кеш, и поэтому пересмотрит QuerySet. Однако возможно позволить объектам временно пережить вызов .delete() из-за кэширования в другом QuerySet, который был оценен до .

- это questions объект переоценивается, когда мы запускаем delete() на его подмножестве del_questions

questions с никогда оценивается в первую очередь. QuerySet является итеративным, и в случае, если вы перебираете его (или извлекаете длину, или что-то еще), результатом будет запрос. Но если вы напишите Model.objects.all().filter(foo=3), тогда Django не сначала "оценит" .all(), извлекая все Model объекты в память.

A QuerySet по сути является инструментом для создания запроса, объединяя операции и каждый раз создавая новый набор запросов. В конце концов вы можете оценить один из наборов запросов.

Здесь примените .filter(..) для двух вызовов. Таким образом, мы построили два разных QuerySet с, и поэтому, если вы оцените первое, то это не приведет к кешированию во втором.

Вторым важным примечанием является то, что .delete() не оценивает набор запросов и, следовательно, не кэширует результаты. Если мы проверим метод .delete() [GitHub] , мы увидим:

def delete(self):
    """Delete the records in the current QuerySet."""
    assert self.query.can_filter(), \
        "Cannot use 'limit' or 'offset' with delete."

    if self._fields is not None:
        raise TypeError("Cannot call delete() after .values() or .values_list()")

    <b>del_query = self._chain()</b>

    # The delete is actually 2 queries - one to find related objects,
    # and one to delete. Make sure that the discovery of related
    # objects is performed on the same database as the deletion.
    del_query._for_write = True

    # Disable non-supported fields.
    del_query.query.select_for_update = False
    del_query.query.select_related = False
    del_query.query.clear_ordering(force_empty=True)

    collector = Collector(using=del_query.db)
    collector.collect(del_query)
    deleted, _rows_count = collector.delete()

    # Clear the result cache, in case this QuerySet gets reused.
    <b>self._result_cache = None</b>
    return deleted, _rows_count

При self._chain() создается копия кверсета. Таким образом, даже если это изменит состояние QuerySet, оно не изменит состояние QuerySet.

Другая интересная часть - self._result_cache = None, здесь Django сбрасывает кеш. Таким образом, если набор запросов уже был оценен за до , который вы вызывали .delete() (например, вы материализовали набор запросов перед вызовом .delete()), то он удалит этот кеш. Так что если вы переоцените QuerySet, это приведет к другому запросу на выборку элементов.

Однако существует сценарий, в котором данные все еще могут быть устаревшими. Например, следующее:

questions = Question.objects.all()  # create a queryset
list(questions)                     # materialize the result
questions2 = questions.all()        # create a copy of this queryset
questions2.delete()                 # remove the entries

Если мы сейчас назовем list(questions), мы получим элементы в кеше questions, и это QuerySet означает не недействительными, поэтому элементы "выживают" в .delete() из другого набора запросов (копия этого запроса, хотя в этом нет необходимости, просто Questions.objects.all().delete() также поможет).

...