恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
在线考试系统MySQL数据库设计:表结构、状态机与并发优化
首页
资讯中心
/
在线考试系统MySQL数据库设计:表结构、状态机与并发优化
在线考试系统MySQL数据库设计:表结构、状态机与并发优化
发布时间:2026/10/9 9:33:32
简介在线考试系统数据库设计文档以PDF形式提供面向需要设计考试系统数据库的开发人员、毕业设计学生及Java/.NET等后端学习者。文档基于MySQL描述中亦提及SQL Server环境详细规划了用户管理、学生信息、考试管理、课程管理、试卷管理、成绩管理以及单选/多选/判断/填空四类题库等10张核心数据表对每张表的字段名、类型、位数、主键及是否允许为空均作了明确说明可作为在线考试系统开发时的建表参照和课程设计参考也可直接复制用于SQL脚本初始化数据层。资源仅含1个PDF文件体积约71KB内容紧凑、表结构清晰便于直接查阅与复用。目前已有272人学习下载适合正在搭建考试平台或学习数据库表结构设计的读者直接复用表结构快速完成数据层设计。1. 在线考试系统数据库设计一张表撑不起一场考试在线考试系统的 MySQL 数据库设计难点从来不在“建几张表”而在一场考试从组卷、报名、答题到判分的过程中表与表之间的状态和关系能不能一直保持正确。很多团队前期只把题库表建完就开工结果联调阶段发现并发交卷时答题记录重复、成绩表总分对不上最后只能靠临时 SQL 洗数据。我习惯的做法是先拆业务流再落表用户、题库、试卷、考试、答题、成绩六个核心域分开建配合状态机和唯一键把并发口子堵住。这篇笔记会给出完整的建表脚本、三处关系陷阱的取舍以及五个值得记进团队规范里的翻车现场。适合正在做在线考试系统后端、或者要重构旧考试模块数据库的同学。2. 从业务流到表结构先画出试卷与答题的状态机设计考试系统的库第一件事不是打开 MySQL 写 CREATE TABLE而是把业务从头到尾走一遍教师创建考试并组卷系统发布考试学生在规定时间内报名和答题交卷后客观题自动判分、主观题人工评阅最后发布成绩归档。这条链路里每个节点对数据的要求都不一样直接一张大表硬扛后面改起来全是血的教训。2.1 核心实体识别考试不是“一张表”我先按业务动作把实体切成六个域用户域管谁在考试题库域管考什么试卷域管怎么组卷考试域管一场考试的时间与状态答题域管学生写了什么成绩域管最终结果。六个域各司其职JOIN 的时候边界才清晰。核心域建议表名一句话职责用户域sys_user学生、教师、管理员统一存放题库域question题干、选项、答案、解析、难度试卷域paper / paper_question试卷主表与试卷题目关联考试域exam考试场次、时间、状态答题域answer_record每个学生每道题的作答明细成绩域exam_result学生一场考试的总分与判分状态这套拆分常见于企业内部的考试中台也适合课程教学场景。关键原则是试卷与考试分离、答题与成绩分离。试卷可以被多场考试复用答题记录要保留每一次作答的原始痕迹成绩表只是最终快照。三者的生命周期不同塞进同一张表会让后续的统计查询和状态更新互相踩脚。2.2 状态机考试从草稿到已归档的六个状态考试表的 status 字段是我最在意的一个设计它不能省。很多新手直接用 start_time 和 end_time 判断考试是否在进行中听起来没问题但遇到提前发布、临时延期、人工归档就全乱套。我一般用六个状态status 值含义触发时机0草稿创建考试但未发布1已发布未开始管理员点击发布2进行中定时任务或启动接口触发3判分中考试时间截止交卷入口关闭4已出分全部主观题判完并发布成绩5已归档考试数据冻结仅供查询状态流转必须是单向的不允许从 4 跳回 2。每一条流转都配合操作日志记录操作人、时间和原因。后台定时任务负责把到期考试从 2 推到 3前端展示只读 status 字段不参与判断。这样设计之后查询正在进行的考试直接WHERE status 2走索引干净利落不用在两个时间字段上做范围比较。2.3 数据字典先行先定字段命名规范再建表建表前我会先花半小时定一套字段约定后面写 JOIN 和排查问题时能省一整天的沟通成本。基本规范如下主键统一叫xxx_id类型 BIGINT 自增时间字段统一create_time/update_time用 DATETIME 而不是 TIMESTAMP避免 2038 年问题分数和金额统一DECIMAL(5,1)不用 FLOAT 和 DOUBLE避免浮点误差状态字段统一 TINYINT 并加上注释说明每个取值含义所有表都带delete_flag做软删除默认 0删除置 1。这里有个容易忽略的细节软删除标记不要参与唯一键。比如用户表的 username 做了唯一键一旦用户被软删除同名用户再注册就会撞唯一键。我通常把唯一键设计成(username, delete_flag)或者删除时把 username 改写为带时间戳的名字。3. 核心表 DDL考试系统 MySQL 建表的完整脚本状态机和数据字典定完建表就是体力活了。下面这份 DDL 是我在一个企业培训考试模块里沉淀下来的骨架去掉了业务专属字段保留最核心的结构。全部基于 MySQL 8.0存储引擎 InnoDB字符集 utf8mb4。3.1 用户与角色表角色不建三张表用户表只要一张角色用字段区分不要拆成 student 表、teacher 表、admin 表三张。原因是三类角色的基础字段几乎一样拆表只会让查询权限时多做一次 UNION。真正需要区分的是业务权限那属于应用层或者权限表的事不该由用户表结构承担。CREATE TABLE sys_user ( user_id BIGINT AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(50) NOT NULL COMMENT 登录名, pass_hash VARCHAR(128) NOT NULL COMMENT 密码哈希值, real_name VARCHAR(50) NOT NULL COMMENT 姓名, role TINYINT NOT NULL DEFAULT 2 COMMENT 角色1教师 2学生 3管理员, org_id BIGINT DEFAULT NULL COMMENT 班级/部门ID外键逻辑关联, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态1正常 0禁用, delete_flag TINYINT NOT NULL DEFAULT 0 COMMENT 软删除0未删 1已删, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (user_id), UNIQUE KEY uk_username (username, delete_flag), KEY idx_role (role) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci COMMENT系统用户表;字段pass_hash只存哈希摘要字段名不叫 password提醒后续接手的人别往里面塞明文。唯一键(username, delete_flag)解决了我前面说的软删除后再注册的冲突问题。角色字段用 TINYINT 存枚举值应用层做映射查询和索引都比字符串高效。COLLATEutf8mb4_0900_ai_ci是 MySQL 8.0 的默认排序规则如果你的环境是 5.7改成utf8mb4_general_ci即可注意同一实例里排序规则尽量统一否则关联比较时容易报字符集不一致的错误。3.2 题库与试卷表题目和试卷的多对多拆分题目表单独存放和试卷之间用一张关联表连接。题目表里我用 JSON 字段存选项而不是拆成 option_a、option_b 这种固定列原因很简单判断题和填空题没有选项主观题只有文本固定列会浪费大量空间而且新增一种题型要改表结构。JSON 在 MySQL 5.7 之后支持良好配合应用层序列化完全够用。CREATE TABLE question ( question_id BIGINT AUTO_INCREMENT COMMENT 题目ID, question_type TINYINT NOT NULL DEFAULT 1 COMMENT 题型1单选 2多选 3判断 4填空 5主观, content TEXT NOT NULL COMMENT 题干内容, options JSON DEFAULT NULL COMMENT 选项JSON如{A:选项内容,B:选项内容}, answer VARCHAR(500) DEFAULT NULL COMMENT 客观题标准答案单选存A多选存A,B, analysis TEXT DEFAULT NULL COMMENT 答案解析, difficulty TINYINT NOT NULL DEFAULT 3 COMMENT 难度系数1最易 5最难, subject_id BIGINT DEFAULT NULL COMMENT 科目ID逻辑关联, creator_id BIGINT DEFAULT NULL COMMENT 创建人ID, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态1可用 0停用, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (question_id), KEY idx_subject_diff (subject_id, difficulty), KEY idx_creator (creator_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci COMMENT题目表;试卷表和题目关联表是配套的。同一道题可以出现在多张试卷里同一张试卷包含多道题这是典型的多对多关系必须拆中间表。中间表里除了两个外键还要带上“该题在这张试卷里的分值”和“排序号”因为同一道题在不同试卷里分值可能不同。CREATE TABLE paper ( paper_id BIGINT AUTO_INCREMENT COMMENT 试卷ID, paper_name VARCHAR(100) NOT NULL COMMENT 试卷名称, total_score DECIMAL(5,1) NOT NULL DEFAULT 0 COMMENT 试卷总分冗余字段, status TINYINT NOT NULL DEFAULT 0 COMMENT 状态0草稿 1已使用 2停用, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (paper_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci COMMENT试卷表; CREATE TABLE paper_question ( id BIGINT AUTO_INCREMENT COMMENT 关联ID, paper_id BIGINT NOT NULL COMMENT 试卷ID, question_id BIGINT NOT NULL COMMENT 题目ID, score DECIMAL(5,1) NOT NULL DEFAULT 2.0 COMMENT 该题在此试卷中的分值, sort_no INT NOT NULL DEFAULT 0 COMMENT 题目在试卷中的排序号, PRIMARY KEY (id), UNIQUE KEY uk_paper_question (paper_id, question_id), KEY idx_question (question_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci COMMENT试卷题目关联表;uk_paper_question是防止重复抽题的关键约束。组卷脚本无论执行多少遍同一道题在一张试卷里都只可能出现一次。sort_no单独存不用题目 ID 排序因为题目 ID 的插入顺序和试卷展示顺序是两回事人工调整题目顺序时只需要 UPDATE 这一行的sort_no。3.3 答题与成绩表交卷时的数据落法考试表关联试卷记录一场考试的时间窗和状态。答题记录表只存“某位考生在某场考试中对某道题的作答”成绩表存“某位考生在某场考试的最终结果”。这两个表是考试系统的核心设计时重点看唯一键和冗余策略。CREATE TABLE exam ( exam_id BIGINT AUTO_INCREMENT COMMENT 考试ID, exam_name VARCHAR(100) NOT NULL COMMENT 考试名称, paper_id BIGINT NOT NULL COMMENT 关联试卷ID, start_time DATETIME NOT NULL COMMENT 考试开始时间, end_time DATETIME NOT NULL COMMENT 考试结束时间, duration_min INT NOT NULL DEFAULT 120 COMMENT 考试时长分钟, status TINYINT NOT NULL DEFAULT 0 COMMENT 状态0草稿 1已发布 2进行中 3判分中 4已出分 5已归档, creator_id BIGINT NOT NULL COMMENT 创建人ID, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (exam_id), KEY idx_paper (paper_id), KEY idx_status_time (status, start_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci COMMENT考试表;CREATE TABLE answer_record ( record_id BIGINT AUTO_INCREMENT COMMENT 答题记录ID, exam_id BIGINT NOT NULL COMMENT 考试ID, user_id BIGINT NOT NULL COMMENT 用户ID, question_id BIGINT NOT NULL COMMENT 题目ID, user_answer VARCHAR(2000) DEFAULT NULL COMMENT 学生答案客观题存选项编号主观题存文本, is_correct TINYINT DEFAULT NULL COMMENT 客观题判分1正确 0错误 NULL待判, score DECIMAL(5,1) DEFAULT NULL COMMENT 该题得分主观题人工判分后写入, submit_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 交卷时间, PRIMARY KEY (record_id), UNIQUE KEY uk_exam_user_question (exam_id, user_id, question_id), KEY idx_exam_user (exam_id, user_id), KEY idx_question (question_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci COMMENT答题记录表;CREATE TABLE exam_result ( result_id BIGINT AUTO_INCREMENT COMMENT 成绩ID, exam_id BIGINT NOT NULL COMMENT 考试ID, user_id BIGINT NOT NULL COMMENT 用户ID, objective_score DECIMAL(5,1) NOT NULL DEFAULT 0 COMMENT 客观题得分, subjective_score DECIMAL(5,1) NOT NULL DEFAULT 0 COMMENT 主观题得分, total_score DECIMAL(5,1) NOT NULL DEFAULT 0 COMMENT 总分冗余, status TINYINT NOT NULL DEFAULT 0 COMMENT 成绩状态0待判 1已判 2已发布, submit_time DATETIME DEFAULT NULL COMMENT 实际交卷时间, finish_time DATETIME DEFAULT NULL COMMENT 判分完成时间, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (result_id), UNIQUE KEY uk_exam_user (exam_id, user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci COMMENT考试成绩表;答题记录表上的uk_exam_user_question是防重复交卷的命门。前端重复提交、接口重试、网络抖动导致的重复请求都会撞上这个唯一键配合INSERT ... ON DUPLICATE KEY UPDATE就能做到幂等写入。成绩表里的total_score是有意冗余的不为了省一条 SUM 查询而是因为成绩一旦发布后续即使题目被调整或答案被修正历史成绩也不能跟着变。成绩表是快照不是视图。4. 三处关系陷阱与索引设计为什么连表查询越来越慢结构定了真正容易埋雷的是表和表之间的关系处理。我见过太多系统上线三个月后慢查询频发问题都出在当初建表时没想清楚这三处关系。4.1 试卷和题目到底要不要中间表答案是必须拆。有些同学图省事把题目直接作为字段塞进试卷表比如question_1_id、question_2_id、question_score_1这种设计。表面上查询一张表就够实际上灾难刚刚开始。第一题目数量没法固定字段会越加越多加一次不改表第二同一道题想被两场考试复用数据得复制两份第三想统计试卷里单选、多选各占多少分要写一堆 UNION 或 CASE WHEN。中间表加上唯一键和分值字段之后这些全都变成普通查询。-- 反例把题目塞进试卷表的错误示范 CREATE TABLE paper_wrong ( paper_id BIGINT PRIMARY KEY, paper_name VARCHAR(100), question_1 BIGINT, question_2 BIGINT, question_3 BIGINT, score_1 DECIMAL(5,1), score_2 DECIMAL(5,1), score_3 DECIMAL(5,1) );一旦题目数量超过三这张表就废了。正确做法就是上一章的paper_question中间表查询某张试卷的所有题目时JOIN 后按sort_no排序即可。中间表上不要建物理外键只建普通索引。物理外键会在插入、删除时产生额外的锁检查和级联操作在线考试系统高并发交卷阶段外键就是性能杀手。完整性交给应用层事务去保证。4.2 答题记录和成绩表该冗余什么答题记录是明细成绩表是汇总。两者的字段重复是合理的exam_result.objective_score就是answer_record里某场考试某学生所有客观题得分的加总。冗余的代价是更新时必须保证一致我给判分脚本套事务明细和汇总同时更新任何一步失败整体回滚。START TRANSACTION; UPDATE answer_record SET score 3.0, is_correct 1 WHERE exam_id 1001 AND user_id 55 AND question_id 200; UPDATE exam_result SET subjective_score 3.0, total_score objective_score 3.0 WHERE exam_id 1001 AND user_id 55; COMMIT;这里的逻辑说明先更新明细再更新汇总事务保证两步的原子性。两行 UPDATE 的 WHERE 条件都带exam_id和user_id因为成绩表是按考试和学生唯一的答题记录是按考试、学生、题目唯一的。如果total_score不冗余查成绩时每次都要 SUM 所有答题记录数据量大时这条查询会非常慢。但冗余就要承担一致性的责任判分并发量高时改用批量事务每批 500 条避免长事务锁住整个表。另外注意 MySQL 默认的 REPEATABLE READ 隔离级别下事务里先 SELECT 再 UPDATE 可能产生间隙锁我一般把判分事务改成直接 UPDATE 不预查询减少锁范围。4.3 索引怎么加从 WHERE 和 JOIN 反推索引设计没有玄学我从实际查询反推。先列出系统最高频的 SQL再给表加索引。常见场景有三个查询某场考试进行中、查询某场考试某学生的答题明细、成绩发布后的分页列表。-- 场景一查询进行中的考试 SELECT exam_id, exam_name, end_time FROM exam WHERE status 2 ORDER BY start_time DESC; -- 场景二查询某学生某场考试的答题明细 SELECT question_id, user_answer, score FROM answer_record WHERE exam_id 1001 AND user_id 55; -- 场景三后台成绩分页列表 SELECT user_id, total_score, submit_time FROM exam_result WHERE exam_id 1001 AND status 1 ORDER BY submit_time DESC LIMIT 20;场景一命中的索引是idx_status_time (status, start_time)状态过滤后按时间排序左前缀原则下这是一个复合索引。场景二命中uk_exam_user_question因为最左列是 exam_id接着是 user_id查询正好用上。场景三需要(exam_id, status, submit_time)方向的复合索引但注意status区分度低放在中间会影响索引效率。实际中我会用(exam_id, submit_time)加(exam_id, status)两个索引替代让每个索引的过滤条件都有价值。给 TEXT 和 JSON 字段加索引没有意义JSON 字段查询要依赖生成列不建生成列就别指望走索引。5. 在线考试数据库设计的避坑指南五个翻车现场这一章整理的是我在考试系统数据库上真实踩过的坑每个都花了不止一个晚上排查。现象、原因、解决一条条写清楚希望后来者少走弯路。5.1 TEXT 存选项导致无法聚合统计现象想统计某道题 A、B、C、D 各被选了多少次用来分析题目难度结果 SQL 写出来慢到超时因为user_answer字段里存的是完整的选项文本而不是选项编号。 原因设计时图省事把选项内容和用户答案原样写入字符串长且重复度高GROUP BY成了全表扫描而且语义上根本没法聚合。 解决answer_record.user_answer只存选项编号单选存A多选存A,B选项的具体文本留在question.options的 JSON 里。统计时直接GROUP BY exam_id, question_id, user_answer走索引数据量再大也只是枚举值聚合。5.2 考试时间判断写在 SQL 里导致索引失效现象查询“我当前正在进行的考试”时用了WHERE now() BETWEEN start_time AND end_time数据量过万后响应时间超过一秒。 原因在时间字段上做范围比较start_time和end_time两个字段的联合判断让优化器无法稳定走索引而且当 end_time 上有函数运算时索引直接失效。 解决不在 SQL 里做时间判断考试状态由定时任务维护。每 30 秒扫描一次exam表把已到开始时间的置为 2已到结束时间的置为 3。业务查询只对status字段过滤走索引稳定高效。时间字段只用于展示和兜底校验。5.3 自动提交与事务边界混乱导致成绩错乱现象判分脚本跑了一半进程崩溃恢复后成绩表显示总分已经更新但答题记录里的明细分数还是 NULL学生成绩页面数据对不上。 原因每条 UPDATE 都是自动提交明细和汇总中间隔着网络 IO 或者计算逻辑没有事务包住一旦中断就会出现半更新状态。 解决判分任务整体包事务明细和总分一起提交或一起回滚。数据量大时按学生分批每批 500 个事务出错的批次整体重跑。重跑逻辑里用UPDATE ... WHERE score IS NULL做过滤只处理没有判过的记录天然支持断点续跑。5.4 试卷抽题和交卷同时发生导致题目重复现象某次考试学生打开试卷发现第 8 题和第 25 题一模一样系统也没有报错。 原因组卷脚本用随机抽题后循环 INSERT但没有在paper_question上建唯一键脚本被并发触发两次后插入了重复数据。 解决uk_paper_question (paper_id, question_id)唯一键兜底插入用INSERT IGNORE或者INSERT ... ON DUPLICATE KEY UPDATE遇到重复自动跳过并重新抽题。这个坑属于典型的“约束兜底”应用层再严谨也挡不住并发数据库层面的唯一键才是最后一道防线。5.5 备份恢复时外键约束导致导入顺序翻车现象把生产库备份恢复到测试环境导入时报外键约束错误恢复失败。 原因建表时为了图省事设置了物理外键恢复了 10 分钟报错说某个父表的数据还没插入。 解决生产环境不建物理外键只建普通索引完整性由应用层保证。如果老系统已经有物理外键临时恢复时在导入脚本前先执行SET FOREIGN_KEY_CHECKS 0;导入完成再恢复为 1。我个人的习惯是“物理外键只存在于文档里”表关系画在 ER 图上不写进 DDL这样备份恢复、分库分表迁移时都能少掉一半麻烦。6. 一套验收脚本用 SQL 验证数据完整性表建完不是终点上开发环境之前我会跑一组验收 SQL验证最核心的两个能力重复交卷的幂等性和报名名额的原子扣减。这两关过了基本的高并发底线就有了。6.1 用两组 SQL 验证幂等性和并发扣减幂等性验证模拟同一道题的重复提交。线上会有网络重试、用户双击交卷按钮请求打进来两次数据库必须只留一条记录。-- 模拟同一道题被重复提交两次 INSERT INTO answer_record (exam_id, user_id, question_id, user_answer) VALUES (1001, 55, 200, A) ON DUPLICATE KEY UPDATE user_answer VALUES(user_answer); INSERT INTO answer_record (exam_id, user_id, question_id, user_answer) VALUES (1001, 55, 200, A) ON DUPLICATE KEY UPDATE user_answer VALUES(user_answer); -- 验证结果应为 1而不是 2 SELECT COUNT(*) FROM answer_record WHERE exam_id 1001 AND user_id 55 AND question_id 200;逻辑说明第二次 INSERT 撞上唯一键uk_exam_user_questionON DUPLICATE KEY UPDATE变成更新操作不会新增记录。如果报错先检查唯一键是否真的建立在三个字段上。并发扣减验证用条件更新模拟报名名额。学生报名时有一条名额上限必须保证并发下不超卖。-- 模拟报名名额扣减的原子操作 UPDATE exam SET remain_seats remain_seats - 1 WHERE exam_id 1001 AND remain_seats 0; -- 影响行数为 1 表示扣减成功行数为 0 表示名额已满 SELECT ROW_COUNT();这里的逻辑说明UPDATE的 WHERE 条件里带上remain_seats 0数据库在行锁内判断并更新多并发请求不会同时扣到负数。比“先 SELECT 查询剩余名额再 UPDATE”稳得多后者在并发下必然超卖。开发环境验证时开两个终端同时执行观察影响行数之和是否等于初始名额。6.2 预留的扩展位多语言试卷与独立判分记录最后说几个我可以提前预留的扩展设计不一定现在就用但表结构上留好口子能省一次重构。多语言试卷场景下question表加一个lang_code字段试卷表记录语言策略。主观题多人阅卷场景下独立建一张判分记录表存阅卷人、得分、评语和时间别把多个阅卷人的数据塞进answer_record因为一个学生一道题可能有多个阅卷结果。考场编排和反作弊场景下建exam_session表关联考试和用户扩展登录设备、摄像头监控记录。我个人的习惯是所有建表脚本带版本号放进迁移文件不直接改生产库每次发布前跑一遍第 6.1 节的验收 SQL生产环境关闭 MySQL 的AUTOCOMMIT后所有批量写操作必须声明事务边界宁慢勿错。最后SQL_SAFE_UPDATES 在测试环境一定要开手滑全表更新这种事我犯过一次希望后来的你别再犯。希望帮到你。本文还有配套的精品资源点击获取