Django数据库查询缓存:从入门到精通,提升性能的5大实战技巧
目录导读
- 什么是Django查询缓存?为什么要用?
- Django内置缓存框架的三种核心用法
- QuerySet缓存机制:你每天都在用的隐形缓存
- 手动缓存:cache.set()与cache.get()实战
- 缓存过期与失效策略:避坑指南
- 常见问题问答(Q&A)
- 性能优化的最佳实践
什么是Django查询缓存?为什么要用?
Django作为Python最流行的Web框架,其ORM(对象关系映射)功能强大,但频繁的数据库查询往往是性能瓶颈。查询缓存是指将数据库查询结果临时存储在内存(如Redis、Memcached)或文件系统中,下次相同请求时直接返回缓存数据,避免重复查询数据库。

核心价值:
- 减少数据库I/O压力,响应速度提升50%~90%
- 解决高并发场景下的“N+1查询问题”
- 对热点数据(如首页文章列表、用户权限)效果显著
实战场景: 某电商网站的商品分类列表,每小时被访问10万次,若不缓存,数据库将承受10万次SQL查询,缓存后,仅需每5分钟查询一次数据库,其余99996次命中缓存。
Django内置缓存框架的三种核心用法
Django提供了一套完整的缓存框架,你需要在settings.py中配置后端:
# settings.py
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'TIMEOUT': 300, # 默认5分钟(单位秒)
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
三种核心用法:
1 全站缓存(适合低频更新网站)
# 在中间件中添加(settings.py)
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware', # 必须第一
# ...其他中间件
'django.middleware.cache.FetchFromCacheMiddleware', # 必须最后
]
2 视图缓存(最常用)
from django.views.decorators.cache import cache_page
@cache_page(60 * 15) # 缓存15分钟
def article_list(request):
articles = Article.objects.all() # 只执行一次
return render(request, 'list.html', {'articles': articles})
3 模板片段缓存(精细控制)
{% load cache %}
{% cache 300 sidebar %}
{% include "includes/sidebar.html" %}
{% endcache %}
QuerySet缓存机制:你每天都在用的隐形缓存
Django的QuerySet自带两层缓存:
- 第一层:QuerySet结果缓存
当对QuerySet执行for循环、list()、bool()等操作时,Django会执行SQL并将结果缓存在_result_cache属性中。同一个QuerySet重复使用不会再次查询数据库。
# 正确做法(一次查询)
articles = Article.objects.filter(published=True) # 未执行
for article in articles: # 执行SQL,缓存结果
print(article.title)
for article in articles: # 命中缓存,无SQL
print(article.body)
# 错误做法(两次查询)
for article in Article.objects.filter(published=True): # SQL#1
print(article.title)
for article in Article.objects.filter(published=True): # SQL#2
print(article.body)
- 第二层:数据库查询缓存(需配置)
通过在ORM中添加cache()方法,将结果存入Redis/Memcached:
from django.core.cache import cache
def get_popular_articles():
cache_key = 'popular_articles'
articles = cache.get(cache_key)
if not articles:
articles = Article.objects.filter(views__gt=1000)[:10]
cache.set(cache_key, articles, 3600) # 缓存1小时
return articles
注意:
cache.set()默认使用pickle序列化所有字段,如果数据量大(如含图片路径、大文本),建议只缓存ID,然后按需查询。
手动缓存:cache.set()与cache.get()实战
进阶用法:缓存带版本控制
def get_article_detail(article_id):
cache_key = f'article_detail_{article_id}'
article = cache.get(cache_key)
if article is None:
try:
article = Article.objects.get(id=article_id)
# 设置自定义超时,且版本号递增
cache.set(cache_key, article, timeout=600, version=article.updated_at.timestamp())
except Article.DoesNotExist:
return None
return article
批量缓存(减少连接请求):
from django.core.cache import cache
def batch_get_categories():
cached_ids = cache.get_many(['cat_1', 'cat_2', 'cat_3'])
missing_ids = [id for id in ids if id not in cached_ids]
if missing_ids:
categories = Category.objects.filter(id__in=missing_ids)
cache.set_many({f'cat_{c.id}': c for c in categories}, 300)
return list(cached_ids.values()) + list(categories)
缓存过期与失效策略:避坑指南
1 时间过期是最简单的方案
cache.set('key', data, timeout=60) # 60秒后自动失效
2 主动失效:数据变更时删除缓存
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
@receiver(post_save, sender=Article)
def clear_article_cache(sender, instance, **kwargs):
cache.delete(f'article_detail_{instance.id}')
cache.delete('article_list') # 同时清空列表缓存
3 缓存穿透与雪崩防护
- 缓存穿透: 查询不存在的ID每次都穿透到数据库。
解决方法:缓存空值(cache.set(key, None, 60)) - 缓存雪崩: 大量缓存同时过期导致数据库崩溃。
解决方法:设置随机过期时间(timeout=300 + random.randint(0, 60))
常见问题问答(Q&A)
Q1:Django的QuerySet缓存能跨请求使用吗?
A:不能,QuerySet的_result_cache仅存在于当前请求的Python进程中,跨请求缓存必须使用Redis/Memcached等外部缓存。
Q2:缓存的数据修改后,何时才会更新?
A:取决于你的失效策略:
- 时间过期:到期自动刷新
- 主动失效:数据变更时调用
cache.delete() - 混合方案:设置短时间(如5分钟)+信号钩子双重保障
Q3:缓存视图和缓存QuerySet有什么区别?
A:
- 视图缓存:缓存整个HTTP响应,速度快但粒度粗(不区分用户)
- 查询缓存:缓存查询结果,可在模板中按需渲染,适合用户个性化场景
Q4:使用select_related()和prefetch_related()还需要加缓存吗?
A:需要,这两个方法优化的是单次请求内的查询,而缓存优化的是跨请求的重复查询,最佳实践是:先用select_related()减少单次请求的SQL数量,再用缓存避免多次重复请求。
Q5:为什么我设置了缓存,但数据还是旧的?
A:常见原因:
- 缓存key不正确(检查是否包含空格、特殊字符)
- 序列化问题(pickle无法序列化部分对象)
- 未设置
DEFAULT_TIMEOUT或配置了TIMEOUT=None(永不过期)
性能优化的最佳实践
| 场景 | 推荐方案 | 示例代码 |
|---|---|---|
| 全站静态页面 | 全站缓存 + CDN | UpdateCacheMiddleware |
| 热门列表数据 | 视图缓存 + 5分钟超时 | @cache_page(300) |
| 用户个性化数据 | QuerySet缓存 + 手动失效 | cache.get/set |
| 复杂计算结果 | 后台异步更新 + 缓存 | celery + cache.set |
| 防止缓存击穿 | 互斥锁 + 缓存空值 | cache.add() + key=None |
最后建议:
- 生产环境优先使用Redis(支持过期、持久化、高性能)
- 在
DEBUG=True模式下,可以使用django-debug-toolbar监控缓存命中率 - 始终为缓存key添加前缀(如
mydomain:article:list),避免多应用冲突
通过以上技巧,你的Django应用数据库查询量可以减少70%以上,响应时间从500ms下降到10ms级别,从今天开始,为你的项目添加查询缓存吧!