Files
martial-web/doc/schedule/archive/schedule-performance-optimization.md
宅房 5b806e29b7
Some checks failed
continuous-integration/drone/push Build is failing
fix bugs
2025-12-11 16:56:19 +08:00

266 lines
7.6 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.
# 赛程编排页面加载性能优化
## 问题描述
用户反馈:点击打开编排页面(`http://localhost:2888/api/martial/project/detail?id=200`)时出现大批量数据库查询,导致页面加载缓慢。
## 原问题分析
### 原实现方式MartialScheduleServiceImpl.java:149-258
```java
public ScheduleResultDTO getScheduleResult(Long competitionId) {
// 1. 查询所有分组
List<MartialScheduleGroup> groups = scheduleGroupMapper.selectList(...);
// 2. 查询所有编排明细
List<MartialScheduleDetail> details = scheduleDetailMapper.selectList(...);
// 3. 查询所有参赛者(使用 IN 查询)
List<MartialScheduleParticipant> participants = scheduleParticipantMapper.selectList(...);
// 4. 在内存中进行数据组装
// ...
}
```
**性能问题**
- 执行了 **3 次独立的数据库查询**
- <20><><EFBFBD>然使用了 IN 查询避免了 N+1 问题,但仍需要多次网络往返
- 数据库需要执行 3 次查询计划,查询优化器无法统一优化
- 数据传输量大,需要多次网络 IO
## 优化方案
### 使用单次 JOIN 查询获取所有数据
#### 1. 创建优化的 VO <20><>
新建文件:`ScheduleGroupDetailVO.java`
```java
@Data
public class ScheduleGroupDetailVO implements Serializable {
// 分组信息
private Long groupId;
private String groupName;
private String category;
private Integer projectType;
private Integer totalTeams;
private Integer totalParticipants;
private Integer displayOrder;
// 编排明细信息
private Long detailId;
private Long venueId;
private String venueName;
private String timeSlot;
// 参赛者信息
private Long participantId;
private String organization;
private String checkInStatus;
private String scheduleStatus;
private Integer performanceOrder;
}
```
#### 2. 添加自定义 Mapper 方法
`MartialScheduleGroupMapper.java` 中添加:
```java
public interface MartialScheduleGroupMapper extends BaseMapper<MartialScheduleGroup> {
/**
* 查询赛程编排的完整详情一次性JOIN查询优化性能
*/
List<ScheduleGroupDetailVO> selectScheduleGroupDetails(@Param("competitionId") Long competitionId);
}
```
#### 3. 实现优化的 SQL 查询
`MartialScheduleGroupMapper.xml` 中实现:
```xml
<select id="selectScheduleGroupDetails" resultType="org.springblade.modules.martial.pojo.vo.ScheduleGroupDetailVO">
SELECT
g.id AS groupId,
g.group_name AS groupName,
g.category AS category,
g.project_type AS projectType,
g.total_teams AS totalTeams,
g.total_participants AS totalParticipants,
g.display_order AS displayOrder,
d.id AS detailId,
d.venue_id AS venueId,
d.venue_name AS venueName,
d.time_slot AS timeSlot,
p.id AS participantId,
p.organization AS organization,
p.check_in_status AS checkInStatus,
p.schedule_status AS scheduleStatus,
p.performance_order AS performanceOrder
FROM
martial_schedule_group g
LEFT JOIN
martial_schedule_detail d ON g.id = d.schedule_group_id AND d.is_deleted = 0
LEFT JOIN
martial_schedule_participant p ON g.id = p.schedule_group_id AND p.is_deleted = 0
WHERE
g.competition_id = #{competitionId}
AND g.is_deleted = 0
ORDER BY
g.display_order ASC,
p.performance_order ASC
</select>
```
#### 4. 重写 Service 层方法
修改 `MartialScheduleServiceImpl.getScheduleResult()` 方法:
```java
@Override
public ScheduleResultDTO getScheduleResult(Long competitionId) {
// 使用优化的一次性JOIN查询获取所有数据
List<ScheduleGroupDetailVO> details = scheduleGroupMapper.selectScheduleGroupDetails(competitionId);
if (details.isEmpty()) {
// 返回空结果
}
// 按分组ID分组数据在内存中处理速度很快
Map<Long, List<ScheduleGroupDetailVO>> groupMap = details.stream()
.collect(Collectors.groupingBy(ScheduleGroupDetailVO::getGroupId));
// 组装 DTO 返回
// ...
}
```
## 优化效果
### 数据库查询次数对比
| 指标 | 优化前 | 优化后 | 提升 |
|------|--------|--------|------|
| SQL 查询次数 | 3 次 | **1 次** | **减少 66.7%** |
| 网络往返次数 | 3 次 | **1 次** | **减少 66.7%** |
| 查询优化 | 分散优化 | **统一优化** | 数据库可进行整体优化 |
### 性能提升分析
1. **减少网络开销**
- 从 3 次<><E6ACA1>络往返减少到 1 次
- 减少了 TCP 连接的建立和等待时间
- 降低了网络延迟的累积效应
2. **数据库查询优化**
- 数据库可以对整个 JOIN 查询进行统一的执行计划优化
- 可以利用索引加速 JOIN 操作
- 减少了查询解析和编译的次数
3. **数据传输优化**
- 虽然单次传输数据量可能略大,但总体网络 IO 更少
- 减少了协议头、认证等额外开销
4. **应用层优化**
- 使用 Java Stream API 在内存中快速分组
- 内存操作速度远快于网络 IO
### 预估性能提升
假设场景:
- 一个比赛有 20 个分组
- 平均每个分组有 30 个参赛者
- 单次数据库查询平均耗时 50ms
**优化前**
- 3 次查询 × 50ms = 150ms
- 加上网络延迟和 Java 处理 ≈ **200ms**
**优化后**
- 1 次查询 × 80ms = 80msJOIN 查询稍慢)
- 加上 Java 内存分组 ≈ **100ms**
**性能提升**:约 **50%** 的响应时间减少
## 实际应用建议
### 何时使用这种优化
**适用场景**
- 需要同时查询多个关联表的数据
- 数据量不是特别大(几千到几万条)
- 需要减少网络往返次数
- 关联关系明确JOIN 条件简单
⚠️ **不适用场景**
- 单表数据量超过 10 万条
- JOIN 会产生笛卡尔积爆炸
- 某些关联数据可选加载(懒加载更合适)
### 进一步优化建议
如果数据量继续增大,可以考虑:
1. **分页加载**
- 前端使用虚拟滚动或分页
- 后端添加 LIMIT/OFFSET
2. **缓存优化**
- 将常用的编排结果缓存到 Redis
- 设置合理的过期时间
3. **数据库索引**
- 确保 `competition_id`, `schedule_group_id` 有索引
- 考虑添加联合索引加速 JOIN
4. **读写分离**
- 查询走从库,减轻主库压力
- 使用 MyBatis Plus 的多数据源配置
## 相关文件
### 新增文件
- `src/main/java/org/springblade/modules/martial/pojo/vo/ScheduleGroupDetailVO.java`
### 修改文件
- `src/main/java/org/springblade/modules/martial/mapper/MartialScheduleGroupMapper.java`
- `src/main/java/org/springblade/modules/martial/mapper/MartialScheduleGroupMapper.xml`
- `src/main/java/org/springblade/modules/martial/service/impl/MartialScheduleServiceImpl.java`
## 测试验证
### 如何测试
1. **启动后端服务**
```bash
cd martial-master
mvn spring-boot:run
```
2. **访问编排页面**
```
http://localhost:2888/api/martial/project/detail?id=200
```
3. **查看数据库日志**
- 在 `application.yml` 中开启 SQL 日志
- 观察只执行了 1 次 JOIN 查询
4. **性能对比**
- 使用浏览器开发者工具查看网络请求时间
- 对比优化前后的响应时间
### 预期结果
- 后端日志中只看到 1 条 SQL 查询语句
- 页面加载速度明显提升
- 数据显示正确,功能无异常
## 总结
这次优化通过将 **3 次独立查询合并为 1 次 JOIN 查询**,显著减少了数据库往返次数和网络 IO预计可将页面加载时间减少约 50%。这是一种常见且有效的性能优化手段,特别适合需要关联多个表的查询场景。