ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

在线考试系统数据迁移实战:历史试卷快照、随机组卷恢复与成绩一致性校验

在线考试系统数据迁移实战:历史试卷快照、随机组卷恢复与成绩一致性校验 在企业培训考试系统升级过程中很多团队最开始会把“数据迁移”理解成数据库搬迁旧数据库 ↓ 字段映射 ↓ 数据清洗 ↓ 新数据库用户表迁过去了题库迁过去了成绩也迁过去了看起来项目似乎已经完成。但真正进入历史数据验收阶段经常会出现一个很棘手的问题成绩还在但当年的试卷已经无法准确还原。尤其是运行时间较长的在线考试系统如果经历过题库修改、随机组卷、选项乱序、题目删除、人工阅卷等业务变化仅仅迁移当前题库远远不够。在宏远培训考试系统从旧版本向新架构升级、历史数据迁移的设计过程中我们也重点处理了这类问题。这里真正需要解决的并不是旧题库能不能导入新系统而是几年以前的一场考试 能不能重新还原出 考生当时看到了什么题 选项是什么顺序 提交了什么答案 当时的正确答案是什么 为什么最终得到这个分数这也是本文重点讨论的问题。一、为什么题库迁移成功历史试卷仍然可能出错很多早期在线考试系统的数据模型大致如下question ↑ │ paper_question ↑ │ paper ↑ │ exam ↑ │ exam_result其中paper_question往往只保存paper_id question_id score真正查看试卷时再通过SELECT * FROM question WHERE id ?;实时读取题库内容。这样的设计在系统刚上线时通常不会暴露明显问题。问题出现在几年以后。假设2022年有一道题发现火灾时首先应该 A. 继续工作 B. 立即报警 C. 等待通知 D. 离开现场 正确答案B2025年因为制度更新管理员修改成发现初起火灾后应首先采取哪项措施 A. 按应急预案处置并及时报告 B. 等待负责人通知 C. 返回岗位 D. 暂不处理 正确答案A如果2026年查询2022年的历史考试而系统仍然通过question_id 30001读取当前题库那么历史答卷展示的就已经不是考生当年看到的题。更严重的是2022年考生选择B 当时判定正确 ↓ 题库后来修改 正确答案变成A ↓ 重新查看历史答卷 系统显示“回答错误”这时候即使最终总成绩仍然保存为90分历史答卷实际上已经失真。因此历史考试不能长期依赖当前题库状态。二、考试系统中应该区分“模板数据”和“事实数据”这是解决历史试卷问题的第一步。在线考试系统里的数据大致可以划分为两类。1. 模板数据包括题库 题目 知识点 试卷模板 组卷规则 题目分类 难度 标签这类数据是可以变化的。例如修改题干 修改答案 增加选项 调整知识点 修改分值 删除试题这些操作都属于正常业务。2. 考试事实数据另一类数据则完全不同。例如某个考生2024年6月18日 参加了某场考试 实际抽到了50道题 第7题展示顺序为C、A、D、B 考生选择了A 当时正确答案为A 该题得到2分 最终总成绩为86分这些数据描述的是已经发生过的考试事实。原则上一旦形成就不应该随着当前题库变化而改变。因此在宏远培训考试系统的新架构设计中题库数据与考试过程数据需要进行逻辑隔离。核心思想就是Question ≠ QuestionSnapshot前者表示当前题库里的题。后者表示某一次具体考试发生时这道题当时是什么样。三、为什么需要题目快照可以把完整的数据结构设计成Question │ │ 组卷 ↓ Paper │ │ 发布 ↓ Exam │ │ 考生进入 ↓ UserPaperSnapshot │ ├── QuestionSnapshot ├── OptionSnapshot └── ExamRuleSnapshot │ ↓ UserAnswer │ ↓ Score其中真正决定历史考试是否能够准确恢复的是UserPaperSnapshot QuestionSnapshot也就是说考生正式进入考试以后应尽量把他真正看到的那份试卷固化下来。四、题目快照不能只保存 question_id有些系统虽然设计了“历史试卷表”但里面仍然只有question_id本质上仍然依赖当前题库。比较完整的题目快照至少应该包含id exam_id paper_snapshot_id source_question_id question_type question_content question_options correct_answer score analysis question_order option_order knowledge_point difficulty content_hash created_time例如{ sourceQuestionId: 30001, type: single, content: 发现火灾时首先应该, options: [ { key: A, content: 继续工作 }, { key: B, content: 立即报警 }, { key: C, content: 等待通知 } ], answer: [B], score: 2, order: 17 }快照生成后即使后台后来执行修改题目 删除题目 调整答案 改变知识点已经完成的考试都不会受到影响。五、选项顺序必须一起固化这是考试系统历史数据中非常容易遗漏的一个问题。例如原题A. 北京 B. 上海 C. 广州 D. 深圳正确答案B如果考试开启选项乱序某个考生实际看到的可能是A. 广州 B. 深圳 C. 上海 D. 北京此时系统内部真正对应的正确答案实际上已经变成C如果数据库仅保存user_answer C却没有保存option_order那么几年以后再恢复试卷时C到底代表上海还是广州可能已经无法确认。因此快照至少应该同时固化题干 原始选项 展示选项顺序 考生答案 正确答案而不能简单认为A/B/C/D本身具有永久业务含义。六、随机组卷是历史试卷迁移最难恢复的数据之一固定试卷通常比较容易处理。真正困难的是随机组卷。例如考试规则单选题20题 判断题20题 多选题10题题库实际上有单选题3000道 判断题1500道 多选题800道这里只保存抽20道单选题 抽20道判断题 抽10道多选题并不能回答三年前张三考试时系统到底随机抽到了哪50道题因此随机组卷规则和考生实际试卷是完全不同的数据。七、迁移老系统时要优先寻找“考试事实表”进行旧系统数据库分析时不应只看paper paper_question question还要重点寻找类似下面的数据表user_exam_question candidate_paper exam_answer_detail user_paper_detail exam_record_detail candidate_question例如user_exam_question user_id exam_id question_id question_order option_order user_answer question_score如果旧系统存在这样的表那么历史考试恢复成功率会高很多。因为真实业务流程实际上是随机组卷规则 ↓ 考生进入考试 ↓ 系统执行随机抽题 ↓ 生成该考生实际试卷 ↓ 保存答题明细迁移真正应该优先恢复的是考生实际考试结果而不是原来的随机规则八、不要试图重新执行随机算法恢复旧试卷这是迁移中非常危险的一种处理方式。假设老数据库只找到随机抽20道题有些程序可能尝试重新执行一次随机抽题以此恢复历史试卷。这样是不正确的。原因很简单假设原来题库有1000道题几年以后新增了500道 删除了100道 修改了200道此时再次执行随机算法Collections.shuffle(questionList);产生的试卷与当年的试卷没有任何确定关系。所以不能用今天的随机结果推算昨天发生过的考试。如果原系统没有保存实际随机结果应明确标识其恢复级别而不是制造一份“看起来完整”的历史试卷。九、历史考试建议划分三级恢复能力实际项目中不是所有老系统数据都具备完整恢复条件。因此不建议只设计迁移成功 迁移失败可以进一步划分为Level A完整恢复旧系统保留考生 考试 实际题目 选项顺序 考生答案 正确答案 分值 阅卷结果可以完整恢复历史试卷 历史答卷 历史成绩Level B条件恢复旧系统保留question_id user_answer score但没有完整题目快照。如果原题库没有发生变化可以通过历史question_id 旧题库生成快照。但是这类数据应增加restore_level B因为一旦原题目曾经修改恢复结果就存在不确定性。Level C结果归档某些老系统可能只有考试名称 姓名 成绩 考试时间 是否合格没有题目级答题数据。这种情况下最合理的方式不是制造历史答卷而是作为历史考试档案保存。例如考试名称2021年度安全考试 姓名张三 成绩86 考试时间2021-06-12 结果合格 历史答卷原系统未保存这里有一个非常重要的数据迁移原则不能验证的数据不要伪装成精确恢复。十、跨系统迁移必须保留ID映射关系系统升级后主键通常会发生变化。例如旧系统user_id 1251 question_id 33881 exam_id 791新系统可能变成user_id 82105 question_id 108234 exam_id 12009如果不建立映射关系后续一旦出现问题很难反查。因此可以增加migration_id_mapping主要字段source_system source_table source_id target_table target_id migration_batch migration_time例如source_system legacy_exam source_table question source_id 33881 target_table question target_id 108234这样可以做到新系统异常数据 ↓ 找到target_id ↓ 查询migration_mapping ↓ 反查旧系统source_id对于长期运行、数据量较大的企业培训考试系统这张映射表非常重要。十一、迁移程序必须具备幂等能力几十万甚至上百万条考试记录迁移时很难保证任务一次执行完成。例如迁移到43% 数据库连接异常或者第583421条数据 发现字段异常如果迁移程序不支持幂等每次都需要从第一条开始执行会非常麻烦。因此可以为迁移数据建立唯一标识source_system source_exam_id source_user_id source_record_id迁移前执行SELECT id FROM exam_question_snapshot WHERE source_system ? AND source_record_id ?;如果已经存在SKIP或者UPDATE不存在才INSERT这样迁移任务可以支持暂停 继续 异常重跑 指定批次重跑而不会重复生成考试数据。十二、不要只校验总成绩历史考试迁移最常见的一种验收方式是旧系统 张三 86分 新系统 张三 86分 结论迁移成功这种校验远远不够。因为完全可能出现旧系统 第1题2分 第2题0分 第3题2分 新系统 第1题0分 第2题2分 第3题2分最后总成绩一样但考试事实已经发生变化。因此宏远培训考试系统在设计历史考试迁移校验时更关注的是多层数据一致性而不是单纯比较最终分数。十三、第一层数量一致性校验首先核对人员数量 考试数量 考试记录数量 答题记录数量 试卷数量 题目数量 证书数据例如旧系统考试记录 186532新系统对应数据应该能够匹配186532如果数量本身就不一致就没有必要直接进入业务验收。十四、第二层关联完整性校验需要检查有没有有成绩没有人员或者有答题记录没有考试记录可以通过SELECT COUNT(*) FROM exam_user_answer a LEFT JOIN exam_user u ON a.exam_user_id u.id WHERE u.id IS NULL;理论上应该得到0同样还应检查Exam ExamUser PaperSnapshot QuestionSnapshot UserAnswer Score之间是否存在孤儿数据。十五、第三层成绩一致性校验对于客观题可以重新计算每题得分之和然后与exam_result.total_score进行比较。例如旧系统92 新系统重新计算90此时不能简单把90更新为92而应该继续向下追踪哪一道题产生了2分差异常见原因包括漏迁题目 题目答案发生变化 分值变化 选项顺序变化 评分规则变化 人工阅卷成绩遗漏十六、第四层通过Hash验证题目内容对于关键考试数据可以进一步计算content_hash例如SHA-256( 题干 选项 正确答案 分值 )迁移前得到old_hash迁移后重新计算new_hash比较old_hash new_hash相比只比较question_idHash更适合检查题目内容是否发生了变化。十七、富文本、图片和附件也是迁移风险点企业考试题目并不一定只是文本。常见内容还有图片 Word内容 公式 表格 音频 附件 富文本HTML例如p请根据下图选择正确答案/p img src/upload/question/2022/018.jpg如果迁移时只处理数据库HTML内容迁成功但没有迁移/upload/question/2022/018.jpg那么多年以后历史试卷中就会显示图片加载失败因此文件迁移还需要处理旧文件路径 ↓ 文件迁移 ↓ 新文件路径 ↓ HTML地址转换同时可以为附件计算file_hash用来验证迁移前后的文件是否一致。十八、历史主观题不能按新标准重新评分这是另一个容易出现业务错误的地方。例如2021年某道简答题满分10分考生作答后老师人工评分8分2025年参考答案修改 评分标准变化在进行历史数据迁移时不能按照2025年的评分规则 重新计算2021年的成绩而应该迁移original_score如果老系统存在还应保留reviewer review_time review_comment即历史考试数据迁移的目标是恢复当时发生过的事实而不是使用今天的规则重新评价过去。十九、试卷规则也应该生成快照除了题目快照还建议保存ExamRuleSnapshot例如{ paperName: 2024年度安全考试, totalScore: 100, passScore: 80, duration: 60, questionCount: 50, allowSubmit: true, randomQuestion: true, randomOption: true }原因是考试规则本身也会发生变化。例如原考试时长 60分钟后来管理员修改为90分钟如果历史考试页面仍然读取当前配置那么几年以后看到的历史考试规则也会失真。二十、快照应该在什么时候生成不同组卷方式需要不同策略。固定试卷可以在考试发布阶段生成PaperSnapshot考试期间所有考生引用同一套试卷快照。随机试卷一般更适合在考生首次进入考试时生成UserPaperSnapshot流程考生进入 ↓ 读取随机组卷规则 ↓ 执行抽题 ↓ 确定题目 ↓ 确定选项顺序 ↓ 生成UserPaperSnapshot ↓ 开始答题以后即使考生断网 刷新 重新登录 重新进入考试也必须重新读取原UserPaperSnapshot不能再次执行Random()否则就可能出现考生考试进行到一半重新进入后发现试题发生变化。二十一、系统升级时真正应该迁移的是什么对于类似宏远培训考试系统这类已经运行多年、需要从旧架构升级到Java及国产化环境的业务系统我们认为真正的数据迁移至少应该包括四个层次第一层 基础数据 人员、部门、岗位、权限 第二层 业务模板数据 题库、课程、试卷、培训计划 第三层 业务事实数据 考试记录、答卷、成绩、证书 第四层 历史过程数据 实际试卷、题目快照、选项顺序、 阅卷结果、考试规则很多迁移项目完成了前三层。真正困难的是第四层因为这决定了历史考试能不能重新打开 能不能重新查看 能不能解释成绩 能不能追溯来源二十二、历史考试迁移建议增加专项验收系统迁移上线之前可以增加历史考试恢复测试不要只随机查看最近一次考试。建议分别抽取不同年份 不同题型 不同试卷模式 不同终端 不同评分方式例如2020年固定试卷 2021年随机试卷 2022年包含图片的试卷 2023年包含人工阅卷的试卷 2024年选项乱序试卷分别验证题目 选项 题目排序 选项排序 考生答案 正确答案 分值 评分结果 总成绩 考试规则只有这些数据能够对应才能说明历史考试恢复基本完成二十三、迁移过程中最容易出现的8个问题结合在线考试系统实际业务以下问题尤其值得注意1. 题目已经删除但历史考试仍然引用原question_id。2. 历史试卷读取当前题库导致修改题目后旧考试同步变化。3. 随机组卷只保存规则没有保存考生真正抽到的题目。4. 开启选项乱序只保存了A/B/C/D没有保存实际排列顺序。5. 图片路径失效数据库迁移完成但题目中的附件没有迁移。6. 主观题重新评分导致历史成绩发生变化。7. 新旧ID没有映射出现异常后无法追溯旧系统原始数据。8. 只验证总成绩没有校验题目级结果。这些问题都说明数据库迁移成功并不等于考试业务迁移成功二十四、结语在线考试系统的数据迁移有一个非常容易被低估的问题题库迁移完成 ≠ 历史试卷恢复完成同样成绩迁移完成 ≠ 历史考试可追溯真正完整的迁移至少应该做到这个人是谁 参加了哪场考试 当时看到什么试卷 具体回答了什么 系统如何评分 为什么得到这个成绩这些问题几年以后仍然能够回答历史考试数据才算真正完整。在宏远培训考试系统的升级设计中我们越来越重视的也正是这一点题库保存的是“现在可以怎么考”而历史试卷保存的是“过去实际发生了什么”。前者可以修改。后者应该尽量固化。对于无法完整恢复的老系统数据则应该通过完整恢复 部分恢复 结果归档进行分级处理并配套来源ID 迁移批次 Hash校验 异常日志 ID映射保证数据来源可以追踪。对于企业培训考试系统来说历史数据迁移真正需要解决的最终不是一句“旧数据库已经导过来了。”而应该能够回答几年以后这场考试还能不能完整解释清楚谁参加了考试、考了什么、答了什么以及为什么得到了这个成绩。这才是考试系统历史数据迁移中真正值得关注的技术问题。
返回列表