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

7.6 KiB
Raw Blame History

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

问题描述

用户反馈:点击打开编排页面(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%
查询优化 分散优化 统一优化 数据库可进行整体优化

性能提升分析

  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. 启动后端服务

    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%。这是一种常见且有效的性能优化手段,特别适合需要关联多个表的查询场景。