1. 概述
1.1 背景
在考试系统中,当大量学生同时开始考试时,系统需要为每个学生创建考试记录 (ExamRecord) 和答题记录 (ExamAnswerRecord)。传统的"按需创建"模式在高并发场景下存在以下问题:
- 性能瓶颈:每次开始考试都需要执行数据库写入操作,响应时间在 200-500ms
- 并发压力:1000+ 学生同时开考时,数据库压力激增,可能导致超时或失败
- 用户体验:学生点击"开始考试"后需要等待较长时间才能进入考试界面

- Github:xin-lai/CodeSpirit
- Gitee:magicodes/CodeSpirit
1.2 解决方案
考试记录预生成方案通过定时任务 (每天凌晨 1 点) 批量预生成所有已发布且尚未开始的考试的记录和答题记录,将数据库写入操作从"考试开始时刻"提前到"凌晨低负载时段",从而:
- ✅ 性能提升:开始考试耗时从 200-500ms 降低到 10-50ms(命中预生成记录时)
- ✅ 并发优化:数据库写入压力分散到凌晨低负载时段,避免影响正在进行的考试
- ✅ 用户体验:学生点击开始后即刻进入考试,无感知延迟
- ✅ 数据一致性:题目顺序预先确定,避免并发冲突
1.3 核心特性
- 定时预生成:每天凌晨 1 点通过定时任务统一预生成,避免影响正在进行的考试
- 智能预生成:仅预生成第一次考试记录 (AttemptNumber = 1),后续考试动态创建
- 缓存优化:预生成记录写入缓存,开始考试时优先查询缓存,减少数据库查询
- 智能检测:开始考试时自动检测预生成记录,命中则快速启动,未命中则动态创建
- 垃圾清理:定时任务自动清理未使用的预生成记录,避免数据冗余
- 容错机制:预生成失败不影响考试发布,新增学生自动降级为动态创建
2. 架构设计
2.1 系统架构图
sequenceDiagram
participant Admin as 管理员
participant Controller as ExamSettingsController
participant Service as ExamSettingService
participant ScheduledTask as 定时预生成任务
participant Cache as 缓存层
participant DB as 数据库
participant Student as 学生
participant StartExam as CreateExamRecordAsync
Note over Admin,Service: 阶段 1:考试发布
Admin->>Controller: 发布考试
Controller->>Service: PublishExamSettingAsync
Service->>DB: 更新考试状态为 Published
Service-->>Admin: 返回成功
Note over ScheduledTask,DB: 阶段 2:定时预生成 (每天凌晨 1 点)
ScheduledTask->>DB: 查询已发布且尚未开始的考试
ScheduledTask->>DB: 检查是否已预生成
ScheduledTask->>DB: 分批创建 ExamRecord(NotStarted)
ScheduledTask->>DB: 批量创建 ExamAnswerRecord
ScheduledTask->>Cache: 写入预生成记录 ID(过期时间=考试结束时间)
ScheduledTask->>ScheduledTask: 打印详细日志
Note over Student,StartExam: 阶段 3:学生开始考试
Student->>StartExam: 点击开始考试
StartExam->>Cache: 查询预生成记录ID
alt 缓存命中
StartExam->>DB: 加载预生成记录
StartExam->>DB: UPDATE 状态为 InProgress<br/>设置 StartTime
StartExam-->>Student: 快速启动 (10-50ms) ✅
else 缓存未命中 (新增学生)
StartExam->>DB: 动态创建完整记录
StartExam-->>Student: 常规启动 (200-500ms) ⚠️
end
Note over ScheduledTask,DB: 阶段 4:定时清理 (每天凌晨 2 点)
ScheduledTask->>DB: 查询已结束考试的 NotStarted 记录
ScheduledTask->>DB: 批量删除未使用记录
ScheduledTask->>Cache: 清理相关缓存

