Django 缓存策略与性能优化

春秋大王2025-10-104 次阅读
Django 缓存策略与性能优化


Django 的缓存系统可将频繁访问的数据(如热门列表、首页内容)暂存到内存、Redis 等介质中,避免重复查询数据库,通常能使 API 响应时间从数百毫秒降至数十毫秒。以下是三种核心缓存策略的实现:​

(1)视图级缓存:缓存整个视图响应​

对于内容不常变化的视图(如热门商品列表、分类页面),使用@cache_page装饰器可直接缓存视图的返回结果,无需修改业务逻辑。

# 1. 配置缓存后端(settings.py)
# 生产环境推荐使用Redis,开发环境可使用本地内存
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',  # Redis地址(数据库1)
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
            'PASSWORD': 'your_redis_password',  # 若Redis有密码需配置
        },
        'TIMEOUT': 300,  # 默认缓存超时时间(秒),可在视图中覆盖
    }
}

# 2. 视图中应用缓存(views.py)
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Product

# 函数视图:缓存15分钟(900秒)
@cache_page(60 * 15)  # 缓存时间:秒数
def hot_products(request):
    """热门商品列表:15分钟更新一次"""
    # 复杂查询:若不缓存,每次请求都会执行该SQL
    hot_products = Product.objects.filter(
        is_hot=True
    ).select_related('category').order_by('-sales')[:10]
    
    data = [
        {
            'id': p.id,
            'name': p.name,
            'price': float(p.price),
            'category': p.category.name
        } for p in hot_products
    ]
    return Response({'code': 200, 'data': data})

# 类视图:使用method_decorator应用缓存
@method_decorator(cache_page(60 * 10), name='dispatch')  # 缓存10分钟
class CategoryListView(APIView):
    """商品分类列表:10分钟更新一次"""
    def get(self, request):
        from .models import Category
        categories = Category.objects.all()
        data = [{'id': c.id, 'name': c.name} for c in categories]
        return Response({'code': 200, 'data': data})

(2)对象级缓存:缓存单个数据对象​

对于频繁访问的单个对象(如用户信息、商品详情),使用cache.get和cache.set手动控制缓存,灵活性更高,适合需要自定义缓存键或超时时间的场景。

from django.core.cache import cache
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Product
from .serializers import ProductSerializer

class ProductDetailView(APIView):
    """商品详情:缓存单个商品对象"""
    def get(self, request, product_id):
        # 1. 定义缓存键:包含业务标识和商品ID,避免键冲突
        cache_key = f'product_detail:{product_id}'
        
        # 2. 尝试从缓存获取数据
        cached_data = cache.get(cache_key)
        if cached_data:
            # 缓存命中:直接返回,无需查询数据库
            return Response({'code': 200, 'data': cached_data})
        
        # 3. 缓存未命中:查询数据库
        try:
            product = Product.objects.get(id=product_id)
        except Product.DoesNotExist:
            return Response({'code': 404, 'msg': '商品不存在'}, status=404)
        
        # 4. 序列化数据
        serializer = ProductSerializer(product)
        product_data = serializer.data
        
        # 5. 存入缓存:设置超时时间为30分钟(60*30秒)
        cache.set(cache_key, product_data, timeout=60*30)
        
        # 6. 返回数据
        return Response({'code': 200, 'data': product_data})
    
    def put(self, request, product_id):
        """更新商品:更新后清除缓存,避免返回旧数据"""
        # 业务逻辑:更新商品信息(此处省略)
        # ...
        
        # 关键操作:清除该商品的缓存,确保下次请求获取最新数据
        cache_key = f'product_detail:{product_id}'
        cache.delete(cache_key)
        
        return Response({'code': 200, 'msg': '商品更新成功'})

(3)ORM 查询优化:减少数据库交互​

除了缓存,ORM 查询优化是提升性能的基础。通过select_related(预加载外键)、prefetch_related(预加载多对多)、only(只查询需要的字段)等方法,可减少 SQL 查询次数,解决 “N+1 查询问题”。

# 反例:未优化的查询(N+1问题)
# 1次查询获取10个商品(1次SQL),然后每个商品查询分类(10次SQL),共11次查询
def bad_product_query():
    products = Product.objects.all()[:10]
    data = []
    for p in products:
        data.append({
            'id': p.id,
            'name': p.name,
            'category_name': p.category.name  # 每次循环都会执行一次SQL
        })
    return data

# 正例1:使用select_related预加载外键(多对一关系)
# 仅1次SQL查询,同时获取商品和关联的分类
def good_product_query1():
    products = Product.objects.select_related('category').all()[:10]  # 预加载外键
    data = []
    for p in products:
        data.append({
            'id': p.id,
            'name': p.name,
            'category_name': p.category.name  # 无额外SQL
        })
    return data

# 正例2:使用prefetch_related预加载多对多关系
# 场景:获取商品及其关联的多个标签(多对多)
def good_product_query2():
    from .models import Tag
    # 2次SQL查询:1次查商品,1次查标签,然后在Python中关联
    products = Product.objects.prefetch_related('tags').all()[:10]
    data = []
    for p in products:
        tag_names = [tag.name for tag in p.tags.all()]  # 无额外SQL
        data.append({
            'id': p.id,
            'name': p.name,
            'tags': tag_names
        })</doubaocanvas>