sci*_*ppy 74 python django caching
我正在使用来自未通过HTTP请求调用的进程的Django数据库模型.该过程应该每隔几秒轮询一次新数据并对其进行一些处理.我有一个循环,睡眠几秒钟,然后从数据库中获取所有未处理的数据.
我所看到的是,在第一次获取后,进程永远不会看到任何新数据.我运行了一些测试,看起来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
Run Code Online (Sandbox Code Playgroud)
如您所见,添加新数据不会更改结果计数.但是,调用管理器的update()方法似乎可以解决问题.
我找不到有关该update()方法的任何文档,也不知道它可能做的其他坏事.
我的问题是,为什么我看到这种缓存行为,这与Django文档所说的相矛盾?我该如何防止它发生?
Nic*_*ood 93
遇到这个问题并找到了两个明确的解决方案,我认为值得发布另一个答案.
这是MySQL的默认事务模式的问题.Django在开始时打开一个事务,这意味着默认情况下您不会看到数据库中所做的更改.
像这样展示
在终端1中运行django shell
>>> MyModel.objects.get(id=1).my_field
u'old'
Run Code Online (Sandbox Code Playgroud)
在终端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'
>>>
Run Code Online (Sandbox Code Playgroud)
返回终端1以演示问题 - 我们仍然从数据库中读取旧值.
>>> MyModel.objects.get(id=1).my_field
u'old'
Run Code Online (Sandbox Code Playgroud)
现在终端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'
>>>
Run Code Online (Sandbox Code Playgroud)
现在读取新数据
这是使用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()
Run Code Online (Sandbox Code Playgroud)
替代解决方案是更改my.cnf for MySQL以更改默认事务模式
transaction-isolation = READ-COMMITTED
Run Code Online (Sandbox Code Playgroud)
请注意,这是Mysql的一个相对较新的功能,并且对二进制日志记录/从属有一些影响.如果你愿意的话,你也可以将它放在django连接序言中.
3年后更新
既然Django 1.6已经在MySQL中启用了自动提交,那么这不再是一个问题.上面的示例现在可以在没有flush_transaction()代码REPEATABLE-READ的情况下正常工作,无论您的MySQL是(默认)还是READ-COMMITTED事务隔离模式.
以非自动提交模式运行的以前版本的Django中发生的事情是第一个select语句打开了一个事务.由于MySQL的默认模式是REPEATABLE-READ这意味着后续select语句不会读取数据库的更新- 因此需要flush_transaction()上面的代码来停止事务并启动新事务.
但是仍然有可能为什么要使用READ-COMMITTED事务隔离.如果您要将终端1置于事务中,并且您希望查看终端2的写入,则需要READ-COMMITTED.
该flush_transaction()代码现在生产在Django 1.6产生警告,所以我建议你删除它.
我们在迫使django刷新"缓存"方面遇到了一些麻烦 - 事实证明,这不是真正的缓存,而是由于交易造成的工件.这可能不适用于您的示例,但肯定在django视图中,默认情况下,会对事务进行隐式调用,然后mysql会从您启动的其他进程中发生的任何更改中隔离出来.
我们使用@transaction.commit_manually装饰器并transaction.commit()在每个需要最新信息的场合之前调用.
正如我所说,这绝对适用于视图,不确定它是否适用于不在视图中运行的django代码.
详细信息在这里:
http://devblog.resolversystems.com/?p=439
好像count()是第一次进入缓存之后.这是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)
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
我不确定我会推荐它......但你可以自己杀死缓存:
>>> qs = MyModel.objects.all()
>>> qs.count()
1
>>> MyModel().save()
>>> qs.count() # cached!
1
>>> qs._result_cache = None
>>> qs.count()
2
Run Code Online (Sandbox Code Playgroud)
这里有一个更好的技术,不依赖于摆弄QuerySet的内部:请记住,缓存是在QuerySet中发生的,但刷新数据只需要重新执行底层的Query.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()
Run Code Online (Sandbox Code Playgroud)
满容易!您当然可以将其实现为辅助函数并根据需要使用.
| 归档时间: |
|
| 查看次数: |
35049 次 |
| 最近记录: |