Django数据库查询缓存怎么用

wen python案例 2

Django数据库查询缓存:从入门到精通,提升性能的5大实战技巧

目录导读

  1. 什么是Django查询缓存?为什么要用?
  2. Django内置缓存框架的三种核心用法
  3. QuerySet缓存机制:你每天都在用的隐形缓存
  4. 手动缓存:cache.set()与cache.get()实战
  5. 缓存过期与失效策略:避坑指南
  6. 常见问题问答(Q&A)
  7. 性能优化的最佳实践

什么是Django查询缓存?为什么要用?

Django作为Python最流行的Web框架,其ORM(对象关系映射)功能强大,但频繁的数据库查询往往是性能瓶颈。查询缓存是指将数据库查询结果临时存储在内存(如Redis、Memcached)或文件系统中,下次相同请求时直接返回缓存数据,避免重复查询数据库。

Django数据库查询缓存怎么用

核心价值:

  • 减少数据库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

最后建议:

  1. 生产环境优先使用Redis(支持过期、持久化、高性能)
  2. DEBUG=True模式下,可以使用django-debug-toolbar监控缓存命中率
  3. 始终为缓存key添加前缀(如mydomain:article:list),避免多应用冲突

通过以上技巧,你的Django应用数据库查询量可以减少70%以上,响应时间从500ms下降到10ms级别,从今天开始,为你的项目添加查询缓存吧!

抱歉,评论功能暂时关闭!