# 性能优化指南 :::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 图片 ``` ### 代码分割 ```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、数据库连接池 ```