简介崔巍编著的《数据库课后实验》是一套面向数据库初学者的实践教学资源适合配合《数据库原理》课程进行课后巩固与上机练习。压缩包体积仅5KB共包含5个sql脚本文件对应多个递进式实验任务可直接在SQL Server等环境中运行调试。实验内容全面覆盖SQL语言基础与高级查询、表创建与数据操作、JOIN多表连接、GROUP BY分组统计、数据库范式设计与ER模型转换、事务ACID属性及并发控制、备份与恢复策略以及索引优化等关键知识点。每个脚本都针对具体问题设计便于读者对照课堂理论逐步拆解。目前已有220人学习适合需要借助现成实验代码快速理解数据库原理、提升SQL编写与排错能力的高校学生。1. 数据库课后实验崔巍编著是什么不是背 SQL是练边界数据库实验课最常见的翻车现场不是 SQL 语法没背熟而是环境没起来、UPDATE 少了 WHERE 条件把整张表改废、两个窗口同时改同一行数据直接卡死。崔巍老师编著的这套课后实验正好把数据库课程里最容易被背题掩盖的环节全拉到了命令行面前从建库建表、增删改查到视图、索引、存储过程和事务并发一题一题逼你动手。它适合三类人上课听懂了但一敲命令就出错的在校生准备数据库课程设计和复试前想补手感的同学以及刚接手实验课、想快速搭出可验收课题的助教。说白了实验报告交上去不是终点重点是你敢不敢把数据库当工具用。2. 先让环境跑起来MySQL 安装、配置与建库脚本2.1 为什么用 MySQL 8.x 档住这套实验崔巍老师这套实验册里的题目语法主体是标准 SQL所以在选数据库引擎时没必要在 Oracle、SQL Server、MySQL 之间反复纠结。我一般建议直接走 MySQL 8.x理由有三个。第一安装包和命令行工具都透明实验报告里每一步操作都能复现不像 Oracle 那样要处理一堆服务账号第二MySQL 8 对标准 SQL 的兼容度比 5.7 时代好很多窗口函数、CTE、CHECK 约束都能跑后面做数据库课程设计或者转去接触人大金仓、达梦这些国产数据库语法迁移成本都很低第三实习面试里被问得最多的也是 MySQL。版本选择上我建议走 LTS 线比如 8.4 LTS而不是追最新大版本。实验册里的 SQL 用不到新版本的功能LTS 的默认行为更稳定的意思是你在网上搜到的排错经验基本都能对上不太会遇到同样的 SQL 在别人机器能跑、在你机器报错的玄学问题。还有一个关键决定用 msi 安装包还是 zip 压缩包。我自己偏好 zip因为它本质就是解压出一个目录和几个可执行文件不注册一堆 Windows 服务也不会在卸载时留一堆残留。想在一台实验机上反复折腾多套环境zip 方式还能让不同版本共存数据目录互相独立。2.2 下载解压到初始化最小命令序列从官网下载 zip 包之后解压到 D:\mysql-8.4 这种纯英文路径。不要放在带中文、带空格的目录里否则后面 mysqld 启动时会出现路径解析问题。解压完成后先不要急着一键启动建议在解压目录根目录和 bin 同级建一个 my.ini[mysqld] basedirD:/mysql-8.4 datadirD:/mysql-8.4/data port3306 character-set-serverutf8mb4路径里的斜杠建议用正向斜杠避免 Windows 下反斜杠被当成转义字符。datadir 是数据目录位置提前指定可以防止 MySQL 把数据塞到系统盘默认路径后面做备份和清理都麻烦。然后进入 bin 目录执行初始化# 解压 zip 包后进入 bin 目录--initialize 生成 data 目录和临时 root 密码 mysqld --initialize --console # 前台启动方便看启动日志确认没问题再 CtrlC 停掉 mysqld --console--initialize是 MySQL 8 首次运行前必须执行的步骤它会创建系统库、初始化权限表、生成一个随机的 root 临时密码打印在控制台。如果当时没截屏导致密码丢了把 data 目录整个删掉重新初始化一次就行反正还没有业务数据。mysqld --console前台启动能直接看到日志里的错误常见三类my.ini 路径不对、data 目录权限不对、3306 端口被占用。这些错误在前台日志里看得清清楚楚一旦注册成服务再排查就得翻半天事件日志。确认启动无报错之后再注册成服务。管理员终端执行mysqld --install然后net start mysql启动。不想用了就net stop mysql加mysqld --remove。实验机不比生产环境服务数量越少出幺蛾子的概率越低。2.3 改密码、字符集和专用实验账号初始化完成后的 root 账号只能本机登录密码是随机生成的。第一件事登录并改掉它mysql -uroot -p登录后执行ALTER USER rootlocalhost IDENTIFIED BY 换成你自己的密码;不要设空密码也别让 root 远程登录。实验课和课程设计共用机器时root 权限太大误操作一下整个实例都受影响。我一般建一个专用实验账号权限只给实验库CREATE USER lablocalhost IDENTIFIED BY lab123456; GRANT ALL PRIVILEGES ON db_lab.* TO lablocalhost; FLUSH PRIVILEGES;平时用 Navicat 连接就用这个账号。Navicat 连不上 MySQL 8 常是两种情况账号绑定的来源不是你能访问的主机或者 MySQL 8 默认的 caching_sha2_password 认证插件跟旧版客户端不兼容。报错信息里直接出现caching_sha2_password字样的优先升级客户端不建议为了迁就插件把安全特性关掉。字符集在 my.ini 里已经写了 utf8mb4启动后可以确认一下SHOW VARIABLES LIKE character_set_server;如果显示不是 utf8mb4说明刚才的 my.ini 没被读到回头检查文件位置和编码不要只在会话里 SET GLOBAL那玩意重启就失效。utf8 和 utf8mb4 的差别也值得在报告里提一句utf8 在 MySQL 里最多三字节emoji 和部分生僻字存不进去utf8mb4 才是完整的四字节编码。2.4 交作业前先跑通的建库脚本实验册翻开第一题几乎都是建库建表。学生、课程、选课是三张必出场的关系表。最小可交作业的脚本长这样CREATE DATABASE IF NOT EXISTS db_lab CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE db_lab; CREATE TABLE student ( sno VARCHAR(10) PRIMARY KEY, sname VARCHAR(20) NOT NULL, ssex VARCHAR(4), sage INT, sdept VARCHAR(20) ) ENGINEInnoDB; CREATE TABLE course ( cno VARCHAR(10) PRIMARY KEY, cname VARCHAR(30) NOT NULL, cpno VARCHAR(10), ccredit INT ) ENGINEInnoDB; CREATE TABLE sc ( sno VARCHAR(10), cno VARCHAR(10), grade INT, PRIMARY KEY (sno, cno), FOREIGN KEY (sno) REFERENCES student(sno), FOREIGN KEY (cno) REFERENCES course(cno) ) ENGINEInnoDB;学号我习惯用 VARCHAR而不是自增 INT。原因是实验册里的数据是纸面直接给的用字符串学号录入时不用先查自增值而且学号不参与算术运算字符串主键完全够用。三张表全部写ENGINEInnoDB不是可选项第 5 章做事务、行锁和死锁复现全依赖这个存储引擎如果建表时写成 MyISAM并发实验根本复现不出来。把整段脚本存成 init_lab.sql每次实验开始前执行一次mysql -u lab -p db_lab init_lab.sql这算是我做实验的固定习惯动手前先恢复干净基线改坏了重跑一遍就回到原点比手工一条条改回去高效得多。到了数据库课程设计阶段这个习惯能救命的频率远超你想象。3. 实验一把增删改查和表结构修改练到不假思索3.1 INSERT / UPDATE / DELETE 的边界条件增删改查是数据库实验的地基但真正能区分会写和敢写的是边界条件。INSERT 的边界是主键冲突、NOT NULL 约束、长度超限UPDATE 的边界是 WHERE 条件有没有写全DELETE 的边界是它能不能被后悔。实验里最常见的翻车现场是兴致勃勃地执行了一句UPDATE sc SET grade grade 5;然后发现整张表所有成绩都被改了。想避免这种事故最稳妥的做法是把所有高风险操作包在事务里START TRANSACTION; UPDATE sc SET grade grade 5 WHERE sno 001; -- 执行完之后先查一下影响范围 SELECT * FROM sc WHERE sno 001; -- 确认没问题再提交有异常直接回滚 COMMIT; -- ROLLBACK;事务的作用是给操作留一道后悔药。实验报告里经常要求展示修改前、修改后用事务把两步截图拼在一起还能顺带说明 COMMIT 和 ROLLBACK 的作用。UPDATE 和 DELETE 还有一个通用习惯先用 SELECT 顶着同一个 WHERE 条件看影响行数再执行写操作。比如要删sdept历史系的学生先SELECT COUNT(*) FROM student WHERE sdept历史系看到数字心里有底了再删。INSERT 的边界则在约束上。向已有数据的表插入重复主键MySQL 会报Duplicate entry插入 NOT NULL 列时漏掉值会报Field sname doesnt have a default value。这些报错不是坏事实验报告里写清楚为什么报错、怎么解决比单纯贴成功结果得分高。注意一张表如果有自增列删除数据后自增值不会回退实验里想清理数据重建现场用 TRUNCATE TABLE 而不是 DELETE后者不会重置自增计数有外键时 TRUNCATE 会失败要先处理子表。3.2 用 ALTER TABLE 修改结构四个高频操作数据库增删改查里最容易让人不设防的是修改表结构。语法本身不难难的是搞清楚 MODIFY、CHANGE、ADD 之间的差别。实验里常要求的四个操作是加列、改列类型、改列名、删列。-- 1. 增加列 ALTER TABLE student ADD COLUMN sphone VARCHAR(15); -- 2. 修改列类型/约束 ALTER TABLE student MODIFY COLUMN sage TINYINT UNSIGNED; -- 3. 修改列名注意这里必须用 CHANGE ALTER TABLE student CHANGE COLUMN sphone phone VARCHAR(15); -- 4. 删除列 ALTER TABLE student DROP COLUMN phone;我的记忆方法是只改类型和约束用 MODIFY要改名必须用 CHANGE原名和新名都要写一遍。如果实验册要求将 age 列改名为 new_age写成MODIFY COLUMN age new_age会直接报错。另外加约束也是 ALTER TABLE 的高频考点ALTER TABLE student ADD CONSTRAINT uq_sname UNIQUE (sname); ALTER TABLE sc ADD CONSTRAINT fk_sc_sno FOREIGN KEY (sno) REFERENCES student(sno);MySQL 8 里修改表结构时会重建表数据量小的时候看不出影响到了课程设计阶段一张十万行的表加列可能要卡几十秒。实验里养成先备份再 ALTER 的习惯后面做大作业少踩很多坑。修改完结构后用DESC student;或SHOW CREATE TABLE student;确认结果比肉眼盯着报错信息猜要快。3.3 连接查询、聚合与分组交作业前反复核对查询实验是课后实验里题量最大的一块老师最喜欢在连接和分组上挖坑。三张表的连接查询建议先写 FROM再写 JOIN 条件最后 SELECT 列不容易漏关联条件SELECT s.sdept, COUNT(DISTINCT s.sno) AS stu_cnt, AVG(sc.grade) AS avg_grade FROM student s LEFT JOIN sc ON s.sno sc.sno GROUP BY s.sdept HAVING stu_cnt 2;这里 LEFT JOIN 和 INNER JOIN 的区别是实验报告里必写的INNER JOIN 只保留两表都有匹配的行LEFT JOIN 会保留左表的所有行右表没匹配上时字段为 NULL。聚合查询里还要注意 COUNT(DISTINCT) 和 COUNT() 的差别如果学号在 student 表里唯一问题不大但一旦 JOIN 出重复行COUNT() 会把重复算进去。AVG 函数会忽略 NULL没有成绩的学生不会拉低平均分这一点结论可以直接写进报告。MySQL 8 默认的 sql_mode 里包含 ONLY_FULL_GROUP_BY所以 SELECT 里的非聚合列必须出现在 GROUP BY 中。比如只 GROUP BY sdept却 SELECT sname会直接报错。这不是 MySQL 难为你还是分组本身的要求。另一个高频坑是查没有选课的学生很多同学写WHERE sno NOT IN (SELECT sno FROM sc)当子查询结果里出现 NULL 时NOT IN 会返回空结果。更稳的写法是SELECT * FROM student s LEFT JOIN sc ON s.sno sc.sno WHERE sc.sno IS NULL;这个IS NULL 判断是实验课上的经典翻车点写进报告的排查记录里很加分。3.4 验证实验结果的三个习惯做完查询实验别急着截图关终端。我一般用三个习惯收尾。第一危险操作前先用 SELECT 顶着同一个 WHERE 看一眼影响行数和预估值对不上就先别动手。第二修改之后立刻用一个聚合查询核对总数比如 UPDATE 后SELECT COUNT(*), SUM(grade)数字和预期一致才算成功。第三每次实验结束把 SQL 脚本和结果截图按日期归档。做数据库课程设计时这套归档习惯能让你在两周后还能定位到哪一步把数据改坏了。这三个习惯都不复杂但能把实验从玄学能跑变成可复现、可解释。4. 视图、索引和存储过程三个高频实验题一次过4.1 视图创建、查询和更新限制视图是实验册里第一个看起来像表但不是表的东西。它的本质是一段保存好的 SELECT查询视图时 MySQL 才去执行这段 SELECT。所以视图字段的语义取决于基表基表数据变了视图结果也跟着变。创建视图的语法和查询基本一样CREATE VIEW v_stu_grade AS SELECT s.sno, s.sname, c.cname, sc.grade FROM student s JOIN sc ON s.sno sc.sno JOIN course c ON c.cno sc.cno; SELECT * FROM v_stu_grade WHERE grade 60;这个视图把三张表连接的结果封装起来后续要查挂科名单就不用重复写 JOIN。视图的最大坑在更新上如果 SELECT 里包含 JOIN、GROUP BY、聚合函数或 DISTINCT这个视图就不可更新。实验里很多同学对视图执行UPDATE v_stu_grade SET grade 90然后收到not updatable报错原因就在这。想演示可更新的视图用单表视图CREATE VIEW v_student_basic AS SELECT sno, sname, sdept FROM student; UPDATE v_student_basic SET sdept 计算机系 WHERE sno 001;视图更新本质上更新的是基表所以还要满足基表本身的约束。实验报告里建议把可更新/不可更新两种情况各放一个例子对比着写比只贴视图查询成功截图扎实得多。4.2 索引EXPLAIN 和实际耗时怎么判断索引实验的重点不是会写 CREATE INDEX而是会证明索引真的生效了。最直接的验证工具是 EXPLAIN。在 sc 表上加一个 cno 索引前后的对比-- 建索引 CREATE INDEX idx_sc_cno ON sc(cno); -- 查看是否走索引 EXPLAIN SELECT * FROM sc WHERE cno C1; -- 删除索引再做一次对比 DROP INDEX idx_sc_cno ON sc;EXPLAIN 输出的关键列是 type 和 key。typeALL 是全表扫描建索引后变成了 ref 或 constkey 列出现索引名这才算索引生效。实验报告里如果只贴一张SHOW INDEX FROM sc的表等于什么都没证明。我一般会把实验数据先灌到几千行否则在几百行的表上全表扫描和索引查找的耗时差异肉眼看不出来EXPLAIN 的扫描行数差异也不明显。另一个值得写进报告的观察是低区分度字段建索引效果差。比如 ssex 只有两个值索引扫描还是得回表读大量行优化器很可能直接选择全表扫描。把这些观察写进去老师能看出你是真跑过而不是背结论。索引不是越多越好每多一个索引INSERT 和 UPDATE 就要多维护一棵 B 树实验里可以故意建三个索引再对比写入耗时结论很有说服力。4.3 存储过程循环、游标和异常处理存储过程是很多同学第一次接触 SQL 里的控制结构。语法不难难在环境配合。直接在 mysql 客户端里写过程体会遇到分号被截断的困惑所以要先改分隔符DELIMITER // CREATE PROCEDURE sp_mark_good_students() BEGIN DECLARE done INT DEFAULT 0; DECLARE v_sno VARCHAR(10); DECLARE v_total DECIMAL(8,2); DECLARE cur CURSOR FOR SELECT sno, SUM(grade) FROM sc GROUP BY sno; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done 1; OPEN cur; loop_label: LOOP FETCH cur INTO v_sno, v_total; IF done 1 THEN LEAVE loop_label; END IF; -- 业务处理把总成绩达标的学生插到荣誉表 INSERT INTO honor_student(sno, total_grade) VALUES (v_sno, v_total); END LOOP; CLOSE cur; END // DELIMITER ;DELIMITER 是必需步骤它告诉 mysql 客户端别在过程体内部的分号处提前结束做完再恢复成;否则后面普通 SQL 全部不能正常执行。游标循环里最容易犯的错是忘记 FETCH 之后立刻判断 done导致循环多跑一次或者取到空值。还有一个常见报错是变量名和列名重名比如变量名叫 sname 同时表里也有 sname 列会造成歧义我习惯在变量名加 v_ 前缀避开。调用存储过程用 CALL带参数版本也一样。如果过程里出错MySQL 默认把错误抛给客户端实验里可以加 DECLARE EXIT HANDLER 做异常处理但大多数课后实验只要能把游标循环跑通就够。项目结束前记得DROP PROCEDURE IF EXISTS清理重跑。4.4 把三个实验打包成可复现的验收脚本视图、索引、存储过程这三个实验分开做没问题但交作业时被人逐个执行一遍很容易翻车因为环境状态对不上。我习惯把三者打包进一个 sql 文件先建视图再建索引再建存储过程最后写一段验证 SELECT执行顺序固定。执行方式mysql -u lab -p db_lab lab_view_index_proc.sql文件开头加一句DROP VIEW IF EXISTS v_stu_grade;和DROP PROCEDURE IF EXISTS sp_mark_good_students;保证脚本可重复执行。索引和存储过程不同MySQL 对DROP INDEX不支持 IF EXISTS所以重建索引前先执行SHOW INDEX FROM sc;确认对象存在再按名字删除。这个样子很适合给同学同步不用把整个数据库目录拷过去一个 sql 文件就把视图、索引和存储过程全部带走对方导入到本地就能跑。养成这个习惯之后后面做数据库课程设计环境迁移也从来不是问题。5. 事务与并发实验避坑锁等待、死锁和连接池5.1 事务隔离级别在实验里怎么验证事务这章最容易翻车因为单开一个窗口根本验证不了并发行为。我一般会开两个 mysql 客户端窗口一个执行操作一个观察结果。先看当前隔离级别SELECT transaction_isolation;MySQL 8 默认值是 REPEATABLE-READ。验证事务是否防脏读可以这样做。窗口 A 开启事务更新一行但不提交START TRANSACTION; UPDATE sc SET grade grade - 10 WHERE sno 001; -- 此时不 COMMIT窗口 B 查询这一行在默认隔离级别下看不到未提交的修改说明当前隔离级别已经防住了脏读。想进一步区分 READ COMMITTED 和 REPEATABLE READ可以在会话 B 里先执行一次 SELECT让窗口 A 提交后再执行一次看两次结果是否一致。REPEATABLE READ 下同一个事务内多次读结果一致READ COMMITTED 下第二次读能看到新提交的数据。把两个窗口的截图并排贴进实验报告比任何文字都有说服力。需要提醒的是隔离级别只是读的规则单靠它防不住更新丢失。要防丢失更新得靠锁机制。实验里如果发现并发叠加成绩总是少一次加法大概率是应用层没有用事务加锁保护临界区这不是 MySQL 配置能解决的。5.2 死锁和锁等待现象、日志与处理数据库并发锁里最经典的现象是窗口 A 更新了一行没提交窗口 B 更新同一行就一直转圈。这不是程序卡死是 B 在等 A 释放行锁。处理办法很简单让 A 提交或回滚。但在实验里这个卡住的过程恰恰是你要抓的证据。卡住的时候去另一个窗口查锁状态SHOW ENGINE INNODB STATUS\G输出里 LATEST DETECTED DEADLOCK 段会列出涉及的事务、持有锁的表和行、等待的关系。死锁比单纯锁等待更麻烦它不需要有人手动干预InnoDB 会自动选一个事务回滚掉另一个继续执行。所以死锁的本质是两个事务以相反的顺序申请锁每一方都攥着对方想要的东西。在实验里复现死锁也很简单两个窗口分别执行顺序相反的更新-- 窗口 A 先执行 START TRANSACTION; UPDATE sc SET grade grade 1 WHERE sno 001 AND cno C1; -- 不要提交切到窗口 B -- 窗口 B 再执行 START TRANSACTION; UPDATE sc SET grade grade 1 WHERE sno 002 AND cno C2; -- 不要提交切回窗口 A然后窗口 A 更新 sno002 的行窗口 B 更新 sno001 的行两边立即死锁其中一个事务被 InnoDB 回滚。解决死锁的工程做法是统一全局加锁顺序所有事务先更新学号小的再更新学号大的。这个结论写在实验报告里比单贴死锁日志更显水平。锁等待超时时间在 my.ini 里配innodb_lock_wait_timeout默认 50 秒实验时不想干等可以临时调小到 5 秒。5.3 连接池参数为什么连不上、为什么慢有些实验册会把连接池放到选做但数据库课程设计和实习面试都绕不开它。连接池本身不复杂作用就是复用连接避免每次 SQL 都走一次 TCP 握手和身份认证。用 HikariCP 的最小配置我一般这么写HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/db_lab?serverTimezoneAsia/Shanghai); config.setUsername(lab); config.setPassword(lab123456); config.setMaximumPoolSize(10); config.setMinimumIdle(2); config.setConnectionTimeout(30000); config.setMaxLifetime(1800000);参数里最常出问题的是 maximumPoolSize 和 maxLifetime。池上限设得比数据库 max_connections 还大高峰期连接会被数据库直接拒绝maxLifetime 设得比数据库 wait_timeout 长空闲连接被服务端断掉后客户端拿到的是死连接一执行 SQL 就抛异常。实验里最常见的报错是Connection is not available, request timed out先查代码里有没有在 finally 中关闭 Statement 和 ResultSet再查事务有没有提交或回滚。连接泄漏十有八九是这两个地方没写干净不是池参数的问题。5.4 避坑清单5 条血泪经验现象第二个窗口 UPDATE 同一行一直不返回。 原因InnoDB 行锁等待第一个事务还没提交。 解决让第一个窗口 COMMIT 或 ROLLBACK调大锁等待超时只是拖延问题不是解法。现象两个事务各自执行到一半其中一个报Deadlock found。 原因两个事务按相反顺序给同一批数据加锁。 解决全局统一加锁顺序对被回滚的事务做重试。现象按实验步骤操作并发场景却没有任何锁效果。 原因表用了 MyISAM或者操作没有包在事务里。 解决建表统一用 InnoDB写操作前后显式 START TRANSACTION不要依赖自动提交。现象Navicat 连接 MySQL 8 报认证插件无法加载。 原因客户端太旧不认识 caching_sha2_password。 解决升级客户端版本临时方案是给实验账号换用 mysql_native_password。现象实验做了一下午表里的数据改得一团乱又记不清原始值。 原因没做基线备份高危操作没放事务。 解决每次实验开始前执行 init_lab.sql 恢复干净环境高危操作前 SELECT 验证影响范围。这些坑单独看都不大但每一个都真实吃掉过我自己的时间。第 3 条尤其隐蔽很多同学写完建表脚本顺手用了默认存储引擎结果到并发实验怎么都复现不出来最后发现是引擎不符重新建表才解决。6. 把课后实验变成面试素材用面试题反过来验证实验做完这一轮课后实验最后一步不是交实验报告而是把每个实验改写成面试答案。数据库面试题里有几个高频话题恰好都能在这套实验里找到证据。比如问数据库死锁怎么排查不要只背概念可以说我在实验里开两个终端复现死锁然后用 SHOW ENGINE INNODB STATUS 看 LATEST DETECTED DEADLOCK定位到两个事务加锁顺序相反最后统一加锁顺序解决。这段经历比死锁是两个事务互相等待有价值得多。比如问索引为什么能提速可以说我在 sc 表建索引前后分别跑 EXPLAINtype 从 ALL 变成 ref扫描行数从全表降到一行同时对比了低区分度字段建索引没有效果。这就把 B 树、回表、区分度几个概念全串起来了。再比如事务隔离级别面试里问MySQL 默认隔离级别是什么怎么复现不可重复读你可以直接说开两个窗口一个读一个改在 READ COMMITTED 下第二次读能看到新数据在 REPEATABLE READ 下看不到。这套操作就是课后实验的延伸。我自己的习惯是每做完一个实验在报告最后补一行如果不这样做会怎么样。不加 WHERE 会更新全表不关游标会占连接不提交事务会锁行连接池不关连接会耗尽。这行字一开始写起来很慢但积累几周后数据库的实验现象就全变成自己的经验了。当年我带实验课的时候很多同学交完报告就把实验丢在一边面试被问到并发锁只能背定义一深问就卡壳。后来我要求他们把实验当一个可复现的小项目每个坑记下来面试反而有东西讲。把课后实验做进简历不丢人。希望帮到你。本文还有配套的精品资源点击获取