9

我有两个 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有一个 ManyToMany 字段MyModel1有权model1

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

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

为什么?这似乎绝对是一个缓存问题,因为我看到数据库表 myapp_mymodel2_mymodel1 中有一个新条目用于m2&之间的链接m1。我该怎么修??

4

4 回答 4

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 获取第二个结果集,前提是缓存尚未失效。

相同的链接解释了缓存键是如何失效的。

为了支持简单的缓存失效,我们使用“刷新列表”来标记对象所属的缓存查询。这样,当该对象更改时,所有找到该对象的查询都将失效。刷新列表将对象键映射到查询键列表。

当一个对象被保存或删除时,其刷新列表中的所有查询键都将被删除。此外,其外键关系的刷新列表将被清除。为了避免过时的外键关系,当外键指向的对象失效时,任何缓存的对象都将被刷新。

很明显,保存或删除一个对象会导致缓存中的许多对象必须失效。因此,您正在使用缓存管理器减慢这些操作。另外值得注意的是,失效文档根本没有提到多对多的字段。对此有一个未解决的问题,从您对该问题的评论中可以清楚地看出您也发现了它。

解决方案

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

于 2016-06-19T05:11:48.570 回答
1

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

不能解决您的问题但将您之前提供的解决方法与我的信号解决方案方法相关联的小示例(我不知道 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)
于 2016-06-21T00:05:49.263 回答
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>]
于 2016-06-16T06:35:02.180 回答
0

AJ 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)
于 2019-05-17T18:47:44.637 回答