
简介Exam是国内首款基于Java与MySQL开发的开源网络考试系统面向教育机构、培训机构及高校教师解决在线组卷、远程考试、自动阅卷与成绩分析等核心教学管理需求。系统支持Windows与Linux双平台稳定运行具备试题库构建、试卷发布、考试监控与智能批改能力适用于K12、职业教育及企业内训等多种教育场景。资源包共318个文件含81个Java后端逻辑文件、39个JSP页面模板、49个JavaScript交互脚本、37个PNG图标资源及16个XML配置文件辅以CSS/LESS/SCSS样式、SQL数据库脚本与Spring相关配置完整呈现MVC分层架构与前后端协同逻辑压缩包仅3.34MB轻量易部署。目前已有192人学习下载提供可直接运行的工程结构、清晰的模块划分如morris图表组件集成、Eclipse项目配置文件等便于开发者快速理解考试流程实现机制、二次定制功能或迁移至微服务架构。 说实话在线考试系统这个东西我前后见过不少团队做过但大部分都停留在“能用就行”的阶段。真正把出题、组卷、考试、判分、成绩分析这一整条链路打通还能让非技术背景的老师用得顺手——这种项目市面上并不多。今天要聊的Exam就是奔着这个目标去的。它是国内首款基于Java与MySQL开发的网络考试系统Java MySQL这个组合听起来平平无奇但恰恰是它最大的优势生态成熟、上手门槛低、部署成本低中小型机构和高校拿来就能用。这套系统解决的是传统线下考试从出卷、印卷、监考到改卷的低效问题同时也给有Java基础的学习者提供了一个完整的实战项目范本。这篇东西适合两类人看一类是学校的教务老师、培训机构负责人想搞一套稳定不折腾的在线考试平台另一类是Java开发者尤其是学完Spring Boot、MySQL基础、想找个完整项目练手的朋友。你可以从里面看到整个系统从数据库设计到核心代码实现的全过程也能直接吸收我在实操中踩过的坑和优化思路。下面我就把它拆开揉碎讲一遍。1. 项目定位与技术选型为什么是Java MySQL1.1 网络考试系统到底在解决什么问题先说结论网络考试系统的核心价值不是把纸质试卷换成网页而是把整个考试流程数字化、自动化。传统线下考试的痛点组织过的人都有体会出卷难出题人要根据教学大纲反复调整题目排版、核分都是纯体力活组卷难手动编排试卷题型分布、难度比例很难精确控制监考改卷难线下考场需要大量人手主观题批改耗时特别长数据分析难成绩出来就完了没有细致的知识点掌握度分析这些痛点落在具体场景里就非常真实。比如一场两百人的期中考试从命题、印卷、布置考场、监考、收卷、批改到成绩录入一个老师全包的话至少得忙活一周而且这期间还要担心试卷泄题、装订错乱、加分算错。Exam把这一套流程压缩成了“创建考试、发链接、出成绩”三步。管理员或教师在后台配置好试卷和考试时间考生拿一个链接就能参加考试提交后客观题立即出分主观题由教师在线评阅成绩自动汇总、自动排名、支持导出。从技术实现角度看这背后涉及用户认证、题库管理、随机组卷、在线答题、自动判分、数据统计等一整套模块。选择的Java MySQL技术栈从属性和需求匹配度来说非常合适考试系统的数据模型以结构化数据为主用户、题目、试卷、成绩之间天然就是强关系MySQL这类关系型数据库能表达得清清楚楚Java则提供了足够稳定的运行时和成熟的业务框架支撑。1.2 技术栈选型的真实考量选型这件事很多人只看“流行”不看“匹配”。市面上做Web开发的语言和框架一大把Python、Node.js、Go各有拥趸但Exam最终落定在Java MySQL背后是有一套成熟逻辑的。首先Java的稳定性。考试系统最怕什么考到一半系统崩了当场翻车。Java的运行时稳定性和成熟的异常处理机制能让研发在高并发场景下更有底气。Spring Boot框架沉淀了十多年事务管理、依赖注入、安全框架都相当成熟属于“社区踩过无数坑之后沉淀下来的稳定方案”对考试系统这种业务不算复杂但对可靠性要求高的场景属于非常稳妥的选择。其次MySQL对数据一致性的支持。考试系统里的核心操作是“提交答卷”这个动作涉及多张表的写入——考试记录表、答卷明细表、成绩表。MySQL的InnoDB引擎支持完整的事务机制ACID能保证这些写入要么全部成功、要么全部失败不会出现“卷子交了但成绩没算出来”这种离谱问题。很多NoSQL数据库写起来快但要在应用层自己处理事务一致性复杂度会明显上升对一个考试系统来说属于本末倒置。再者招人和学习的成本。Exam定位是“国内首款基于Java与MySQL开发的网络考试系统”名字里就明确定义了技术方向从一开始就希望降低使用和维护门槛。Java开发者基数大学校里教、培训班讲、社区里聊的都是JavaMySQL也是几乎所有Java开发者都会接触的数据库。一个系统如果技术栈太冷门后面找人维护都是难题。受众广意味着遇到问题能搜到大量现成答案这对一个要落地长期使用的系统来说是比语言性能更重要的隐性优势。当然技术选型也有取舍。Java单体应用在极端高并发下不如Go或微服务架构能扛MySQL在超大表量下也需要分库分表或引入中间件。但对考试系统这个特定场景来说单场考试人数通常就是几百到几千Spring Boot单体 MySQL的关系型方案恰恰是性价比最高的选择。后续真需要扩展再引入Redis缓存、消息队列、读写分离也完全来得及系统架构的演进路径是顺畅的。2. 核心模块拆解与数据库设计一张表就是一门生意2.1 功能模块拆解六个核心模块各司其职Exam的功能模块我习惯拆成六块用户管理管理员、教师、考生三种角色登录认证、权限控制题库管理单选、多选、判断、填空、简答按知识点和难度维度管理试卷管理手动组卷、随机组卷试卷模板与分值设置考试管理创建考试、时间窗口设置、考试过程中断与恢复成绩管理自动判分、人工评阅、成绩排名与导出系统管理操作日志、参数配置、基础数据维护从角色视角来看各模块的使用频率和深度完全不同。管理员最关心的是用户和系统配置教师的核心工作在题库、试卷和成绩三个模块之间来回切换考生则只接触考试列表、在线答题、查看成绩这几个入口。考试系统的产品设计必须围绕这三个角色做权限隔离不然很容易出现教师误删系统参数、考生点进后台管理页之类的失控状态。每个模块都有自己的难点。这里重点说题库管理。题目不能只是“存一个字符串”必须从一开始就把题型、难度、知识点、选项、答案这几个维度设计好否则以后做组卷和成绩分析的时候会非常别扭。比如一道单选题选项可能是四个也可能是六个甚至部分语言类题目选项数量不固定所以选项不能做成option1、option2这种死板字段而要能灵活扩展。这里我建议用单独的选项表或者用JSON格式存储选项后者更灵活MySQL从5.7开始原生支持JSON类型存起来和查询都很方便。“在线考试系统里出了判断题答案就一个布尔值填空可能是多个空每空一个答案简答则需要存参考答案文字供教师比对。如果一开始不区分这些差异把所有题目的选项和答案都塞进一个TEXT字段后面做判分就要靠字符串解析硬抠维护成本会直线上升。”2.2 数据库表结构设计详解核心七张表的来龙去脉数据库设计是整个系统的灵魂。我见过不少功能看着齐全、表设计却一团糟的项目导致后面做统计、做扩展时寸步难行。Exam的表结构是经典的考试系统模型这里我把核心表列出来逐一说明表名说明关键字段sys_user用户表id, username, password, role, create_timeexam_question题目表id, type, difficulty, knowledge_point, content, options, answer, scoreexam_paper试卷表id, paper_name, total_score, duration, creator_id, create_timeexam_paper_question试卷题目关联表id, paper_id, question_id, question_order, scoreexam_info考试信息表id, exam_name, paper_id, start_time, end_time, statusexam_record考试记录表id, exam_id, user_id, start_time, submit_time, status, scoreexam_answer答卷明细表id, record_id, question_id, user_answer, is_correct, score这张表结构看起来简单但每个字段都有讲究。sys_user的role字段建议用TINYINT存储再配合Java端的枚举类做映射可读性和扩展性都更好。直接用字符串存“admin”“teacher”“student”虽然直观但一旦角色种类变多、需要加层级权限字符串维护起来非常痛苦。数据库层面用数字存储代码层面用枚举解释这是很多企业级项目的标准做法。exam_question表是题库系统的核心。type字段区分题型difficulty字段存难度等级比如1到5knowledge_point字段存知识点标签options字段用JSON格式存储选项answer字段对客观题存答案标识、对主观题存参考答案文字。这里要注意选项的JSON结构最好一开始就定好比如统一是[{“key”: “A”, “content”: “xx”}, ...]这种格式后面渲染试卷和判分都能直接复用。exam_paper_question是典型的关联表把“试卷”和“题目”两个实体解耦。为什么要单独一张表因为同一道题可能出现在多张试卷里一张试卷也会有多道题这是多对多关系必须由关联表承接。表里的question_order字段存储题号顺序score字段存储该题在此试卷中的分值。同一个题目在不同试卷里分值不同是完全正常的所以分值必须放在关联表里而不能放在题目表里。exam_record表里要加一个status字段标记考试是“进行中”还是“已提交”。这个字段直接决定了考生能不能再次进入答题页面是防重复提交的数据库层保障。索引设计也要提一下。考试系统的查询热点集中在考生查自己的考试记录、教师查某场考试的成绩列表、分页查询题目列表。这几个查询都要建好索引。举例来说exam_record表建(user_id, exam_id)联合索引exam_answer表建(record_id)索引题目表按(knowledge_point, type)建索引。这样后面的成绩分析和随机组卷都会快很多。有一点提醒不要在业务系统里滥用存储过程。见过一些老系统喜欢把判分逻辑写进MySQL存储过程这种设计在考试系统里非常坑——逻辑一复杂存储过程调试、版本管理都很难受而且一旦从MySQL迁移到其他数据库存储过程的兼容性问题会让人头大。判分这种业务逻辑放在Java服务层做数据一致性问题用事务解决这才是正确姿势。存储过程更适合处理纯数据库内部的批量任务不适合承载核心业务规则。2.3 试卷生成与自动判分最核心的两个功能点现在重点聊试卷生成和自动判分。这两个功能是网络考试系统区别于“纸质试卷上云”的关键也是面试时最容易深挖的细节。试卷生成有两种模式手动组卷教师在题库里勾选题目设置每题分值系统按指定顺序生成试卷。这个最简单本质上就是往exam_paper_question关联表里批量插数据。随机组卷教师设置规则——比如“单选题10道、多选题5道、判断题5道、简答题2道知识点覆盖第三、四章难度比例简单:中等:困难5:4:1”。系统按规则从题库里随机抽取题目组成试卷这是考试系统体验的分水岭。随机组卷的SQL实现我建议分两步走先按条件过滤出候选题目池SELECT id FROM exam_question WHERE knowledge_point IN (...) AND type 1 AND difficulty 1在应用层对ID列表做随机打乱取前N个为什么不在SQL里用ORDER BY RAND() LIMIT n因为RAND()会对目标范围的每一行都计算随机值再排序数据量上了5万这个查询就会从毫秒级变成秒级。考试高峰期如果多个教师同时触发随机组卷数据库CPU会直接飙红。在应用层对几千个ID做Collections.shuffle()速度是微秒级而且代码可读性更高。这是MySQL优化里非常经典的一个案例也是我在项目中反复强调的原则能用应用层简单解决的问题就不要丢给数据库去做高开销操作。自动判分逻辑客观题选择、判断、填空可以自动判主观题简答、论述需要人工评阅。客观题判分实现单选题user_answer.equals(correct_answer) ? score : 0多选题默认采用“完全匹配才得分”的策略因为部分给分规则漏选给几分、错选是否扣分在业务上容易引发争议如果非要支持务必在创建试卷时就明确规则并写入数据库判断题布尔值比较注意前端传参的格式统一填空题字符串比较首尾空格要trim掉多个空的按分隔符拆开逐空比对主观题评阅教师登录后在待评阅列表里看到考生答案参照参考答案手动打分系统把分数累加到总分里。最终成绩计算有个特别要注意的细节总成绩 客观题得分 主观题得分必须在所有题目都判完后才更新exam_record的score字段否则可能出现“总分已出但主观题还是零分”的尴尬情况。这块用事务包起来在Java服务层控制。顺带说一句在判分逻辑里Java枚举类型的使用价值非常大。定义一个QuestionType枚举SINGLE_CHOICE、MULTIPLE_CHOICE、JUDGE、FILL_BLANK、ESSAY判分策略按枚举分支处理代码比switch字符串清爽得多。如果你正在刷Java面试题枚举 策略模式怎么搭配这正好是一个可以写进简历的真实业务场景。3. 从零到一环境搭建与核心功能实操3.1 开发环境准备JDK、MySQL、连接工具的坑先讲环境搭建。Exam基于Java和MySQL所以开发环境的准备分三块JDK、MySQL数据库、开发工具和连接工具。JDK安装与环境变量配置这一步看似基础但真有不少人卡在这里。安装JDK之后必须配置JAVA_HOME、PATH、CLASSPATH三个环境变量。JAVA_HOME指向JDK安装目录比如C:\Program Files\Java\jdk-17PATH里添加%JAVA_HOME%\binCLASSPATH配置为.;%JAVA_HOME%\lib。配置完成后在命令行执行java -version能正常输出版本号就说明环境变量配置成功。这里有个很常见的坑如果电脑上装了多个JDK版本命令行里java -version显示的版本可能不是你期望的那个。这通常是PATH里JDK路径的顺序问题把想用的版本路径往前放就行。另外Spring Boot 3.x要求JDK 17但很多老项目或者教学项目还停留在JDK 8两个版本并存时IDE和Maven的编译级别一定要保持一致。新手经常遇到“java: 警告: 源发行版 17 需要目标发行版 17”的报错本质就是IDE的JDK版本和Maven设置不一致。MySQL安装与版本选择MySQL的安装分Windows本地开发和Linux服务器部署两种场景。Windows下安装就是下载安装包或ZIP压缩包一路下一步ZIP版需要手动初始化核心几步是解压后创建my.ini配置文件、以管理员身份运行mysqld --initialize-insecure、安装服务mysqld --install、启动服务net start mysql。Linux下更推荐用包管理器或者二进制包安装比如CentOS/Rocky下用rpm包Ubuntu下用apt install mysql-server。版本选择上如果条件允许建议优先考虑MySQL 5.7或8.0。5.7是老牌稳定版本资料多很多老系统都跑在它上面8.0性能更好、JSON支持更完善但要注意8.0默认的密码加密方式和老版本不同老客户端连接时会报认证插件不兼容——这就是广为人知的“mysql连接2059错误”。解决方法是在登录后执行下面这条SQLALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码;或者干脆升级客户端驱动。另外MySQL 8.0驱动类的包名和连接URL也有变化写JDBC连接串的时候要用com.mysql.cj.jdbc.DriverURL里还要加serverTimezoneAsia/Shanghai参数不然会报时区错误。数据库连接工具我平时习惯用Navicat for MySQL图形化界面方便建表、导数据、看执行计划都很快。如果你不想装商业软件用MySQL官方的MySQL Workbench也够用。Workbench里面可以直接打开SQL编辑器敲CREATE TABLE语句快速用命令行方式新建数据表。这里提醒一点无论用哪种工具数据库字符集务必统一用utf8mb4不是utf8。utf8在MySQL里最多支持3字节Emoji字符和一部分生僻字存不进去学员昵称或者题目解析里一旦出现这类字符直接报错。utf8mb4才是真正完整的UTF-8实现。3.2 核心业务代码实现要点认知、权限、事务一个不能少现在说代码实现。Exam的核心后端逻辑用Spring Boot MyBatis这套主流方案来演示这是目前Java Web开发最常见的技术组合。项目结构上按职责分层exam-plus/ ├── src/main/java/com/exam/ │ ├── controller/ // 控制层 │ ├── service/ // 业务层 │ ├── mapper/ // 数据访问层 │ ├── entity/ // 实体类 │ ├── dto/ // 数据传输对象 │ └── config/ // 配置安全、跨域、拦截器 └── src/main/resources/ ├── mapper/ // MyBatis XML └── application.yml用户登录与权限拦截做用户认证时我推荐用JWTJSON Web Token 拦截器的方式原因很简单前后端分离无状态认证扩展性好。考生登录成功后服务端签发一个token前端存在本地后续请求的Header里带上token后端拦截器校验token有效性。如果项目是前后端分离的Vue Node环境也可以让前端Node层做一个转发代理后端只认token就行。拦截器里按角色做权限判断——管理员、教师、考生三种角色能访问的接口完全不一样。考生不能调创建试卷的接口教师不能删除用户。这种权限控制如果做得不够细系统上线后就会变成“谁都能考但谁也能乱改题库”的失控状态。我见过有系统只做了登录校验、没做角色区分结果考生直接拼URL访问后台接口把题库给改了这种事故上线前一定要堵住。在线考试的时间控制考试系统最核心的并发场景就是在线答题。考生进入考试时服务端记录开始时间考生提交时服务端判断当前时间是否超过考试截止时间。关键细节前端倒计时只能做展示真正的计时必须以服务端时间为准。前端时间可以被用户自己改的把截止时间判断放在后端才能防住简单的作弊手段。答题过程中每道题的答案要实时保存还是最后统一提交这取决于产品设计。实时保存的好处是防丢失坏处是数据库写压力大统一提交的好处是简单坏处是一旦断网答了半天全丢。我的建议是折中每道题切题时做一个防抖保存最后提交时再做一次完整校验。这样既不会频繁写库也能最大限度减少数据丢失。答题提交的事务处理这是整个项目里最容易写崩的地方。一次交卷动作涉及更新exam_record状态为“已提交”批量插入exam_answer答卷明细计算客观题得分更新exam_record的score字段这三个操作必须放在一个事务里任何一个失败都要回滚。用Spring的Transactional注解包在service方法上就行。这里我要特别提醒事务里不要做耗时操作比如发邮件通知、推送WebSocket消息、生成PDF报告这些一定要挪到事务提交之后做。否则事务长时间占用数据库连接高并发下一会儿就把连接池打满了系统表现为“越用越慢、最后卡死”。现代Java开发中lambda表达式和函数式接口用得越来越多。比如在判分逻辑里把不同的判分策略做成一个MapQuestionType, Function用lambda去取策略代码会特别清爽这也正是Java 8之后的核心编程风格转变。如果你在学Java这段代码非常值得动手敲一遍。3.3 部署上线与Linux环境踩坑记录项目写完之后真正的考验才开始部署。先说构建。Spring Boot项目用Maven打包执行mvn clean package -DskipTests生成一个可执行的jar包。这个jar包内嵌了Tomcat丢到服务器上直接java -jar exam-plus.jar就能启动不需要单独装Tomcat。如果不想用内嵌容器也可以打war包部署到外部Tomcat但对于这种轻量级项目没必要多此一举。Linux服务器上MySQL的启动常见问题很多。如果是RPM或apt方式安装的MySQL用systemctl start mysqld启动如果是二进制包解压安装的启动命令通常是bin/mysqld_safe --usermysql 。启动完成后用mysql -u root -p登录第一次登录可能要查临时密码——在RPM安装方式下临时密码会写进/var/log/mysqld.log可以用下面的命令查grep temporary password /var/log/mysqld.log我强烈推荐用Docker来部署MySQL能省掉大量环境配置的痛。一条命令就能拉起一个带指定字符集的数据库实例docker run -d --name mysql-exam -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD你的密码 \ -e MYSQL_DATABASEexam_db \ mysql:5.7 \ --character-set-serverutf8mb4 \ --collation-serverutf8mb4_unicode_ci注意这里通过--character-set-server和--collation-server参数直接指定字符集和排序规则避免建库之后字符集不对再改的麻烦。用Docker部署的好处是换一台服务器同样的命令再来一遍环境完全一致不会出现“我本机好好的到服务器就挂了”这种玄学问题。当然如果你偏好传统方式用二进制包在Linux上安装MySQL也是常规操作注意初始化参数文件和数据目录权限启动步骤网上都有完整教程。部署时还有一个高频坑防火墙和云安全组。服务器上MySQL端口3306和应用端口8080必须放行否则外部访问不通。很多新手在本地写得好好的一上云服务器就“连不上数据库”“页面打不开”十有八九就是忘了放行端口。如果项目用了前后端分离架构前端用Vue构建后通过Nginx部署静态文件再配置反向代理把/api开头的请求转发到Java后端的8080端口。Nginx的配置核心就是一段location规则location /api/ { proxy_pass http://127.0.0.1:8080; }这里要注意proxy_pass末尾有没有斜杠有斜杠和没有斜杠转发的路径拼接结果完全不一样很容易踩坑。例如不带末尾斜杠时会把完整路径传过去带斜杠时则可能剥离部分前缀实际项目中建议在测试环境先验证一次。4. 常见问题排查与性能优化实录4.1 高频报错速查表照着定位思路清晰实操过程中我整理了一个常见问题速查表基本覆盖了新人从搭建到上线全流程会遇到的高频问题报错/问题原因解决办法MySQL 2059错误客户端与MySQL 8.0认证插件不兼容修改用户认证方式为mysql_native_password或升级驱动Command denied to user数据库账号权限不足使用GRANT命令授予对应权限MySQL锁表lock wait timeout事务未提交或长时间占用行锁检查未提交事务优化事务内耗时操作java: 警告: 源发行版 17 需要目标发行版 17IDE和Maven的JDK版本不一致统一project SDK和Maven的Java版本Lombok报错you arent using a compiler supported by lombokLombok版本和JDK版本不兼容升级Lombok版本或降低JDK版本OutOfMemoryError: insufficient memoryJVM堆内存不足调整-Xmx参数检查是否存在内存泄漏中文乱码连接串/表/代码字符集不一致统一utf8mb4URL加characterEncodingutf8这里专门展开说两个高频坑。第一个是MySQL 2059错误。MySQL 8.0改了默认的认证插件老版JDBC驱动连接时会直接报错。解决办法有两个方向要么改MySQL端用户认证方式要么升级MySQL驱动到8.0以上。我建议优先升级驱动因为改认证方式只是暂时兼容治标不治本。第二个是MySQL锁表。考试高峰期多个考生同时提交答卷如果某个事务没提交就抛异常退出对应的行锁会被一直占着其他事务就会卡在lock wait timeout。排查方法执行SHOW PROCESSLIST;找到Sleep状态的陈旧连接并kill掉。更彻底的办法是检查代码里Transactional注解的方法确保事务边界合理、异常能正常回滚。还有一个实用技巧事务里尽量不要查全表查询尽量走索引锁的粒度越小并发冲突越少。顺带说两个SQL小知识点都是我在帮别人review代码时发现的共性问题LIMIT语法LIMIT n是取前n条LIMIT offset, n才是分页。很多新手把LIMIT 10, 20当成“取10到20条”实际意思是“跳过10条取20条”。分页时更推荐LIMIT offset, size写法或者配合ORDER BY字段精确排序。OR操作符的去重问题SQL里的OR本身不去重。如果你想查“知识点是A或者B的所有题目”直接写WHERE point A OR point B在结果集存在重复数据时需要加DISTINCT或者改写成INA, B。表结构合理的情况下IN写法更清晰也更容易走索引。4.2 高并发答题场景的性能优化三层递进思路聊性能优化之前先看你系统的目标并发量。考试系统的典型瓶颈不在“登录”而在“批量提交答卷”的那一瞬间几百个考生同时点提交每份答卷要往数据库插几十条答案记录这个写入压力非常集中。我的优化思路分三层第一层数据库连接池。HikariCPSpring Boot 2.x后的默认连接池默认最大连接数是10并发一上来很快就不够用了。适当调大比如maximum-pool-size50但要结合服务器内存来调连接数是会占内存的不要盲目往大调。4G内存的机器连接池50还扛得住2G内存的机器建议控制在30以内。第二层SQL批量插入。考生提交答卷时几十道题的答案不要一条一条insert用MyBatis的foreach标签一次性批量插入可以减少几十倍的数据库交互次数。这里有个细节批量插入的SQL大小要控制一次插入50条左右比较合理如果一次性几千条SQL语句过长反而会拖垮数据库。第三层异步化。提交答卷的核心事务完成后像“发送考试完成通知”“生成成绩报告PDF”这种非核心操作扔到线程池或消息队列里异步处理不要让用户等待这些额外操作完成。Java里用Async注解或手动ExecutorService都能实现。关于缓存考试系统里最适合加Redis缓存的场景是“考试基本信息”和“热门题目静态数据”比如试卷名称、考试时间段、考生已参加的考试列表这些读多写少的数据。但有一点必须强调考生的答案和成绩这类数据绝不能只放缓存不落库考试数据的安全性大于一切。Redis里丢了还能从MySQL恢复MySQL里丢了就是事故。另外题目列表的分页查询在数据量大时也容易慢。优化方案除了加索引还可以考虑用覆盖索引查询的所有字段都在索引里减少回表再高级一点的做法是“延迟关联”先查ID列表再用ID关联查详情。这些经验用到实际项目里面试时聊MySQL优化也有现成素材。4.3 数据一致性与并发安全别让考试数据出错最后聊一个容易被忽视但极其重要的点数据一致性。考试系统的数据一致性风险主要在三个地方第一重复提交。考生可能双击提交按钮也可能交完卷又点了一次。后端必须做幂等处理。我的做法是在exam_record表加一个唯一约束(exam_id, user_id)利用数据库的唯一索引从物理上杜绝同一个考生在同一场考试里产生多条记录。业务层再配合校验如果该记录状态已经是“已提交”直接返回“请勿重复提交”。第二成绩汇总的原子性。总分 客观题自动判分 主观题人工评分。如果多个人同时改同一份试卷或者自动判分和人工评分并发更新score字段就可能出现成绩被覆盖。解决方式所有更新score的操作先加行锁用SELECT ... FOR UPDATE保证同一时间只有一个线程在改这个分数更新完再提交事务释放锁。第三考试时间边界。考试结束时间到了但还有考生在答题。这里的策略取决于业务规则一种是在结束时间后立即锁定试卷未提交的自动提交并计算已答题目得分另一种是给一个宽限期。不管是哪种都必须保证“结束时间判断”发生在服务端而不是依赖前端倒计时。前端可以提示但强制逻辑必须后端做主。再补充一个MySQL优化里的基础知识点。我见过有人写SELECT id FROM exam_question WHERE id 5 100这种在WHERE条件里对字段做运算的写法会导致索引失效因为MySQL无法直接使用id列上的索引来加速“id 5 100”这个条件。正确写法是把条件改写成id 95让索引生效。这是MySQL优化的一个基础规范但对查询性能的影响非常实在。关于MySQL排序做成绩排名时要注意如果字段类型是VARCHAR存的数字排序结果会按字典序排而不是按数字大小排比如1、10、2。所以设计成绩字段时一定要用INT或DECIMAL类型不要在数据类型上犯低级错误。如果历史数据已经存了VARCHAR用ORDER BY CAST(本文还有配套的精品资源点击获取