尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

MySQL实战:从零搭建学生选课库,搞定建库建表与存储过程

发布时间:2026/9/26 5:55:14

资讯中心
01
ARTICLE

MySQL实战:从零搭建学生选课库,搞定建库建表与存储过程

MySQL实战:从零搭建学生选课库,搞定建库建表与存储过程
1. 第一次MySQL作业从零搭一个学生选课库前几天部门来了个实习生我给他布置了入职后的第一个正式任务在一台全新的Linux服务器上从安装MySQL开始到建库建表、写增删改查、再搞一个存储过程最后交一份完整的选课系统数据库出来。这个任务听起来简单但真正走一遍会发现坑全埋在细节里——比如socket连不上、UPDATE语法写错、存储过程里的分号导致报错每一个都能把人卡住半天。这篇文章就把这份“MySQL第一次作业”的完整过程拆给你看。不管你是刚入行的开发新人还是准备带新人的老手照着这个流程走一遍MySQL的日常操作基本就过关了。文中涉及的所有命令和SQL我都标注了版本和适用环境用的是MySQL 8.0版本操作系统为CentOS 7以上的Linux发行版。如果你用的是Windows或macOS安装部分我会额外说明差异。作业目标我设成了这样做一个简化版的学校选课系统包含学生表、课程表、选课记录表三张核心表要求完成建库、建表、插入数据、按条件查询、修改数据、删除数据、编写存储过程并且全程记录遇到的问题和解决方法。这个范围刚好覆盖了MySQL最常用、面试最容易问到的知识点。先看整体流程总共分六个阶段安装MySQL、连接与建库、建表与约束设计、增删改查、存储过程、问题排查。下面我按实际操作的顺序一步步来讲。2. 安装这一步为什么推荐官网Yum仓库而不是sudo apt很多新手装MySQL习惯直接sudo apt install mysql-server然后在Debian系机器上装完发现连mysqld都找不到或者装了个MariaDB的兼容分支版本和官方不一致后面做JSON字段、窗口函数时各种报错。我第一次带人做作业就吃了这个亏所以我现在的建议是一律从官方仓库装或者用Docker起官方镜像二选一别用发行版自带的。CentOS/RHEL系统官方推荐的安装方式是先加MySQL官方Yum仓库# 下载并安装MySQL官方仓库rpm包 wget https://dev.mysql.com/get/mysql80-community-release-el7-7.noarch.rpm yum localinstall mysql80-community-release-el7-7.noarch.rpm -y # 安装MySQL社区版服务端 yum install mysql-community-server -y # 初始化并启动服务 systemctl start mysqld systemctl enable mysqld装完之后第一件事是找临时密码grep temporary password /var/log/mysqld.log这个临时密码是MySQL 8.0安全策略的一部分首次登录会让你强制修改。登录命令是mysql -uroot -p输入上面日志里查到的临时密码进入MySQL后立刻执行ALTER USER rootlocalhost IDENTIFIED BY Your_Strong_Password_2025;注意这里密码必须够强MySQL 8.0默认开了validate_password组件要求至少8位且包含大小写字母、数字和特殊字符。如果不想这么麻烦可以临时降低密码策略再改回SET GLOBAL validate_password.policy LOW; SET GLOBAL validate_password.length 6; ALTER USER rootlocalhost IDENTIFIED BY 123456; SET GLOBAL validate_password.policy MEDIUM;如果你用的是Docker那就更省事一条命令拉起来docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -e MYSQL_DATABASEschool \ mysql:8.0这里要提醒一个常见坑Docker方式启动时容器内部的MySQL默认只监听localhost如果外部客户端连不上大概率是因为没有指定--networkhost或者没做端口映射。上面命令里我写了-p 3306:3306这一步不能省。另外数据持久化一定要挂载数据卷否则容器一删数据全没了docker run -d --name mysql8 \ -p 3306:3306 \ -v mysql-data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD123456 \ mysql:8.0安装完成后顺手验证一下版本SELECT VERSION();输出应该是8.0.x到了这一步环境就算是准备好了。3. 第一次连上MySQL你能遇到的最典型的三个“连不上”作业做完环境搭建接下来要面对的坑几乎都集中在“连不上”这三个字上。我总结了一下我带人过程中见过最高频的三种情况每种我都附了排查思路你照着做就行这些错误在热搜词里也出现了好几个。3.1 Error 2002: Cant connect through socket新手执行mysql -uroot -p时看到下面这行报错会特别懵ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock (2)这句话翻译过来就是MySQL客户端想通过/tmp/mysql.sock这个本地socket文件连到服务端但找不到这个文件。(2)表示文件不存在错误码2在Linux里对应ENOENT。出现这个报错通常是两个原因一是MySQL服务根本没启动二是socket文件路径不对。先排除第一个原因systemctl status mysqld如果服务没启动直接systemctl start mysqld再登录一次就好了。如果服务已经启动了还是报socket不存在那就用TCP方式连一次试试mysql -uroot -p -h127.0.0.1 -P3306用-h127.0.0.1时客户端会走TCP协议而不是socket文件。如果能连上说明socket路径配置和生产不一致修改/etc/my.cnf里的socket行即可。如果TCP也连不上检查端口ss -lntp | grep 3306没有输出说明mysqld根本没监听3306八成是配置文件的bind-address设置问题。3.2 Navicat连接报错密码插件不兼容服务器上mysql -uroot -p能进本地Navicat却连不上这是第二个高频问题。MySQL 8.0默认的认证插件是caching_sha2_password而老版本Navicat12.0以下只支持mysql_native_password两边对不上自然连不上。解决方式有两种。第一种是改用户的认证插件ALTER USER root% IDENTIFIED WITH mysql_native_password BY 123456;第二种更推荐——升级Navicat到16以上版本全系列都支持caching_sha2_password了。3.3 JDBC连接报SSL错误如果作业里要求写Java程序连MySQL你大概率会看到下面这行报错java.sql.SQLException: SSL connection error: java.net.SocketException: Broken pipeMySQL 8.0默认开启SSL但自签名证书在JDBC中的校验经常出问题。对作业场景来说直接在JDBC连接串后面加useSSLfalse就能绕过去jdbc:mysql://127.0.0.1:3306/school?useSSLfalseserverTimezoneAsia/Shanghai如果你确实想用SSL连接需要把CA证书导入Java信任库步骤比较繁琐作业阶段不建议碰。当然生产环境SSL是必开的我的意思是别在第一次作业上死磕这个先保证程序能跑通。4. 建库建表类型选错是后面所有麻烦的起点连上MySQL之后正式开始做作业里的核心部分。先建库CREATE DATABASE IF NOT EXISTS school DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;utf8mb4比utf8多支持四字节的Emoji字符和生僻字现在的MySQL项目我基本全部用utf8mb4不要在作业阶段为了省空间用utf8mb3或latin1。排序规则选utf8mb4_general_ci就够了追求精确排序可以用utf8mb4_unicode_ci但对选课系统这个场景来说没差别。接下来建三张表。学生表CREATE TABLE student ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender ENUM(M,F) DEFAULT M COMMENT 性别, birth_date DATE COMMENT 出生日期, phone VARCHAR(20) COMMENT 手机号码, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB COMMENT学生表;课程表CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 课程编号, course_name VARCHAR(100) NOT NULL COMMENT 课程名称, credit DECIMAL(3,1) DEFAULT 2.0 COMMENT 学分, teacher_name VARCHAR(50) COMMENT 授课教师 ) ENGINEInnoDB COMMENT课程表;选课记录表CREATE TABLE student_course ( id BIGINT PRIMARY KEY AUTO_INCREMENT, student_id BIGINT NOT NULL COMMENT 学生ID, course_id INT NOT NULL COMMENT 课程ID, score DECIMAL(5,2) DEFAULT 0 COMMENT 成绩默认0, selected_at DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_sc_student FOREIGN KEY (student_id) REFERENCES student(id), CONSTRAINT fk_sc_course FOREIGN KEY (course_id) REFERENCES course(id), UNIQUE KEY uk_student_course (student_id, course_id) ) ENGINEInnoDB COMMENT选课记录表;这里有几个关键设计点值得说明score字段我特意设置了默认值0对应热搜词里的“mysql设置默认值为0”。选课之后成绩还没出先存0是合理的业务建模以后查“选了课但没成绩”的学生就是WHERE score 0。外键约束FOREIGN KEY在作业里必须有面试官问“为什么用外键”的时候标准回答是保证数据完整性防止插入一个不存在的学生ID。同时也要知道外键对写入性能有影响生产环境大表往往不用物理外键靠应用层或定时任务保证这是个加分的延伸知识。student_id course_id加了唯一键业务含义是“一个学生同一门课只能选一次”这个约束如果漏了后面写插入语句时就会出现重复选课数据。表结构建好后验证一下SHOW CREATE TABLE student\G; DESC student_course;把输出截图存进作业文档里这步能帮你确认每张表的字段类型、默认值、约束是否符合预期。我第一次带人时他建完表直接开始插入数据结果插到一半发现course表里少了个字段又得删表重建前面的数据全白费了。建表之后先验证结构再动数据。5. 插入数据与UPDATE语法踩坑最多的地方结构没问题接下来造数据。给三张表各插几条INSERT INTO student (name, gender, birth_date, phone) VALUES (张三, M, 2000-01-15, 13800000001), (李四, F, 2001-06-20, 13800000002), (王五, M, 2002-03-12, 13800000003); INSERT INTO course (course_name, credit, teacher_name) VALUES (高等数学, 5.0, 陈老师), (大学英语, 3.0, 刘老师), (数据库原理, 4.0, 赵老师); INSERT INTO student_course (student_id, course_id, score) VALUES (1, 1, 85.5), (1, 2, 0), (2, 1, 92.0), (2, 3, 76.5), (3, 2, 0);注意第二条选课记录和最后一条的score故意保持0为的是后面演示“查询未出成绩的学生”这个场景。然后进入作业里最容易出错的部分UPDATE语法。先看正确写法-- 把张三的数据库成绩改成88 UPDATE student_course SET score 88 WHERE student_id 1 AND course_id 3; -- 把李四的英语成绩赋值为默认值 -- 注意这里没有WHERE会更新所有行作业里一般不会这么玩 UPDATE student_course SET score DEFAULT;新手最常见的错误有两个。第一个是忘记写WHERE直接把整张表的成绩全改了等发现的时候已经晚了。第二个是微软风格写法UPDATE student_course SET score 88, student_id 1 WHERE course_id 3这种在MySQL中完全合法但会把student_id错改。要避免这类问题就一个习惯写UPDATE先写WHERE再写SET。这个习惯我在生产环境也一直用改数据前先跑一遍SELECT确认影响行数再执行UPDATE。我刚才建表时把score默认值设成0可以用在查询里-- 找出所有选了课但还没有成绩的学生 SELECT s.name, c.course_name FROM student s JOIN student_course sc ON s.id sc.student_id JOIN course c ON c.id sc.course_id WHERE sc.score 0;这条JOIN查询就是作业里的核心考点之一了三表关联查每一行都要说清楚为什么用JOIN而不是子查询。JOIN在MySQL里优化的空间更大数据量大时通常比逐行子查询快。6. 排序、日期处理与存储过程作业里最见功力的部分6.1 ORDER BY与字符串转日期排序是MySQL的入门高频点。按成绩从高到低排SELECT s.name, c.course_name, sc.score FROM student s JOIN student_course sc ON s.id sc.student_id JOIN course c ON c.id sc.course_id ORDER BY sc.score DESC;如果要查某门课成绩排名前二的学生加个LIMIT 2。面试喜欢问“ORDER BY到底按什么规则排序”其实默认升序ASC字符按排序规则的二进制比较数值按数值大小NULL总是排在最前面升序时。日期处理这块也是热搜词里高频项。假设作业给的数据里日期是字符串格式2025-01-15要转成日期类型才能和birth_date比较可以用-- 字符串转日期 SELECT STR_TO_DATE(2025-01-15, %Y-%m-%d); -- 日期转字符串 SELECT DATE_FORMAT(birth_date, %Y年%m月%d日) FROM student; -- 年龄计算 SELECT name, TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) AS age FROM student;STR_TO_DATE和DATE_FORMAT是两个方向相反的函数一个是字符串进、日期出一个是日期进、字符串出。第一次写的人经常会搞混参数顺序我见过好几个新手把格式化字符串放在前面导致查不出数据。记住STR_TO_DATE(字符串, 格式)是去解析字符串DATE_FORMAT(日期, 格式)是去格式化日期。6.2 存储过程分号处理是核心存储过程是这份作业的压轴任务代码本身倒不难难的是理解MySQL客户端的分号分隔规则。先看这段代码DELIMITER $$ CREATE PROCEDURE sp_add_score( IN p_student_id BIGINT, IN p_course_id INT, IN p_score DECIMAL(5,2) ) BEGIN DECLARE v_exists INT DEFAULT 0; SELECT COUNT(*) INTO v_exists FROM student_course WHERE student_id p_student_id AND course_id p_course_id; IF v_exists 0 THEN INSERT INTO student_course (student_id, course_id, score) VALUES (p_student_id, p_course_id, p_score); ELSE UPDATE student_course SET score p_score WHERE student_id p_student_id AND course_id p_course_id; END IF; END$$ DELIMITER ;重点在第1行和第19行。MySQL客户端默认把;当作一条语句结束的标记而存储过程体内部有很多分号如果不把分隔符临时改成$$客户端会在DECLARE那行就截断过程体然后报一堆语法错误。这个DELIMITER就是热搜词里“mysql中触发器中分隔符”指的核心点写触发器、存储过程、事件调度器时全都绕不开。写完存储过程后调用CALL sp_add_score(3, 1, 66.5);然后查一下SELECT * FROM student_course WHERE student_id 3;如果用DBeaver或Navicat这类GUI工具存储过程体里的分号不需要手动改分隔符工具内部会处理。但命令行模式下不改DELIMITER一定报错这个区别值得在作业总结里写一笔能体现你真正实践过。6.3 存储过程中的错误处理作业进阶一点可以在存储过程里加异常处理。比如成绩不在0到100之间就抛个错误DELIMITER $$ CREATE PROCEDURE sp_add_score_safe( IN p_student_id BIGINT, IN p_course_id INT, IN p_score DECIMAL(5,2) ) BEGIN DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; SIGNAL SQLSTATE 45000 SET MESSAGE_TEXT 成绩无效或插入失败; END; START TRANSACTION; IF p_score 0 OR p_score 100 THEN SIGNAL SQLSTATE 45000 SET MESSAGE_TEXT 成绩必须在0-100之间; END IF; INSERT INTO student_course (student_id, course_id, score) VALUES (p_student_id, p_course_id, p_score) ON DUPLICATE KEY UPDATE score p_score; COMMIT; END$$ DELIMITER ;这里用到了三个新知识点DECLARE EXIT HANDLER声明异常处理器SIGNAL主动抛出错误ON DUPLICATE KEY UPDATE处理唯一键冲突。如果你第一次接触这些概念建议把上面这段逐行读明白它能覆盖MySQL面试题里存储过程相关的六成题型。从“三建”到“一过程”作业的数据操作部分就完整了。到这里已经能交一份很体面的作业了但既然说了要做一个好project下面的性能优化是加分项。7. 作业提交前的自查索引与SQL调优作业要拿高分只把功能跑通是不够的。我让每个新人在提交前都做一遍自查查三件事索引、执行计划、死锁处理。7.1 索引什么时候该建什么时候不该建student_course表上已经有唯一索引uk_student_course它同时覆盖了student_id和course_id两个列所以按student_id查选课记录时能直接走索引。但如果你经常按course_id查有哪些学生选了课这个索引帮不上忙因为唯一索引的最左前缀原则要求查询条件必须包含student_id。这时候就需要单独建一个索引CREATE INDEX idx_course_id ON student_course(course_id);用EXPLAIN来验证一下EXPLAIN SELECT * FROM student_course WHERE course_id 2;type列的ref和key列的idx_course_id说明走了索引。如果看到的type是ALL表示全表扫描数据量一大就会慢。第一次作业能会用EXPLAIN看执行计划已经超越绝大多数新人了。但索引也不是越多越好。写过一篇博客讲过“索引是拿空间换时间”每多一个索引写入时就要多维护一棵B树所以只为高频查询列建索引别把表里每个字段都套上。7.2 数据量一大为什么查询慢从全表扫描到索引覆盖给学生表多插10万条模拟数据然后看一条查询SELECT COUNT(*) FROM student WHERE birth_date 2001-01-01;在没有任何索引的情况下这个COUNT(*)需要扫全表。如果在birth_date上加索引CREATE INDEX idx_birth_date ON student(birth_date);再看执行计划会发现优化器选择走索引扫描行数大幅下降。这就是为什么热词里会有“mysql性能调优”——很多情况下慢查询不是SQL写错了而是没有合适的索引。推动这一步的意义在于第一次作业往往表里就几行数据什么SQL都快一旦上了生产几百万行数据才见真章。所以作业阶段养成EXPLAIN的习惯等遇到真正的慢查询时你已经知道该从哪里入手。7.3 锁与死锁为什么你的UPDATE卡住了另一个高深考点是锁。热搜词里出现过“mysql show full processlist killed”这也确实是线上诊断必备技能。如果某个更新语句一直执行不完很可能是有别的事务没提交拿着行锁不放。这时候执行SHOW FULL PROCESSLIST;能看到每个连接的当前状态。如果某个连接State是Waiting for table metadata lock或updating可以确认它在等待锁。处理方式是-- 根据processlist查到的id杀掉阻塞源 KILL 12345;KILL是最后手段生产环境要谨慎使用可能造成事务回滚。但对第一次作业来说能用SHOW FULL PROCESSLIST观察SQL执行状态、理解锁的概念已经是超出作业要求的程度了。再提一个面试常问点InnoDB的行锁不是“只锁一行”而是基于索引加锁。如果你更新时WHERE条件没走索引InnoDB会升级成锁全表记录这就是网上常说的“不通过索引更新行锁变表锁”。我在作业文档里让学生做过一次实验先不加索引执行更新开另一个会话更新不同ID的记录结果被卡住加索引后再试同样的操作流畅通过。这个实验做一遍对锁的理解会非常深刻。8. 别只看MySQL本身让作业体现“全栈视野”一个让人印象深刻的作业不会只有表和SQL还应该体现你对整个技术栈的理解。我让学生至少做下面三件事之一第一用JDBC写一个小Java程序完成“输入学生姓名查询他所有选的课和成绩”这个功能。涉及的关键点包括DriverManager、PreparedStatement、ResultSet、关闭连接。我在前面提过JDBC的SSL连接报错以及serverTimezone配置都会在这一步出现。直接把连接串写成String url jdbc:mysql://127.0.0.1:3306/school?useSSLfalseserverTimezoneAsia/Shanghai; String user root; String password 123456; try (Connection conn DriverManager.getConnection(url, user, password); PreparedStatement ps conn.prepareStatement( SELECT c.course_name, sc.score FROM student s JOIN student_course sc ON s.id sc.student_id JOIN course c ON c.id sc.course_id WHERE s.name ?)) { ps.setString(1, 张三); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { System.out.println(rs.getString(course_name) : rs.getBigDecimal(score)); } } }用PreparedStatement而不是Statement是因为它有预编译、防SQL注入是生产规范。作业里能用上这个细节面试官一眼就看得出受过正规训练。第二本地起一个Python脚本用pymysql或mysql-connector-python读写MySQL复现“录入学生信息并查询选课结果”的流程import pymysql conn pymysql.connect( host127.0.0.1, port3306, userroot, password123456, databaseschool, charsetutf8mb4 ) with conn.cursor() as cursor: cursor.execute( INSERT INTO student (name, gender, birth_date) VALUES (%s, %s, %s), (赵六, M, 2003-11-11) ) conn.commit() cursor.execute( SELECT s.name, c.course_name, sc.score FROM student s JOIN student_course sc ON s.id sc.student_id JOIN course c ON c.id sc.course_id WHERE s.name %s, (赵六,) ) for row in cursor.fetchall(): print(row) conn.close()Python脚本里同样用%s占位符别拿字符串拼接SQL习惯要从第一次作业就开始养。第三展示一次完整的备份与恢复操作对应热搜词里“怎么使用mysql主从复制”、“把远程库的这张表同步到本地”这一类问题。第一步用mysqldump导出mysqldump -uroot -p school school_backup.sql第二步把备份文件传到另一台机器恢复mysql -uroot -p school school_backup.sql结合作业要求单独导出某张表mysqldump -uroot -p school student_course student_course_only.sqlmysqldump单表导出时不会带建库语句恢复前要先确保目标库存在。这个我上课时反复强调过结果还是有人没建库直接导入导致报错。把这三个方向加进作业谈吐之间立刻从“我会建表”变成“我理解整个数据链路”。面试官喜欢这样的候选人因为这意味着你能独立处理从存储到应用的完整闭环。9. 复盘我的第一次作业哪些坑最值得记带过好几轮实习生每个新人踩的坑都大同小异。我把最高频的五个问题集中列出来你们写作业时可以直接对照检查问题典型报错原因解决mysql命令找不到-bash: mysql: command not foundPATH中没包含MySQL二进制目录将/usr/local/mysql/bin加入PATH或使用完整路径socket文件缺失ERROR 2002 (HY000)服务未启动或 socket路径不一致检查服务状态查看/etc/my.cnf的socket配置密码验证失败ERROR 1045 (28000): Access denied密码错误或账号锁定重置密码检查account_locked字段表数据中文乱码???或 客户端字符集与服务端不一致连接时加--default-character-setutf8mb4建库时用utf8mb4数据重复Duplicate entry for key uk_student_course唯一键冲突或业务重复插入前先查重或改用ON DUPLICATE KEY UPDATE最后一个问题值得多讲两句。很多新人在作业中遇到唯一键冲突时第一反应是“删掉唯一索引”这就本末倒置了。唯一索引是约束数据重复的手段冲突说明业务逻辑里可能没先查重就插入了。正确的做法有两种一种是在应用层先SELECT判断再INSERT另一种是直接用ON DUPLICATE KEY UPDATE让数据库自己决定更新还是插入效率更高。我自己第一次用ON DUPLICATE KEY UPDATE时也有个疑问它到底算INSERT还是UPDATE实际上它在两种操作之间自适应但对AUTO_INCREMENT列有个副作用——即使最终走了更新分支自增值也会被消耗掉。所以如果你看到表的主键不是从1连续开始的不要大惊小怪这是自增机制的常见现象不是数据错乱。做完上面所有事情这份作业就算圆满完成了。以我个人的经验能把一个“最简单的选课库”做成这样MySQL的日常开发能力已经足够应付绝大多数业务场景。后面再进阶的方向可以看存储引擎选择、备份恢复策略、读写分离方案、慢查询分析但那些都是后话了。先把这次作业里的每一条SQL、每一个报错、每一次EXPLAIN的解读吃透比囫囵吞枣刷一百道面试题都管用。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。