慢查询主因是使用不当而非ORM本身,核心问题包括SELECT *、N+1循环、缺索引和未分页;应改用select_related/prefetch_related预加载、添加db_index、只取必要字段并分页处理。
慢查询不是orm的锅,而是你没告诉它“怎么查”——绝大多数情况,select *、n+1循环、缺索引、没分页,四者占了线上慢查询的90%以上。
为什么for obj in Model.objects.all()一跑就卡住
这不是代码写得“直观”,是触发了最典型的惰性求值陷阱:每次访问外键或反向关系时,Django 都会单独发一条 SQL。比如遍历 100 个 Product 并读取 product.category.name,就会执行 101 次查询(1 次主表 + 100 次分类表)。
- 别在循环里点外键属性,比如
obj.foreign_key_field.name - 一对一或外键关联,用
select_related('field_name')—— 它生成JOIN,一次查完 - 多对多或反向外键(如
author.book_set.all()),必须用prefetch_related('book_set')—— 它分两步查,但避免 N+1 -
select_related只接受外键/一对一字段;prefetch_related支持字符串、Prefetch对象、甚至带queryset的精细控制
为什么加了 filter() 还是全表扫描
因为数据库根本没走索引。Django 的 filter(title__icontains='xxx') 或 filter(created_at__gt=...) 如果对应字段没建索引,MySQL 就只能扫全表——哪怕只有 1 万行,也可能耗时 300ms+。
- 外键字段默认不自动建索引,务必手动加
db_index=True(如category = models.ForeignKey(..., db_index=True)) - 高频
WHERE条件字段(status、is_active、type)都该加单列索引 - 联合查询(如
.filter(category=..., created_at__gte=...))优先建复合索引:indexes = [models.Index(fields=['category', 'created_at'])] - 用
EXPLAIN看执行计划:如果type是ALL,说明没命中索引
为什么 Model.objects.all() 返回几百MB内存还超时
你不是在查数据,是在把整张表拖进 Python 进程。尤其当模型含 TextField、JSONField 或大量字段时,all() 会加载全部列、全部行、全部对象实例,内存和网络开销爆炸。
- 永远不要对非小表用
all();改用values()或values_list()获取原始数据结构 - 只取需要的字段:
Product.objects.only('id', 'name', 'price')—— 大字段(如description)会被延迟加载 - 大数据集必分页:
Paginator(queryset, per_page=20);禁用[offset:limit]做深分页,游标分页更稳 - 导出类场景,用
iterator(chunk_size=2000)流式读取,避免内存溢出
为什么本地快、线上慢,且 django.db.connection.queries 显示 SQL 很简单
问题可能不在 ORM 层,而在连接本身。高并发下,每次请求新建 MySQL 连接会导致 TCP 握手 + 认证 + 网络延迟叠加,平均多耗 50–200ms。
立即学习“Python免费学习笔记(深入)”;
- Django 默认不带连接池,
CONN_MAX_AGE=0表示每次请求后关闭连接 - 生产环境应设
CONN_MAX_AGE=60复用连接,或引入django-db-geventpool等第三方连接池 - 检查 MySQL 的
max_connections是否被耗尽,SHOW PROCESSLIST看是否有大量Sleep连接堆积 - 确认是否启用了
pool_pre_ping=True(SQLAlchemy)或等效健康检测,避免拿到失效连接
真正卡住你的,往往不是某一行 ORM 代码,而是它背后没显式声明的依赖:索引是否存在、连接是否复用、结果集是否可控。优化不是“改写法”,而是补上这些隐性契约。


















