Django中的属性缓存 - 意义何在?

3 投票
2 回答
1742 浏览
提问于 2025-04-15 13:00

我刚刚查看了EveryBlock的源代码,发现了alerts/models.py文件中的这段代码:

    def _get_user(self):
    if not hasattr(self, '_user_cache'):
        from ebpub.accounts.models import User
        try:
            self._user_cache = User.objects.get(id=self.user_id)
        except User.DoesNotExist:
            self._user_cache = None
    return self._user_cache
    user = property(_get_user)

我注意到这种写法在很多地方都有,但我不太明白它的用法。这个做法的主要目的是确保在访问self(self是警报对象)上的外键时,只从数据库中获取一次用户对象吗?为什么不直接依赖数据库的缓存和Django的ForeignKey()字段呢?我发现模型定义中只保存了用户的ID,而没有外键字段:

class EmailAlert(models.Model):
     user_id = models.IntegerField()
     ...

任何见解都将不胜感激。

2 个回答

3

虽然数据库内部会缓存一些数据,但每次想要查看相关字段的值时,还是会有一些额外的开销。这包括在Django中设置查询、连接数据库时的网络延迟、通过网络返回数据、在Django中创建对象等等。如果你知道在这段时间内数据没有变化——而在一次网页请求的上下文中,你可能并不在意数据是否变化——那么一次性获取数据并缓存起来,明显比每次都去查询要更合理。

我参与的一个项目有一个非常复杂的主页,里面包含了大量的数据。之前这个主页需要进行超过400次数据库查询才能渲染出来。现在我对它进行了重构,减少到“仅仅”使用80次查询,采用了和你提到的类似的技术,你可以相信,这样做大大提升了性能。

4

我不明白为什么这个字段是一个整数类型;看起来它应该是一个外键(ForeignKey)指向用户(User)字段——这样的话,你就会失去一些功能,比如select_related()等。

关于缓存,很多数据库并不会缓存结果——它们(或者说,是操作系统)会缓存获取结果所需的数据,这样第二次查找时应该会比第一次快,但还是需要一些时间。

查找数据时,依然需要和数据库进行一次交互。根据我的经验,在Django中,查找一个项目大约需要0.5到1毫秒,这个时间是发给本地Postgresql服务器的SQL命令的时间,加上有时还会有一些额外的开销。1毫秒的时间如果不必要的话,其实是挺多的——这样做几次,就可能把一个30毫秒的请求变成35毫秒。

如果你的SQL服务器不在本地,实际上还需要处理网络延迟,那么这个时间会更长。

最后,人们通常希望访问某个属性的速度是快的;当这些操作复杂到需要进行SQL查询时,缓存结果通常是个好主意。

撰写回答