Django单体应用优化:从30秒到142毫秒的报告加载
在一个遗留的Django项目中,由于对包含28万条记录的订单表进行全表顺序扫描,分支报告加载耗时30秒。EXPLAIN ANALYZE显示:
Seq Scan on orders (cost=0.00..18420.00 rows=2841 width=847)
Filter: (branch_id = 42)
Rows Removed by Filter: 284100
Execution Time: 28340 ms
ORM因相关对象的N+1问题生成了2800多个查询。业务逻辑放在控制器中进一步加剧了负载。
原始查询:
SELECT * FROM orders
JOIN order_items ON orders.id = order_items.order_id
JOIN products ON order_items.product_id = products.id
WHERE orders.branch_id = 42;
消除N+1问题
N+1导致负载呈指数级增长。优化前:
orders = Order.objects.filter(branch_id=branch_id)
for order in orders:
items = order.order_items.all() # N+1
for item in items:
product = item.product # N+1
使用预取优化后:
orders = Order.objects.filter(
branch_id=branch_id
).select_related('customer').prefetch_related('order_items__product')
查询次数降至3次。select_related用于外键的JOIN,prefetch_related用于多对多和反向关系的IN查询。
为表添加索引
解决N+1后,顺序扫描问题依然存在。我们无停机添加了索引:
CREATE INDEX CONCURRENTLY idx_orders_branch_created ON orders(branch_id, created_at DESC);
CREATE INDEX CONCURRENTLY idx_products_search ON products USING GIN(to_tsvector('english', name));
新的执行计划:
Index Scan using idx_orders_branch_created on orders
Index Cond: (branch_id = 42)
Execution Time: 142 ms
执行时间从28秒降至142毫秒。CONCURRENTLY可防止生产环境中的表锁定。
关键索引建议:
- 在(过滤条件 + 排序字段)上创建复合索引
- 使用GIN索引进行全文搜索
- 添加索引前务必检查EXPLAIN ANALYZE
重构为领域驱动设计(DDD)
控制器中的业务逻辑造成了技术债务。我们独立于框架提取了领域和用例。
用例示例:
class GetBranchReportUseCase:
def __init__(self, repo: OrderRepository):
self._repo = repo
def execute(self, branch_id, period) -> BranchReport:
orders = self._repo.get_by_branch_and_period(branch_id, period)
return BranchReport.from_orders(orders)
视图变得简洁:
class BranchReportView(APIView):
def get(self, request, branch_id):
use_case = GetBranchReportUseCase(DjangoOrderRepository())
report = use_case.execute(branch_id, DateRange.from_request(request))
return Response(BranchReportSerializer(report).data)
用例可通过模拟仓库进行测试,无需依赖Django和数据库。新功能的上市时间缩短了一半。
代码质量控制
新增:
- Mypy严格模式:
[mypy]
strict = true
disallow_untyped_defs = true
warn_return_any = true
- Pytest覆盖率≥87%,在GitLab CI中设置质量门禁。
- 由于早期错误检测,平均故障检测时间(MTTD)降低了40%。
改进指标
| 指标 | 优化前 | 优化后 |
|--------|--------|-------|
| 报告加载时间 | 30秒 | 1.5秒 |
| 数据库CPU使用率 | 80% | 32% |
| 每页查询次数 | 2800+ | 3 |
| 功能上市时间 | X | X/2 |
| 平均故障检测时间 | - | -40% |
关键要点
- EXPLAIN ANALYZE是排查性能问题的第一步
- N+1在高负载下是性能杀手,prefetch_related/select_related必不可少
- 在(branch_id, created_at)上创建复合索引可带来200倍加速
- DDD隔离业务逻辑,加速开发和测试
- Mypy + pytest + CI门禁可降低MTTD,且无额外开销
— Editorial Team
暂无评论。