Django缓存查询后取对象属性是否触库?缓存框架使用咨询
嘿,我来帮你把这两个Django缓存相关的问题讲清楚,都是很容易踩的坑~
问题1:从缓存的查询结果中请求对象属性时,Django会访问数据库吗?
答案是不一定,核心取决于你缓存的内容:
- 如果你缓存的是未执行的QuerySet(比如直接缓存
Model.objects.all()):这时候缓存的只是查询的“蓝图”,并没有实际从数据库拉取数据。当你后续访问对象属性时,QuerySet才会被触发执行,这时候就会访问数据库。 - 如果你缓存的是已经执行过的查询结果(比如转成列表的
list(Model.objects.all())、values()/values_list()返回的数据,或者序列化后的对象):数据已经存在内存里了,访问属性不会触发数据库查询。
问题2:你的代码示例中,访问
list[0].title会触发数据库查询吗? 会的!问题出在你的缓存逻辑上。
先看你的代码:
@classmethod def get_all_books(cls): query = cache.get_or_set('all_books', cls.objects.all()) return query
这里你缓存的是cls.objects.all()这个QuerySet对象,而不是实际的书籍数据。Django的QuerySet是惰性执行的——只有当你真正需要用到数据的时候(比如转成列表、取索引、遍历它),它才会去数据库执行查询。
所以你在shell里的执行流程是:
list = Book.get_all_books():从缓存拿到QuerySet,但此时还没查数据库。list[0].title:这时候触发了QuerySet的执行,它会去数据库拉取所有书籍数据,再返回第一个对象的title。
更糟的是,哪怕你缓存了这个QuerySet,每次后续触发它执行时,都会重新去数据库查询——因为QuerySet本身只是查询模板,不会缓存数据。
怎么修正?
你需要缓存实际的查询结果,而不是QuerySet,有两种简单的实现方式:
方式1:手动执行QuerySet再缓存
@classmethod def get_all_books(cls): books = cache.get('all_books') if not books: # 用list()触发QuerySet执行,拿到实际的对象列表 books = list(cls.objects.all()) cache.set('all_books', books) return books
方式2:用get_or_set的回调函数
get_or_set的第二个参数如果是可调用对象(比如lambda函数),会在缓存不存在时执行这个函数,把返回结果存入缓存,这样能确保缓存的是执行后的结果:
@classmethod def get_all_books(cls): # 用lambda包裹查询,确保缓存的是执行后的列表 books = cache.get_or_set('all_books', lambda: list(cls.objects.all())) return books
额外提醒:处理外键的N+1问题
如果你的Book模型有外键(比如示例里的author),哪怕你缓存了Book列表,默认访问book.author.username时还是会触发额外的数据库查询(因为author没有被预取)。建议查询时加上select_related(针对一对一/外键)或prefetch_related(针对多对多),再缓存结果:
books = list(cls.objects.all().select_related('author'))
内容的提问来源于stack exchange,提问作者Amin Hemati Nik
相关产品推荐
相关产品推荐

