7.6 KiB
7.6 KiB
赛程编排页面加载性能优化
问题描述
用户反馈:点击打开编排页面(http://localhost:2888/api/martial/project/detail?id=200)时出现大批量数据库查询,导致页面加载缓慢。
原问题分析
原实现方式(MartialScheduleServiceImpl.java:149-258)
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 次独立的数据库查询
- <EFBFBD><EFBFBD><EFBFBD>然使用了 IN 查询避免了 N+1 问题,但仍需要多次网络往返
- 数据库需要执行 3 次查询计划,查询优化器无法统一优化
- 数据传输量大,需要多次网络 IO
优化方案
使用单次 JOIN 查询获取所有数据
1. 创建优化的 VO <20><>
新建文件:ScheduleGroupDetailVO.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 中添加:
public interface MartialScheduleGroupMapper extends BaseMapper<MartialScheduleGroup> {
/**
* 查询赛程编排的完整详情(一次性JOIN查询,优化性能)
*/
List<ScheduleGroupDetailVO> selectScheduleGroupDetails(@Param("competitionId") Long competitionId);
}
3. 实现优化的 SQL 查询
在 MartialScheduleGroupMapper.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() 方法:
@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% |
| 查询优化 | 分散优化 | 统一优化 | 数据库可进行整体优化 |
性能提升分析
-
减少网络开销
- 从 3 次<><E6ACA1>络往返减少到 1 次
- 减少了 TCP 连接的建立和等待时间
- 降低了网络延迟的累积效应
-
数据库查询优化
- 数据库可以对整个 JOIN 查询进行统一的执行计划优化
- 可以利用索引加速 JOIN 操作
- 减少了查询解析和编译的次数
-
数据传输优化
- 虽然单次传输数据量可能略大,但总体网络 IO 更少
- 减少了协议头、认证等额外开销
-
应用层优化
- 使用 Java Stream API 在内存中快速分组
- 内存操作速度远快于网络 IO
预估性能提升
假设场景:
- 一个比赛有 20 个分组
- 平均每个分组有 30 个参赛者
- 单次数据库查询平均耗时 50ms
优化前:
- 3 次查询 × 50ms = 150ms
- 加上网络延迟和 Java 处理 ≈ 200ms
优化后:
- 1 次查询 × 80ms = 80ms(JOIN 查询稍慢)
- 加上 Java 内存分组 ≈ 100ms
性能提升:约 50% 的响应时间减少
实际应用建议
何时使用这种优化
✅ 适用场景:
- 需要同时查询多个关联表的数据
- 数据量不是特别大(几千到几万条)
- 需要减少网络往返次数
- 关联关系明确,JOIN 条件简单
⚠️ 不适用场景:
- 单表数据量超过 10 万条
- JOIN 会产生笛卡尔积爆炸
- 某些关联数据可选加载(懒加载更合适)
进一步优化建议
如果数据量继续增大,可以考虑:
-
分页加载
- 前端使用虚拟滚动或分页
- 后端添加 LIMIT/OFFSET
-
缓存优化
- 将常用的编排结果缓存到 Redis
- 设置合理的过期时间
-
数据库索引
- 确保
competition_id,schedule_group_id有索引 - 考虑添加联合索引加速 JOIN
- 确保
-
读写分离
- 查询走从库,减轻主库压力
- 使用 MyBatis Plus 的多数据源配置
相关文件
新增文件
src/main/java/org/springblade/modules/martial/pojo/vo/ScheduleGroupDetailVO.java
修改文件
src/main/java/org/springblade/modules/martial/mapper/MartialScheduleGroupMapper.javasrc/main/java/org/springblade/modules/martial/mapper/MartialScheduleGroupMapper.xmlsrc/main/java/org/springblade/modules/martial/service/impl/MartialScheduleServiceImpl.java
测试验证
如何测试
-
启动后端服务
cd martial-master mvn spring-boot:run -
访问编排页面
http://localhost:2888/api/martial/project/detail?id=200 -
查看数据库日志
- 在
application.yml中开启 SQL 日志 - 观察只执行了 1 次 JOIN 查询
- 在
-
性能对比
- 使用浏览器开发者工具查看网络请求时间
- 对比优化前后的响应时间
预期结果
- 后端日志中只看到 1 条 SQL 查询语句
- 页面加载速度明显提升
- 数据显示正确,功能无异常
总结
这次优化通过将 3 次独立查询合并为 1 次 JOIN 查询,显著减少了数据库往返次数和网络 IO,预计可将页面加载时间减少约 50%。这是一种常见且有效的性能优化手段,特别适合需要关联多个表的查询场景。