Django QuerySet 懒加载与缓存机制源码级拆解文档

Django QuerySet 懒加载与缓存机制源码级拆解文档

(基于 Django 4.2.7 + CPython 3.11)


1 阅读指引

  • 目标:彻底讲清"为什么第一次 for article in articles: 才会真正发 SQL"
  • 深度:从 Python for 字节码 → Django 源码 → 数据库驱动调用栈
  • 用法:可复制到内部 Wiki 或直接发博客,代码块均可运行

2 一句话结论

QuerySet 只在第一次被迭代 时才会拼 SQL、访问数据库;

之后无论再 forlen()bool()list() 都直接读内存缓存。

这是通过"Python 迭代协议 → QuerySet.iter() → _fetch_all()"链式触发实现的。


3 总览时序图

scss 复制代码
for article in articles
    ├─ articles.__iter__()          # QuerySet 级别
    │   ├─ self._fetch_all()        # 懒加载闸门
    │   │   ├─ self._result_cache = list(self.iterator())
    │   │   │   └─ self.iterator()  # 拼 SQL + 调驱动
    │   │   │       ├─ compiler.as_sql()
    │   │   │       └─ cursor.execute(sql, params)
    │   │   └─ 行→Model 实例
    │   └─ 返回 iter(self._result_cache)
    └─ next() 拿实例

( gates:只有 _fetch_all()self._result_cache is None 才会走数据库)


4 环境准备

bash 复制代码
git clone https://github.com/django/django.git
cd django && git checkout 4.2.7
pip install -e .

5 源码拆解

5.1 入口:QuerySet.iter

文件:django/db/models/query.py #L394

python 复制代码
class QuerySet:
    def __iter__(self):
        self._fetch_all()           # ① 保证数据已加载
        return iter(self._result_cache)  # ② 返回列表迭代器

5.2 闸门:_fetch_all()

文件:同文件 #L1903

python 复制代码
def _fetch_all(self):
    if self._result_cache is None:     # 仅第一次为 None
        self._result_cache = list(self.iterator())
  • 之后无论 len()bool()list() 都直接读 self._result_cache
  • list(self.iterator()) 会一次性把数据库行读进内存

5.3 真正拼 SQL:iterator()

文件:同文件 #L368

python 复制代码
def iterator(self, chunk_size=2000):
    compiler = self.query.get_compiler(using=self.db)
    for row in compiler.results_iter(chunk_size=chunk_size):
        yield self.model.from_db(self.db, self.model._meta.fields, row)
  • compiler.results_iter()cursor.execute(sql, params) # 见下节

5.4 数据库调用点

文件:django/db/models/sql/compiler.py #L1511

python 复制代码
def results_iter(self, chunk_size=None):
    cursor = self.connection.cursor()
    cursor.execute(sql, params)          # ← 真正发 SQL
    for rows in self.cursor_iter(cursor, chunk_size):
        for row in rows:
            yield row

6 Python 层:for 怎么调到 iter

CPython 字节码

scss 复制代码
GET_ITER        # 等价于 iter(obj) → obj.__iter__()
FOR_ITER        # 不断 next(it)

GET_ITER 源码 (Python/ceval.c) 固定走 PyObject_GetIter → tp_iter → __iter__

因此任何可迭代对象 只要实现 __iter__ 就会被 for 触发;

QuerySet 正是利用这一点把"迭代"挂钩到 _fetch_all()


7 缓存验证实验

python 复制代码
from django import db
from blog.models import Article

articles = Article.objects.filter(status='published')
print(articles._result_cache)   # None

for a in articles:              # 第一次迭代
    pass
print(len(db.connection.queries))  # 1 条 SQL
print(articles._result_cache)   # [<Article:...>, ...]

for a in articles:              # 第二次迭代
    print(a.title)              # 不再发 SQL
print(len(db.connection.queries))  # 仍是 1 条

8 常见误区速查表

操作 是否立即查库 备注
Article.objects.all() 只创建 QuerySet
qs.filter(...) 返回新 QuerySet
qs.order_by(...) 同上
qs[10] 切片会触发 _fetch_all
bool(qs) 会调用 __bool__→_fetch_all
len(qs) 会调用 __len__→_fetch_all
list(qs) 直接 list() 会迭代
for x in qs: 第一次迭代
再次 for x in qs: 用缓存

9 小结口诀

QuerySet 是"惰性链表 "------

链上每一步都只是"记录条件",
直到第一次迭代才把整个链编译成 SQL 拉进内存

拉完后结果装进 _result_cache

往后任何姿势的遍历都不再打扰数据库

相关推荐
源码之屋8 小时前
计算机毕业设计:Python天气数据采集与可视化分析平台 Django框架 线性回归 数据分析 大数据 机器学习 大模型 气象数据(建议收藏)✅
人工智能·python·深度学习·算法·django·线性回归·课程设计
架构师老Y10 小时前
003、Python Web框架深度对比:Django vs Flask vs FastAPI
前端·python·django
暴力袋鼠哥1 天前
基于 Django 与 Vue 的汽车数据分析系统设计与实现
vue.js·django·汽车
360智汇云1 天前
PostgreSQL 全文检索深度指南:内置 FTS、zhparser 与 pg_search 全解
postgresql·django·全文检索
leo_messi942 天前
2026版商城项目(三)-- ES+认证服务
后端·python·django
毕胜客源码2 天前
改进yolov8的香蕉成熟度检测系统,改进前后的模型指标对比,有技术文档,支持图像、视频和摄像实时检测
人工智能·python·深度学习·yolo·django
Coding茶水间3 天前
基于深度学习的草莓健康度检测系统演示与介绍(YOLOv12/v11/v8/v5模型+Django+web+训练代码+数据集)
人工智能·深度学习·yolo·机器学习·django
源码之屋4 天前
计算机毕业设计:Python出行数据智能分析与预测平台 Django框架 可视化 数据分析 PyEcharts 交通 深度学习(建议收藏)✅
人工智能·python·深度学习·数据分析·django·汽车·课程设计
vx_biyesheji00014 天前
计算机毕业设计:Python网约车订单数据可视化系统 Django框架 可视化 数据大屏 数据分析 大数据 机器学习 深度学习(建议收藏)✅
大数据·python·机器学习·信息可视化·django·汽车·课程设计
I love studying!!!4 天前
Web项目:从Django入手
后端·python·django