Как заставить 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 – это один и тот же экземпляр каждый раз. Клонируя его, вы создаете новый экземпляр без кешированного значения. Разумеется, вы всегда можете использовать и недействить кеш, если хотите использовать один и тот же экземпляр.

  • Как сделать этот запрос соединения в Django
  • Пересечение списка объектов в шаблоне Django
  • Django Template Slice - Обратный порядок
  • Celery + Django: не удается запустить celerybeat в Windows 7
  • Как конкретно работают типы контента Django?
  • Итерации forLoop как индекс индекса в шаблоне Django
  • Как добавить дополнительные данные в сообщения Django?
  • Python / Django импортирует неправильный модуль (относительный, когда он должен быть абсолютным)
  • Развертывание Django. Ошибка при загрузке модуля MySQLdb. Чтение / запись неисправностей из каталога / tmp
  • Как проверить, является ли этот пользователь анонимным или фактически пользователем в моей системе?
  • Django, ManyToManyField - ProgrammingError: отношение foo_bar не существует. Признается в миграциях, хотя отношение никогда не создается
  • Python - лучший язык программирования в мире.