Python中Django ORM QuerySet lazy loading(懒加载) 是如何实现的?原理为何?
比如说 books = Book.objects.all()
这一行代码并不会在运行时就取出所有数据
而是要到真正使用 books 时才会去取出数据
怎么做到的呢?原理是什么?
Python中Django ORM QuerySet lazy loading(懒加载) 是如何实现的?原理为何?
可能是魔术方法。
Django ORM的QuerySet懒加载是通过延迟执行数据库查询来实现的。核心原理是:当你创建QuerySet时(比如User.objects.all()),它并不会立即访问数据库,而是等到你真正需要数据时(比如遍历、切片或调用list())才会执行SQL查询。
具体实现机制:
-
Query类封装SQL:每个QuerySet内部有一个
Query对象,它负责构建SQL语句但不会立即执行 -
惰性求值链:链式调用(如
.filter().exclude().order_by())只是不断修改内部的Query对象 -
触发执行的时机:
- 迭代QuerySet时(
for user in queryset:) - 切片非0开始的索引时(
queryset[5:10]) - 调用
len(),list(),bool()等方法时 - 序列化时
- 迭代QuerySet时(
-
缓存机制:首次执行查询后,结果会被缓存在QuerySet内部,避免重复查询
看个代码示例就明白了:
# 创建QuerySet - 此时没有数据库查询
users = User.objects.filter(is_active=True).order_by('-date_joined')
print(users.query) # 只打印SQL语句,不执行
# 触发查询的几种方式:
# 1. 迭代
for user in users: # 这里才执行SELECT查询
print(user.username)
# 2. 转换为列表
active_users = list(users) # 如果上面已执行,这里用缓存
# 3. 切片(注意:queryset[0]和queryset[:5]会触发查询)
first_five = users[:5] # 执行带LIMIT的查询
# 验证缓存
print(users._result_cache) # 查看缓存的结果集
关键实现代码在django/db/models/query.py的QuerySet类中,_fetch_all()方法是触发实际查询的入口点。这种设计让开发者可以灵活构建复杂查询而不产生不必要的数据库开销。
简单说就是:不真正用数据就不查数据库。
Django 不熟,是异步吗 ……
没看过源码,我想到的一种可能实现是:
1、all()只是编译好了 sql,但没执行
2、orm object 的__iter__()方法才是真执行 sql
大致逻辑:
class ORMObject(object):
def all(self):
self.sql = …
return self
def filter(self, condition):
self.sql = …
return self
def iter(self):
self.execute()
哇 v 站评论不支持 markdown 也就罢了,还自动删减空格…
楼上正解, 在执行这些魔术方法的时候去执行的 sql 并生成缓存:
Iteration, ie. 对 Queryset 进行 For 循环的操作.
slicing, e.g. Entry.objects.all()[:5], 获取 queryset 中的前五个对象, 相当于 sql 中的 LIMIT 5
picling/caching
repr/str
len (Note: 如果你只想知道这个 queryset 结果的长度的话, 最高效的还是在数据库的层级调用 count()方法, 也就是 sql 中的 COUNT(). )
list()
bool()
但调 queryset[5], values_list(), iterator()也会触发执行 sql, 但没有 cache 生成。
参考我的笔记: https://changchen.me/blog/20170503/django-performance-and-optimisation/

