Files
djangoadmin-wiki/DjangoAdmin_Django_NaiveVue/zh/volume3/performance.md
T
2026-09-13 18:12:07 +08:00

274 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 性能优化指南
:::tip 说明
本文介绍本项目中常见的性能优化策略,涵盖后端 ORM 查询、缓存、分页、前端加载等方面。
:::
## ORM 查询优化
### N+1 查询问题
N+1 查询是最常见的性能问题:查询 N 条记录时,每条记录又触发一次关联查询。
```python
# ❌ 错误:N+1 查询
articles = Article.objects.filter(is_delete=False) # 1 次查询
for article in articles:
category = Category.objects.get(id=article.category_id) # N 次查询
# ✅ 正确:使用 select_related(外键关联)
articles = Article.objects.filter(is_delete=False).select_related('category')
# ✅ 正确:使用 prefetch_related(多对多/反向关联)
roles = Role.objects.filter(is_delete=False).prefetch_related('menus')
```
### select_related vs prefetch_related
| 方法 | 适用场景 | 实现方式 |
|------|---------|---------|
| `select_related` | 外键关联(ForeignKey) | JOIN 查询,一次 SQL |
| `prefetch_related` | 多对多/反向关联 | 两次查询,Python 层合并 |
### 只查询需要的字段
```python
# ❌ 错误:SELECT * 查询所有字段
users = User.objects.filter(is_delete=False)
# ✅ 正确:只查询需要的字段
users = User.objects.filter(is_delete=False).values('id', 'username', 'realname')
# ✅ 正确:使用 only() 只加载指定字段
users = User.objects.filter(is_delete=False).only('id', 'username', 'realname')
```
### 避免循环查询
```python
# ❌ 错误:循环中查询数据库
for role_id in role_ids:
role = Role.objects.get(id=role_id)
# ✅ 正确:批量查询
roles = Role.objects.filter(id__in=role_ids)
```
## 数据库索引策略
### 常用索引类型
| 类型 | 说明 | 适用场景 |
|------|------|---------|
| 单列索引 | `db_index=True` | WHERE 条件字段 |
| 唯一索引 | `unique=True` | 唯一性约束字段 |
| 复合索引 | `index_together` | 多字段联合查询 |
| 覆盖索引 | `include` | 查询字段都在索引中 |
### 索引设计原则
```python
class Example(BaseModel):
name = models.CharField(max_length=100, db_index=True) # 常用查询字段加索引
status = models.IntegerField(db_index=True) # 状态字段加索引
category_id = models.IntegerField(db_index=True) # 外键字段加索引
class Meta:
db_table = 'django_example'
index_together = [
['status', 'is_delete'], # 复合索引:状态 + 软删除
]
```
### 索引注意事项
```
1. 不要过度索引:每个索引都会降低写入性能
2. 选择性高的字段优先:如 username 比 status 选择性更高
3. 复合索引顺序:选择性高的字段放前面
4. 定期分析:使用 EXPLAIN 分析慢查询
```
## 分页优化
### 传统分页 vs 游标分页
```python
# 传统分页(OFFSET):大偏移量时性能差
paginator = Paginator(queryset, page_size)
page = paginator.page(page_no) # OFFSET 100000 时很慢
# 游标分页(推荐大数据量):性能稳定
last_id = request.GET.get('lastId', 0)
queryset = Example.objects.filter(
is_delete=False, id__gt=last_id
).order_by('id')[:page_size]
```
### 分页优化建议
```
1. 小数据量(< 10万):传统分页即可
2. 大数据量(> 10万):使用游标分页
3. 避免 SELECT *:只查需要的字段
4. 避免 COUNT(*):大表 COUNT 很慢,可用缓存
5. 使用索引:ORDER BY 字段必须有索引
```
## Redis 缓存策略
### 缓存穿透
查询不存在的数据,每次都打到数据库:
```python
# 解决方案:缓存空值
def get_user(user_id):
cache_key = f'user:{user_id}'
cached = cache.get(cache_key)
if cached is not None:
return None if cached == 'NULL' else cached
user = User.objects.filter(id=user_id, is_delete=False).first()
if user:
cache.set(cache_key, user, timeout=3600)
else:
cache.set(cache_key, 'NULL', timeout=300) # 缓存空值,短 TTL
return user
```
### 缓存击穿
热点数据过期时,大量请求打到数据库:
```python
# 解决方案:互斥锁
def get_hot_data(key):
cached = cache.get(key)
if cached:
return cached
# 加锁,只允许一个请求查询数据库
lock_key = f'lock:{key}'
if cache.add(lock_key, '1', timeout=10):
try:
data = query_database(key)
cache.set(key, data, timeout=3600)
return data
finally:
cache.delete(lock_key)
else:
# 等待其他请求查询完成
time.sleep(0.1)
return get_hot_data(key)
```
### 缓存雪崩
大量缓存同时过期,请求全部打到数据库:
```python
# 解决方案:随机 TTL
import random
base_timeout = 3600
random_offset = random.randint(0, 600)
cache.set(key, data, timeout=base_timeout + random_offset)
```
## 前端性能优化
### 路由懒加载
```typescript
// 路由懒加载:按需加载页面组件
const routes = [
{
path: '/system/user',
component: () => import('@/views/system/user/index.vue'),
},
];
```
### 组件按需导入
```typescript
// NaiveUI 按需导入
import { NButton, NDataTable, NForm } from 'naive-ui';
```
### 图片懒加载
```vue
<img v-lazy="imageUrl" alt="图片" />
```
### 代码分割
```typescript
// Vite 自动代码分割
// 大型依赖单独打包
import(/* webpackChunkName: "editor" */ '@wangeditor/editor');
```
## Gunicorn Worker 配置
```bash
# gunicorn.conf.py
import multiprocessing
# Worker 数量:CPU 核心数 * 2 + 1
workers = multiprocessing.cpu_count() * 2 + 1
# Worker 类型:sync(默认)/ gevent(异步)
worker_class = 'gevent'
# 超时时间
timeout = 120
# 最大请求数(防止内存泄漏)
max_requests = 1000
max_requests_jitter = 50
```
## 数据库连接池
```python
# application/settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'CONN_MAX_AGE': 600, # 连接存活时间(秒)
'CONN_HEALTH_CHECKS': True, # 连接健康检查
}
}
```
## 慢查询排查
```bash
# 开启 MySQL 慢查询日志
mysql -u root -p -e "
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
"
# 查看慢查询
tail -f /var/log/mysql/slow.log
# 分析查询计划
mysql -u root -p -e "EXPLAIN SELECT * FROM django_example WHERE status = 1;"
```
## 总结
性能优化的核心原则:
```
1. ORM 优化:避免 N+1 查询,使用 select_related/prefetch_related
2. 索引策略:WHERE/ORDER BY 字段加索引,避免过度索引
3. 分页优化:大数据量使用游标分页
4. 缓存策略:穿透/击穿/雪崩三种场景的解决方案
5. 前端优化:路由懒加载、组件按需导入、图片懒加载
6. 配置调优:Gunicorn Worker、数据库连接池
```