Django 优化技巧

FreeGuideOnline 最新 2026-07-15

python

不会立即执行查询

qs = MyModel.objects.filter(status='active')

这里才会执行查询

for obj in qs: print(obj.name)


#### 2. 使用 select_related 与 prefetch_related 减少关联查询

当模型之间存在外键或一对一关系时,`select_related` 使用 SQL JOIN 一次性获取关联对象,避免 N+1 查询问题。对于多对多或多对一反向关系,应使用 `prefetch_related` 进行额外的查询并做 Python 层面的关联。

```python
# 糟糕的做法:每条记录都会触发一次新查询
books = Book.objects.all()
for book in books:
    print(book.author.name)   # 每次循环查询作者

# 优化为一条 JOIN 查询
books = Book.objects.select_related('author').all()
for book in books:
    print(book.author.name)

# 多对多关系
authors = Author.objects.prefetch_related('books').all()

3. 仅获取需要的字段:only 与 defer

如果你只需要模型的部分字段,使用 only()defer() 可以避免加载大文本或二进制字段,减少数据传输和内存开销。

# 只加载 id 和 title
posts = Post.objects.only('id', 'title')

# 推迟加载大字段 content
posts = Post.objects.defer('content')

4. 合理使用查询集缓存

每次对 QuerySet 求值后,结果会保存在其内部缓存中。重复使用同一个 QuerySet 对象可避免重复查询。

qs = Entry.objects.filter(status='published')
# 第一次求值,缓存结果
count = len(qs)
# 第二次直接从缓存读取,不会查询数据库
items = list(qs)

注意:切片或过滤操作会新建 QuerySet,不共享缓存。

5. 聚合与批量操作

在需要统计或批量更新时,优先使用数据库层面的聚合函数和 update()bulk_create()bulk_update(),而非在 Python 中循环处理。

# 一次性更新符合条件的所有行
Product.objects.filter(price__lt=10).update(price=10)

# 批量创建
Product.objects.bulk_create([
    Product(name='A'),
    Product(name='B'),
    Product(name='C'),
])

# 计数使用 count() 而非 len(queryset)
num = Product.objects.filter(active=True).count()

缓存策略

合理使用缓存能极大减轻数据库压力,并提升页面响应速度。

1. 视图缓存

使用 cache_page 装饰器可以轻松缓存整个视图输出,指定缓存秒数即可。

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)  # 缓存 15 分钟
def my_view(request):
    ...

2. 模板片段缓存

在模板中使用 {% cache %} 标签仅缓存变化不频繁的部分,降低缓存粒度。

{% load cache %}
{% cache 500 sidebar request.user.username %}
    ...
{% endcache %}

3. 低级缓存 API

通过 django.core.cache 可以灵活缓存任意数据,例如复杂的查询结果或计算结果。

from django.core.cache import cache

def get_top_products():
    key = 'top_products'
    result = cache.get(key)
    if result is None:
        result = Product.objects.filter(rating__gt=4).order_by('-sales')[:10]
        cache.set(key, result, 300)
    return result

4. 缓存后端选择

开发环境可使用本地内存缓存,生产环境建议使用 Redis 或 Memcached。配置示例(使用 Redis):

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.redis.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
    }
}

静态文件与媒体资源优化

前端资源的加载速度直接影响用户体验,以下技巧不可忽视。

1. 使用 CDN 加速静态文件

将静态资源托管到 CDN 并配置 STATIC_URL,降低服务器负载并加快全球访问速度。

2. 压缩与合并

利用 django-compressorwebpack 等工具合并 CSS/JS 并压缩,减少请求数量和文件大小。生产环境务必启用 Gzip 或 Brotli 压缩。

3. 设置合适的缓存头

在你的 Web 服务器(Nginx/Apache)中对静态文件设置远期 Expires 头,实现浏览器缓存。

中间件与请求处理优化

1. 精简中间件栈

每个中间件都会在每个请求中执行,移除不必要的中间件可以提高吞吐量。检查 MIDDLEWARE 配置,去除非必须组件。

2. 使用流式响应处理大文件

在处理大文件下载或生成大量数据时,使用 StreamingHttpResponse 可以避免内存耗尽。

from django.http import StreamingHttpResponse

def large_csv(request):
    rows = (f"{i},data\n" for i in range(1000000))
    response = StreamingHttpResponse(rows, content_type='text/csv')
    response['Content-Disposition'] = 'attachment; filename="large.csv"'
    return response

异步与并发处理

从 Django 3.1 开始支持异步视图,适合处理 I/O 密集型任务。

1. 编写异步视图

将阻塞 I/O 操作改为异步执行,释放线程处理其他请求。

from django.http import JsonResponse
import asyncio

async def async_view(request):
    await asyncio.sleep(1)  # 模拟异步 I/O
    return JsonResponse({'message': 'Hello async world!'})

2. 搭配 ASGI 服务器

部署时使用 Uvicorn 或 Daphne 等 ASGI 服务器取代传统 WSGI,才能发挥异步优势。

通用性能实践

1. 分页与限制结果集

永远不要一次性加载大量数据到内存,使用 Django 的分页器或 QuerySet 的切片。

from django.core.paginator import Paginator

def list_view(request):
    qs = Product.objects.all()
    paginator = Paginator(qs, 25)  # 每页 25 条
    page_obj = paginator.get_page(request.GET.get('page'))
    return render(request, 'list.html', {'page_obj': page_obj})

2. 使用 Django Debug Toolbar 剖析

在开发环境安装 django-debug-toolbar,直观查看 SQL 查询、缓存命中率、执行时间等,快速定位瓶颈。

# settings.py
if DEBUG:
    INSTALLED_APPS += ['debug_toolbar']
    MIDDLEWARE = ['debug_toolbar.middleware.DebugToolbarMiddleware'] + MIDDLEWARE
    INTERNAL_IPS = ['127.0.0.1']

3. 数据库索引

为常用查询字段添加数据库索引,特别是 filter()order_by() 中的字段。

class Product(models.Model):
    name = models.CharField(max_length=100, db_index=True)
    category = models.ForeignKey(Category, on_delete=models.CASCADE, db_index=True)

但不要滥用索引,它会拖慢写入速度并占用存储。

4. 连接池与持久连接

设置 CONN_MAX_AGE 启用数据库持久连接,减少重复握手的开销。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'CONN_MAX_AGE': 600,  # 保持连接 10 分钟
    }
}