最快见效的缓存手段是cache_page或cache.get_or_set(),但需避开用户态数据误缓存、并发重建打崩db、外键未展开致失效三大雷区。

直接用 cache_page 或 cache.get_or_set() 是最快见效的手段,但必须绕开三个高频雷区:用户态数据误缓存、并发重建打崩 DB、外键字段未展开导致缓存失效。
cache_page 装饰器为什么对登录页会出事
它只适合无状态页面——比如商品分类页、帮助文档、轮播图接口。一旦视图里出现 request.user、request.session 或基于用户角色渲染的内容,就绝对不能用 @cache_page。
- 现象:未登录用户看到已登录用户的购物车入口,或管理员看到普通用户的权限按钮
- 根本原因:Django 默认不区分请求上下文,同一 URL 的响应被全量缓存并复用
- 类视图要用
@method_decorator(cache_page(300))包裹get方法,而不是加在类定义上 - 如果前端用了 CDN 或 Nginx 缓存,必须手动加
Cache-Control: public, max-age=300响应头,否则cache_page可能被跳过
cache.get_or_set() 怎么避免“雪崩式重建”
if not cache.get('key'): cache.set('key', expensive_call()) 在高并发下会同时触发几十次 expensive_call(),数据库瞬间被打满;而 cache.get_or_set() 是原子操作,底层由 Redis/Memcached 保证只有一个线程执行回调函数。
- 别传
QuerySet实例:cache.get_or_set('books', Book.objects.all(), 300)会立刻求值,失去惰性;正确写法是cache.get_or_set('books', lambda: list(Book.objects.all()), 300) - 超时值要按业务节奏设:静态页可设
3600(1 小时),库存类建议 ≤60(1 分钟) - 计算成本极高时(如全表聚合),可在 lambda 里先用
cache.add('lock_key', '1', timeout=10)加锁,但优先走get_or_set,它已内置防击穿逻辑
为什么缓存了 Book 对象却还在查 Author 表
因为只缓存了 ORM 实例或 book.author_id,访问 book.author.name 时仍会触发新查询——缓存等于白做。
- 正确做法:把外键字段一并展开成 dict,例如
{'id': b.id, 'title': b.title, 'author_name': b.author.name, 'category_name': b.category.name} - 预加载必须用
select_related('author', 'category')或prefetch_related('tags'),再转成字典缓存;绝不要缓存原始Book实例 - 更新时必须清理缓存:在
Book或Author的post_save信号中,用带dispatch_uid的接收器调用cache.delete('books'),否则脏数据会长期存在
真正难的不是怎么设缓存,而是判断哪条数据该缓存、缓存到什么粒度、以及什么时候该删——这些没法靠装饰器自动解决,得结合业务变更频率和一致性要求一条条对齐。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











