Skip to content

性能优化指南

说明

本文介绍本项目中常见的性能优化策略,涵盖后端 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外键关联(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=TrueWHERE 条件字段
唯一索引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
// ElementPlus 按需导入
import { ElButton, ElTable, ElForm } from 'element-plus';

图片懒加载

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、数据库连接池

小蚂蚁云团队 · 提供技术支持