Django Querysets + Memcached:最佳实践

18

试图理解django低级缓存设置(cache.set())的过程,特别是关于哪部分查询集(queryset)存储在memcached中的详细信息。

首先,我是否正确理解了django文档?

  • 查询集(Python对象)具有/维护自己的缓存
  • 访问数据库是惰性的;即使查询集.count为1000,如果我只为1条记录做一个object.get,则数据库仅被访问一次,即用于该1条记录。
  • 当通过apache prefork MPM访问django视图时,每次特定守护进程实例X调用包括"tournres_qset = TournamentResult.objects.all()"等内容的特定视图时,都会导致每次创建新的tournres_qset对象。也就是说,任何可能已经在前面(tcp/ip)访问中由tournres_qset Python对象内部缓存的东西,都不会被新请求的tournres_qset使用。

现在关于在视图中将事物保存到memcached的问题。假设我在视图顶部添加了这样的内容:

tournres_qset = cache.get('tournres', None)
if tournres_qset is None:
    tournres_qset = TournamentResult.objects.all()
    cache.set('tournres', tournres_qset, timeout)
# now start accessing tournres_qset
# ...

cache.set()会存储什么?

  • 整个查询集(Python对象)是否被序列化并保存?

  • 由于查询集尚未用于获取任何记录,因此这只是浪费时间,因为实际上没有保存任何特定记录的内容在memcache中 (任何未来的请求都将从memcache中获取查询集对象,该对象始终从空的本地查询集缓存开始;访问数据库将始终发生。)

  • 如果以上内容属实,那么在视图结束时,我应该重新保存查询集吗?这样它就可以在整个视图中被使用以访问一些记录,这将导致查询集的本地缓存得到更新,并且应该始终重新保存到memcached中?但是这将再次导致序列化查询集对象。所以加速事情变得更糟。

  • 还是说cache.set()强制查询集对象迭代并从数据库中访问所有记录,这也会保存在memcache中?所有内容都将被保存,即使视图仅访问一部分查询集?

我看到了各种陷阱,这让我认为我可能误解了很多事情。

希望这有意义,感谢澄清或指向一些“标准”指南。 谢谢。

1个回答

27
查询集(Querysets)是惰性的,这意味着只有在被评估之前它们才会调用数据库。它们被评估的一种方式是对它们进行序列化,这正是cache.set背后所做的。因此,不,这不是浪费时间:如果您想要缓存整个Tournament模型的内容,则将被缓存。但这可能并不是您想要的,如果您进一步筛选查询集,则Django将返回到数据库,这将使整个过程变得有点毫无意义。您应该只缓存实际需要的模型实例。
请注意,初始设置中的第三点并不完全正确,因为这与Apache或preforking无关。它只是一个视图就像任何其他函数一样,函数内部定义的所有本地变量在该函数返回时都会失效。因此,在视图内定义和计算的查询集在视图返回响应时将失效,并且下次调用视图时将创建一个新的查询集,即在下一次请求时。无论您使用哪种方式提供Django,这都是适用的。
然而,这很重要,如果您将查询集设置为全局(模块级别)变量,它将在请求之间保持持久状态。 Django的大多数服务方式(包括mod_wsgi)在重复利用之前会让进程存活许多请求,因此查询集的值将对所有这些请求保持不变。这可以作为一种廉价的缓存,但很难做到正确,因为您无法知道进程会持续多长时间,而且其他并行运行的进程可能具有其自己版本的全局变量。
更新以回答评论中的问题。你的问题表明你还没有完全理解查询集的工作原理。关键在于它们何时被评估:如果你列出、迭代或切片一个查询集,那么就会对其进行求值,并且此时会进行数据库调用(我认为在这里将序列化视同迭代),并将结果存储在查询集的内部缓存中。因此,如果你已经对查询集执行了其中一种操作,然后将其设置到(外部)缓存中,那不会导致另一个数据库请求。
但是,即使在已经评估过的查询集上的每个filter()操作都是另一个数据库请求。这是因为它修改了基础SQL查询,所以Django将返回一个新的查询集,并具有自己的内部缓存。

Daniel,非常好的回答,感谢你花时间解释我问题中的一些要点。有几个后续问题。首先确认:每当对queryset执行cache.set()时,这将触发一个在幕后查询queryset中所有记录的数据库,无论视图是否使用了所有记录。第二个后续问题,在进行cache.get并返回queryset后,为什么过滤queryset会导致重新访问数据库?难道不是所有的记录都已经可用吗?谢谢。 - jd.
1
Daniel,我知道这是一个老问题,但你提到了一个重要的点,即我错过了关于后续filter()调用再次访问数据库的问题。谢谢!在那些反复调用queryset上的filter将导致大量额外查询的情况下,您是否发现手动使用Python进行过滤是值得的? - B Robster
3
在这里补充一点,如果你想避免.filter()重新向数据库查询,那么可以使用Python的过滤器而不是Django ORM的过滤器。因此,可以像这样做:new_queryset = filter(lambda x: x.field == True, cached_queryset)。这样就不会再次查询数据库。 - awidgery

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接