我如何强制Django忽略任何caching和重新加载数据?

我正在使用未从HTTP请求调用的进程中的Django数据库模型。 这个过程应该每隔几秒轮询一次新的数据,并对其进行一些处理。 我有一个睡眠了几秒钟的循环,然后从数据库中获取所有未处理的数据。

我所看到的是在第一次获取之后,这个过程从来没有看到任何新的数据。 我运行了一些testing,看起来像Djangocaching结果,即使我每次构build新的QuerySets。 为了validation这一点,我从一个Python shell中做了这个:

>>> 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 

如您所见,添加新数据不会改变结果数量。 但是,调用pipe理器的update()方法似乎解决了这个问题。

我找不到有关update()方法的任何文档,也不知道可能会有哪些不好的事情发生。

我的问题是,为什么我看到这个caching行为,这与Django文档所说的相矛盾? 我如何防止它发生?

有这个问题,并find了两个明确的解决scheme,我认为值得发布另一个答案。

这是MySQL默认事务模式的一个问题。 Django在开始时打开一个事务,这意味着默认情况下你不会看到在数据库中所做的更改。

演示像这样

在terminal1中运行django shell

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

另一个在terminal2

 >>> 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' >>> 

回到terminal1来certificate问题 – 我们仍然从数据库中读取旧值。

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

现在在terminal1演示解决scheme

 >>> 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() 

另一种解决scheme是更改My.cnf for MySQL以更改默认事务模式

 transaction-isolation = READ-COMMITTED 

请注意,这是Mysql的一个相对较新的function,并且对二进制日志/从属有一些影响 。 如果你愿意的话,你也可以把它放在Django连接的前言中。

3年后更新

现在Django 1.6已经在MySQL中启用了自动提交function,这已经不是什么问题了。 上面的例子现在可以在没有flush_transaction()代码的情况下正常工作,不pipe你的MySQL处于REPEATABLE-READ (默认)还是READ-COMMITTED事务隔离模式。

在非自动提交模式下运行的以前版本的Django发生的事情是,第一个select语句打开了一个事务。 由于MySQL的默认模式是REPEATABLE-READ这意味着后续select语句不会读取数据库的更新,因此需要上面的flush_transaction()代码来停止事务并启动一个新的事务。

尽pipe你可能想要使用READ-COMMITTED事务隔离,但仍有一些原因。 如果您要将terminal1置于事务中,而您希望看到来自terminal2的写入,则需要READ-COMMITTED

flush_transaction()代码现在在Django 1.6中产生一个弃用警告,所以我build议你删除它。

我们一直在努力争取django来刷新“caching” – 事实certificate,这实际上并不是一个真正的caching,而是一个由交易造成的神器。 这可能不适用于您的示例,但在django视图中,默认情况下会隐式调用事务,然后,mysql将从您开始的其他进程发生的任何更改中隔离出来。

我们使用@transaction.commit_manually装饰器,并在需要最新信息的每个场合之前调用transaction.commit()

正如我所说,这绝对适用于视图,不确定是否适用于不在视图中运行的Django代码。

详细信息在这里:

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

看起来像count()在第一次之后进入caching。 这是QuerySet.count的django源代码:

 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计数。
如果性能不是非常重要的,我只是做你正在做的事情,在count之前调用update

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 

我不确定我会推荐它…但你可以自己杀死caching:

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

下面是一个更好的方法,它不依赖于QuerySet内部的操作:请记住,caching是在QuerySet中发生的,但刷新数据只需要重新执行基础Query 。 QuerySet实际上只是一个包装Query对象的高级API,另外还有一个用于查询结果的容器(包含caching!)。 因此,给定一个查询集,这是一个强制刷新的通用方法:

 >>> 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() ,以确保任何内部caching均失效。

根本原因是MyModel.objects都是同一个实例。 通过克隆它,您正在创build一个没有caching值的新实例。 当然,如果你喜欢使用相同的实例,你总是可以访问和使caching无效。