This commit is contained in:
265
doc/schedule/archive/schedule-performance-optimization.md
Normal file
265
doc/schedule/archive/schedule-performance-optimization.md
Normal file
@@ -0,0 +1,265 @@
|
||||
# 赛程编排页面加载性能优化
|
||||
|
||||
## 问题描述
|
||||
|
||||
用户反馈:点击打开编排页面(`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 = 80ms(JOIN 查询稍慢)
|
||||
- 加上 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%。这是一种常见且有效的性能优化手段,特别适合需要关联多个表的查询场景。
|
||||
Reference in New Issue
Block a user