# 赛程编排页面加载性能优化 ## 问题描述 用户反馈:点击打开编排页面(`http://localhost:2888/api/martial/project/detail?id=200`)时出现大批量数据库查询,导致页面加载缓慢。 ## 原问题分析 ### 原实现方式(MartialScheduleServiceImpl.java:149-258) ```java public ScheduleResultDTO getScheduleResult(Long competitionId) { // 1. 查询所有分组 List groups = scheduleGroupMapper.selectList(...); // 2. 查询所有编排明细 List details = scheduleDetailMapper.selectList(...); // 3. 查询所有参赛者(使用 IN 查询) List participants = scheduleParticipantMapper.selectList(...); // 4. 在内存中进行数据组装 // ... } ``` **性能问题**: - 执行了 **3 次独立的数据库查询** - ���然使用了 IN 查询避免了 N+1 问题,但仍需要多次网络往返 - 数据库需要执行 3 次查询计划,查询优化器无法统一优化 - 数据传输量大,需要多次网络 IO ## 优化方案 ### 使用单次 JOIN 查询获取所有数据 #### 1. 创建优化的 VO �� 新建文件:`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 { /** * 查询赛程编排的完整详情(一次性JOIN查询,优化性能) */ List selectScheduleGroupDetails(@Param("competitionId") Long competitionId); } ``` #### 3. 实现优化的 SQL 查询 在 `MartialScheduleGroupMapper.xml` 中实现: ```xml ``` #### 4. 重写 Service 层方法 修改 `MartialScheduleServiceImpl.getScheduleResult()` 方法: ```java @Override public ScheduleResultDTO getScheduleResult(Long competitionId) { // 使用优化的一次性JOIN查询获取所有数据 List details = scheduleGroupMapper.selectScheduleGroupDetails(competitionId); if (details.isEmpty()) { // 返回空结果 } // 按分组ID分组数据(在内存中处理,速度很快) Map> groupMap = details.stream() .collect(Collectors.groupingBy(ScheduleGroupDetailVO::getGroupId)); // 组装 DTO 返回 // ... } ``` ## 优化效果 ### 数据库查询次数对比 | 指标 | 优化前 | 优化后 | 提升 | |------|--------|--------|------| | SQL 查询次数 | 3 次 | **1 次** | **减少 66.7%** | | 网络往返次数 | 3 次 | **1 次** | **减少 66.7%** | | 查询优化 | 分散优化 | **统一优化** | 数据库可进行整体优化 | ### 性能提升分析 1. **减少网络开销** - 从 3 次��络往返减少到 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%。这是一种常见且有效的性能优化手段,特别适合需要关联多个表的查询场景。