Как заставить Django игнорировать любые кеши и перезагружать данные?

Я использую модели базы данных Django из процесса, который не вызывается из HTTP-запроса. Предполагается, что процесс будет обрабатывать новые данные каждые несколько секунд и выполнять некоторую обработку на нем. У меня есть цикл, который спит в течение нескольких секунд, а затем получает все необработанные данные из базы данных.

Я вижу, что после первой выборки процесс никогда не видит никаких новых данных. Я провел несколько тестов, и похоже, что Django – это кеширование результатов, хотя я каждый раз создаю новые QuerySets. Чтобы проверить это, я сделал это из оболочки Python:

>>> MyModel.objects.count() 885 # (Here I added some more data from another process.) >>> MyModel.objects.count() 885 >>> MyModel.objects.update() 0 >>> MyModel.objects.count() 1025 

Как видите, добавление новых данных не изменяет счетчик результатов. Однако вызов метода update () менеджера, похоже, устраняет проблему.

Я не могу найти документацию по этому методу update () и не знаю, какие другие плохие вещи он может сделать.

Мой вопрос: почему я вижу это поведение кеширования, что противоречит тому, что говорит Django ? И как я могу предотвратить это?

6 Solutions collect form web for “Как заставить Django игнорировать любые кеши и перезагружать данные?”

Получив эту проблему и нашел для нее два окончательных решения, я подумал, что стоит опубликовать другой ответ.

Это проблема с режимом транзакций MySQL по умолчанию. Django открывает транзакцию в начале, что означает, что по умолчанию вы не увидите изменений, внесенных в базу данных.

Продемонстрируйте это

Запустите оболочку django в терминале 1

 >>> MyModel.objects.get(id=1).my_field u'old' 

И еще в терминале 2

 >>> MyModel.objects.get(id=1).my_field u'old' >>> a = MyModel.objects.get(id=1) >>> a.my_field = "NEW" >>> a.save() >>> MyModel.objects.get(id=1).my_field u'NEW' >>> 

Вернемся к терминалу 1, чтобы продемонстрировать проблему – мы все еще читаем старое значение из базы данных.

 >>> MyModel.objects.get(id=1).my_field u'old' 

Теперь в терминале 1 продемонстрируйте решение

 >>> from django.db import transaction >>> >>> @transaction.commit_manually ... def flush_transaction(): ... transaction.commit() ... >>> MyModel.objects.get(id=1).my_field u'old' >>> flush_transaction() >>> MyModel.objects.get(id=1).my_field u'NEW' >>> 

Теперь новые данные считываются

Вот этот код в легко вставляемом блоке с docstring

 from django.db import transaction @transaction.commit_manually def flush_transaction(): """ Flush the current transaction so we don't read stale data Use in long running processes to make sure fresh data is read from the database. This is a problem with MySQL and the default transaction mode. You can fix it by setting "transaction-isolation = READ-COMMITTED" in my.cnf or by calling this function at the appropriate moment """ transaction.commit() 

Альтернативным решением является изменение my.cnf для MySQL для изменения режима транзакций по умолчанию

 transaction-isolation = READ-COMMITTED 

Обратите внимание, что это относительно новая функция для Mysql и имеет некоторые последствия для двоичного ведения журнала / ведомости . Вы также можете поместить это в преамбулу соединения django, если хотите.

Обновление через 3 года

Теперь, когда Django 1.6 включил autocommit в MySQL, это уже не проблема. Приведенный выше пример теперь отлично работает без flush_transaction() независимо от того, находится ли ваш MySQL в режиме изоляции транзакции REPEATABLE-READ (по умолчанию) или READ-COMMITTED .

То, что происходило в предыдущих версиях Django, работавших в режиме без автокоммутации, состояло в том, что первый оператор select открыл транзакцию. Поскольку режим по умолчанию MySQL является REPEATABLE-READ это означает, что никакие обновления в базе данных не будут прочитаны последующими операциями select поэтому необходимо, чтобы код flush_transaction() выше, который останавливает транзакцию и запускает новую.

Есть еще причины, по которым вы, возможно, захотите использовать изоляцию транзакций READ-COMMITTED . Если вы должны были положить терминал 1 в транзакцию, и вы хотели бы видеть записи с терминала 2, вам понадобится READ-COMMITTED .

flush_transaction() код flush_transaction() выдает предупреждение о flush_transaction() в Django 1.6, поэтому я рекомендую удалить его.

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

мы использовали декоратор @transaction.commit_manually и звонили на transaction.commit() непосредственно перед каждым случаем, когда вам нужна обновленная информация.

Как я уже сказал, это определенно относится к представлениям, не уверен, будет ли оно применяться к коду django, который не запускается внутри представления.

подробная информация здесь:

http://devblog.resolversystems.com/?p=439

Кажется, что count() переходит в кеш после первого раза. Это источник django для QuerySet.count:

 def count(self): """ Performs a SELECT COUNT() and returns the number of records as an integer. If the QuerySet is already fully cached this simply returns the length of the cached results set to avoid multiple SELECT COUNT(*) calls. """ if self._result_cache is not None and not self._iter: return len(self._result_cache) return self.query.get_count(using=self.db) 

update , похоже, делает довольно много дополнительной работы, помимо того, что вам нужно.
Но я не могу придумать лучшего способа сделать это, не написав собственный SQL для подсчета.
Если производительность не очень важна, я просто буду делать то, что вы делаете, вызывая update перед count .

QuerySet.update:

 def update(self, **kwargs): """ Updates all elements in the current QuerySet, setting all the given fields to the appropriate values. """ assert self.query.can_filter(), \ "Cannot update a query once a slice has been taken." self._for_write = True query = self.query.clone(sql.UpdateQuery) query.add_update_values(kwargs) if not transaction.is_managed(using=self.db): transaction.enter_transaction_management(using=self.db) forced_managed = True else: forced_managed = False try: rows = query.get_compiler(self.db).execute_sql(None) if forced_managed: transaction.commit(using=self.db) else: transaction.commit_unless_managed(using=self.db) finally: if forced_managed: transaction.leave_transaction_management(using=self.db) self._result_cache = None return rows update.alters_data = True с def update(self, **kwargs): """ Updates all elements in the current QuerySet, setting all the given fields to the appropriate values. """ assert self.query.can_filter(), \ "Cannot update a query once a slice has been taken." self._for_write = True query = self.query.clone(sql.UpdateQuery) query.add_update_values(kwargs) if not transaction.is_managed(using=self.db): transaction.enter_transaction_management(using=self.db) forced_managed = True else: forced_managed = False try: rows = query.get_compiler(self.db).execute_sql(None) if forced_managed: transaction.commit(using=self.db) else: transaction.commit_unless_managed(using=self.db) finally: if forced_managed: transaction.leave_transaction_management(using=self.db) self._result_cache = None return rows update.alters_data = True с def update(self, **kwargs): """ Updates all elements in the current QuerySet, setting all the given fields to the appropriate values. """ assert self.query.can_filter(), \ "Cannot update a query once a slice has been taken." self._for_write = True query = self.query.clone(sql.UpdateQuery) query.add_update_values(kwargs) if not transaction.is_managed(using=self.db): transaction.enter_transaction_management(using=self.db) forced_managed = True else: forced_managed = False try: rows = query.get_compiler(self.db).execute_sql(None) if forced_managed: transaction.commit(using=self.db) else: transaction.commit_unless_managed(using=self.db) finally: if forced_managed: transaction.leave_transaction_management(using=self.db) self._result_cache = None return rows update.alters_data = True с def update(self, **kwargs): """ Updates all elements in the current QuerySet, setting all the given fields to the appropriate values. """ assert self.query.can_filter(), \ "Cannot update a query once a slice has been taken." self._for_write = True query = self.query.clone(sql.UpdateQuery) query.add_update_values(kwargs) if not transaction.is_managed(using=self.db): transaction.enter_transaction_management(using=self.db) forced_managed = True else: forced_managed = False try: rows = query.get_compiler(self.db).execute_sql(None) if forced_managed: transaction.commit(using=self.db) else: transaction.commit_unless_managed(using=self.db) finally: if forced_managed: transaction.leave_transaction_management(using=self.db) self._result_cache = None return rows update.alters_data = True с def update(self, **kwargs): """ Updates all elements in the current QuerySet, setting all the given fields to the appropriate values. """ assert self.query.can_filter(), \ "Cannot update a query once a slice has been taken." self._for_write = True query = self.query.clone(sql.UpdateQuery) query.add_update_values(kwargs) if not transaction.is_managed(using=self.db): transaction.enter_transaction_management(using=self.db) forced_managed = True else: forced_managed = False try: rows = query.get_compiler(self.db).execute_sql(None) if forced_managed: transaction.commit(using=self.db) else: transaction.commit_unless_managed(using=self.db) finally: if forced_managed: transaction.leave_transaction_management(using=self.db) self._result_cache = None return rows update.alters_data = True 

Я не уверен, что рекомендую … но вы можете просто убить кеш себе:

 >>> qs = MyModel.objects.all() >>> qs.count() 1 >>> MyModel().save() >>> qs.count() # cached! 1 >>> qs._result_cache = None >>> qs.count() 2 

И вот лучшая техника, которая не полагается на возиться с внутренностями QuerySet: помните, что кеширование происходит в QuerySet , но обновление данных просто требует повторного выполнения базового Query . QuerySet – это просто API-интерфейс высокого уровня, который обертывает объект Query, а также контейнер (с кешированием!) Для результатов запроса. Таким образом, с учетом набора запросов, это универсальный способ принудительного обновления:

 >>> MyModel().save() >>> qs = MyModel.objects.all() >>> qs.count() 1 >>> MyModel().save() >>> qs.count() # cached! 1 >>> from django.db.models import QuerySet >>> qs = QuerySet(model=MyModel, query=qs.query) >>> qs.count() # refreshed! 2 >>> party_time() 

Довольно легко! Вы можете, конечно, реализовать это как вспомогательную функцию и использовать по мере необходимости.

Если вы .all() в набор запросов, это заставит перечитать данные из БД. Попробуйте MyModel.objects.all().count() вместо MyModel.objects.count() .

Вы также можете использовать MyModel.objects._clone().count(). Все методы в QuerySet вызывают _clone() перед выполнением какой-либо работы – это гарантирует, что любые внутренние кэши будут недействительными.

Основная причина в том, что MyModel.objects – это один и тот же экземпляр каждый раз. Клонируя его, вы создаете новый экземпляр без кешированного значения. Разумеется, вы всегда можете использовать и недействить кеш, если хотите использовать один и тот же экземпляр.

  • Как опубликовать в API-интерфейсе Django REST Framework с помощью связанных моделей
  • страшная «не такая же ошибка объекта», пробирая объект queryset.query
  • Форма редактирования Django на основе формы добавления?
  • Почему Django и CherryPy не поддерживают HTTP-глагол?
  • Как сделать python только на https Heroku?
  • Шаблон Django: Вставить css из файла
  • Как отправить не-ASCII-символы с помощью httplib, когда content-type - "application / xml"
  • Python Django удаляет текущий объект
  •  
    Interesting Posts for Van-Lav

    ChoiceField не отображает пустую метку при использовании кортежа

    Добавить легенду в 3D-диаграмме рассеяния с разбросом () в Matplotlib

    Как отключить сторонний API при выполнении модульных тестов Django?

    Передача параметра в задание на ткань

    Зачем понимать список в переменной цикла, но генераторы этого не делают?

    Запрос HTTPConnection socket.gaierror в python

    Почему это непоследовательное поведение при использовании результатов печати на основе scrapy?

    декоратор для установки атрибутов функции

    В чем преимущество понимания списка по циклу for?

    PyPy – Как он может победить CPython?

    Подписанный эквивалент шестнадцатеричного значения дополнения 2

    «Слишком много значений для распаковки» Исключение

    Project Euler # 18 – как перебрать все возможные пути в древовидной структуре с помощью Python?

    Операция строки Python, извлечение текста между html-тегами

    Python сохраняет фигуру matplotlib на объекте PIL Image

    Python - лучший язык программирования в мире.