Django 优化技巧
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-compressor 或 webpack 等工具合并 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 分钟
}
}