# 赛程编排页面加载性能优化

## 问题描述

用户反馈：点击打开编排页面（`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 次独立的数据库查询**
- ���然使用了 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<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 次��络往返减少到 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%。这是一种常见且有效的性能优化手段，特别适合需要关联多个表的查询场景。
