为什么Django会返回过时的缓存数据?

时间:2016-06-16 03:08:59

标签: python django django-cache django-cache-machine

我有两个Django模型,如下所示,MyModel1& MyModel2

class MyModel1(CachingMixin, MPTTModel):
    name = models.CharField(null=False, blank=False, max_length=255)
    objects = CachingManager()

    def __str__(self):
        return "; ".join(["ID: %s" % self.pk, "name: %s" % self.name, ] )

class MyModel2(CachingMixin, models.Model):
    name = models.CharField(null=False, blank=False, max_length=255)
    model1 = models.ManyToManyField(MyModel1, related_name="MyModel2_MyModel1")
    objects = CachingManager()

    def __str__(self):
        return "; ".join(["ID: %s" % self.pk, "name: %s" % self.name, ] )

MyModel2有一个名为MyModel1的{​​{1}}的ManyToMany字段

现在看看当我向这个ManyToMany字段添加一个新条目时会发生什么。据Django说,它没有效果:

model1

为什么呢?它似乎绝对是一个缓存问题,因为我看到数据库表myapp_mymodel2_mymodel1中有一个新条目,用于>>> m1 = MyModel1.objects.all()[0] >>> m2 = MyModel2.objects.all()[0] >>> m2.model1.all() [] >>> m2.model1.add(m1) >>> m2.model1.all() [] &之间的此链接。 m2。我应该怎么解决?

4 个答案:

答案 0 :(得分:9)

真的需要django-cache-machine吗?

MyModel1.objects.all()[0]

大致翻译为

SELECT * FROM app_mymodel LIMIT 1

这样的查询总是很快。无论是从缓存还是从数据库中获取速度,速度都不会有显着差异。

当你使用缓存管理器时,你实际上在这里增加了一些开销,这可能会使事情变得有点慢。大多数情况下,这种努力将被浪费,因为可能没有缓存命中,如下一节所述。

django-cache-machine如何工作

  

每当您运行查询时,CachingQuerySet都会尝试查找该查询   在缓存中。查询由{prefix}:{sql}键控。如果它在那里,我们   返回缓存的结果集,每个人都很高兴。如果查询不是   在缓存中,执行运行数据库查询的正常代码路径。   随着结果集中的对象被迭代,它们被添加到   一旦迭代完成就会缓存的列表。

来源:https://cache-machine.readthedocs.io/en/latest/

因此,在您的问题中执行的两个查询相同的情况下,缓存管理器将从memcache获取第二个结果集,前提是缓存未被无效。

相同的链接解释了缓存密钥的无效方式。

  

为了支持轻松缓存失效,我们使用“刷新列表”来标记   对象所属的缓存查询。那样,所有的查询都在哪里   当该对象发生更改时,找到的对象将失效。红晕   列表将对象键映射到查询键列表。

     

保存或删除对象时,其刷新列表中的所有查询键   将被删除。另外,其外键的刷新列表   关系将被清除。为了避免陈旧的外键关系,任何   当对象的外键时,缓存的对象将被刷新   指向无效。

很明显,保存或删除对象会导致缓存中的许多对象无效。因此,您通过使用缓存管理器来减慢这些操作。另外值得注意的是,失效文档根本没有提到多个字段。对此有一个open issue,根据您对该问题的评论,很明显您也发现了它。

解决方案

查克缓存机。缓存所有查询几乎都不值得。它导致各种难以发现的错误和问题。最好的方法是优化表格并微调查询。如果您发现某个特定查询太慢,请手动缓存它。

答案 1 :(得分:1)

这是我的解决方案:

    >>> m1 = MyModel1.objects.all()[0]
    >>> m1
    <MyModel1: ID: 8887972990743179; name: my-name-blahblah>

    >>> m2 = MyModel2.objects.all()[0]
    >>> m2.model1.all()
    []
    >>> m2.model1.add(m1)
    >>> m2.model1.all()
    []

    >>> MyModel1.objects.invalidate(m1)
    >>> MyModel2.objects.invalidate(m2)
    >>> m2.save()
    >>> m2.model1.all()
    [<MyModel1: ID: 8887972990743179; name: my-name-blahblah>]

答案 2 :(得分:1)

您是否考虑过挂入模型信号以在添加对象时使缓存无效?对于您的情况,您应该查看M2M Changed Signal

没有解决问题的小例子,但workaround you gave before与我的信号解决方案方法有关(我不知道django-cache-machine):

def invalidate_m2m(sender, **kwargs):
    instance = kwargs.get('instance', None)
    action = kwargs.get('action', None)

    if action == 'post_add':
        Sender.objects.invalidate(instance)

m2m_changed.connect(invalidate_m2m, sender=MyModel2.model1.through)

答案 3 :(得分:0)

A。 J. Parr的回答是最正确的,但是您忘记了post_remove,也可以将其绑定到每个ManytoManyfield上,如下所示:

from django.db.models.signals import m2m_changed
from django.dispatch import receiver

@receiver(m2m_changed, )
def invalidate_cache_m2m(sender, instance, action, reverse, model, pk_set, **kwargs ):
    if action in ['post_add', 'post_remove'] :
        model.objects.invalidate(instance)