如何强制 Django 忽略任何缓存并重新加载数据?

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

我看到的是,在第一次获取之后,进程从未看到任何新数据。我运行了一些测试,看起来 Django 正在缓存结果,尽管我每次都在构建新的 QuerySet。为了验证这一点,我使用了 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

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

我找不到关于 update ()方法的任何文档,也不知道它还会做什么坏事。

我的问题是,为什么我会看到这种缓存行为,它与 姜戈医生的说法相矛盾?我该怎么阻止这种事发生呢?

42697 次浏览

看起来 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。
如果性能不是特别重要,我会像你一样,在 count之前调用 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

您还可以在执行任何工作之前使用 MyModel.objects._clone().count().QuerySet调用 _clone()中的所有方法,这样可以确保任何内部缓存失效。

根本原因是 MyModel.objects每次都是相同的实例。通过克隆它,您将创建一个没有缓存值的新实例。当然,如果您希望使用相同的实例,总是可以访问并使缓存失效。

我们在强制 django 刷新“缓存”方面做了很多努力——事实证明,这根本不是一个缓存,而是由于事务而产生的一个工件。这可能不适用于您的示例,但是在 django 视图中,默认情况下,有一个对事务的隐式调用,然后 mysql 将该事务与启动后从其他进程发生的任何更改隔离开来。

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

正如我所说的,这肯定适用于视图,但不确定它是否适用于在视图内不运行的 django 代码。

详细信息请点击:

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

有了这个问题,并找到了两个明确的解决方案,我认为它值得张贴另一个答案。

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

像这样演示

在终端1中运行 django shell

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

另一种解决方案是更改 MySQL 的 my.cnf 以更改默认事务模式

transaction-isolation = READ-COMMITTED

注意,这对于 Mysql 来说是一个相对较新的特性,它具有 二进制日志记录/奴役的一些后果。如果需要,您还可以将此内容放在 django 连接序言中。

三年后更新

现在 Django 1.6已经有了 在 MySQL 中打开自动提交,这不再是一个问题。不管你的 MySQL 是在 REPEATABLE-READ(默认)还是 READ-COMMITTED事务隔离模式下,上面的例子现在没有 flush_transaction()代码也能正常工作。

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

尽管如此,你仍然有理由使用 ABc0事务隔离。如果要在事务中放置终端1,并希望看到来自终端2的写操作,则需要 READ-COMMITTED

flush_transaction()代码现在在 Django 1.6中生成了一个弃用警告,因此我建议您删除它。

我不太建议你这么做,但你可以自己毁掉缓存:

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

这里有一个更好的技术,它不依赖于修改 QuerySet 的内部结构: 记住缓存是在 查询集中进行的,但是刷新数据只需要重新执行底层的 质疑。QuerySet 实际上只是一个包装 Query 对象的高级 API,外加一个容器(带缓存!)查询结果。因此,给定一个查询集,这里有一个强制刷新的通用方法:

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

非常简单! 您当然可以将其实现为一个 helper 函数,并根据需要使用。

如果将 .all()附加到查询集,它将强制从数据库重新读取 MyModel.objects.all().count()而不是 MyModel.objects.count()