数据库查询必须用 select_related 和 prefetch_related,前者用于外键关联(一对一/一对多)生成 join 查询,后者用于多对多或反向外键执行额外 select 后内存拼接,避免 n+1 查询。

数据库查询必须用 select_related 和 prefetch_related
博客首页加载文章列表时,如果每篇文章都要查作者、分类、标签,不加优化就会产生 N+1 查询——1 次查文章,N 次分别查关联对象。页面加载慢、数据库压力大,这是最常见也最容易被忽略的性能坑。
实际做法是:在 ListView 的 get_queryset 方法里主动预取:
-
select_related('author', 'category')用于外键(一对一/一对多),生成 JOIN 查询 -
prefetch_related('tags')用于多对多或反向外键,执行额外的 SELECT 后内存拼接 - 不要在模板里写
{{ post.author.username }}就完事——那会触发懒加载,每个 post 都单独查一次 - 如果用了
Tag的自定义字段(比如is_hot),prefetch_related默认不带这些字段,得配合Prefetch显式指定
Django Admin 界面别直接用默认配置
默认的 admin 页面在文章数量超过 500 条后,打开 /admin/blog/post/ 就卡顿甚至超时。这不是服务器问题,而是 admin 默认把所有字段、所有关联对象都拉出来渲染了。
关键调整项:
- 在
PostAdmin中用list_display只放真正需要展示的字段,比如('title', 'author', 'category', 'pub_date', 'views') - 禁用全量搜索:
search_fields = ['title', 'content']改成只搜标题和摘要,避免全文扫描 - 加过滤器但限制范围:
list_filter = ('category', 'pub_date'),别加tags——多对多字段过滤会拖慢 SQL - 启用分页:
list_per_page = 50,防止一次加载太多 DOM 节点
静态文件和缓存必须分离部署
开发时用 Django 自带的 runserver 提供 /static/ 没问题,但上线后它根本扛不住并发请求。浏览器反复请求 CSS/JS 图片,Django 进程全在干 I/O,CPU 利用率虚高,响应延迟飙升。
真实可行的做法:
- 用
python manage.py collectstatic把所有静态资源归到STATIC_ROOT目录 - Nginx 直接 serve
/static/和/media/路径,完全绕过 Django - 缓存必须用 Redis,别用文件缓存或本地内存——多进程下不共享,失效逻辑混乱
- 对文章详情页做页面级缓存:
@cache_page(60 * 15),但记得在save()时调用cache.delete()清对应 key
URL 设计要避开 pk,优先用 slug
用 /article/123/ 看似简单,但带来两个硬伤:一是 SEO 不友好,二是无法做内容级缓存(同一 URL 下内容变了,缓存却没失效)。
正确路径结构应为 /article/<code>slug/,且需满足:
-
slug字段设unique=True,并在保存前用slugify(title)自动生成 - URL pattern 写成
path('article/<slug>/', ArticleDetailView.as_view(), name='article_detail')</slug> - 视图里用
get_object_or_404(Post, slug=slug),而不是先查再判断——减少一次 DB 查询 - 如果文章改标题,旧
slug必须 301 重定向到新地址,否则搜索引擎收录会断链
缓存键设计、数据库索引字段选择、以及 slug 更新后的重定向逻辑,这三处最容易在线上跑一段时间后才暴露问题——不是代码报错,而是流量一上来就变慢,排查起来特别费时间。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











