简介:《MySQL数据库基础实例教程(第2版)》是面向软件技术、移动互联等专业学生的专业必修课教学大纲,内容系统规划了MySQL数据库从入门到进阶的学习路径。大纲涵盖数据库基础知识、数据库设计、数据定义、数据操作、数据查询、数据视图、索引与分区、数据库编程及数据安全九大教学模块,并配有PetStore综合实例贯穿实践环节;同时说明课程从就业岗位调研入手提炼出九个典型工作任务,采用项目模拟方式组织教学。课程总学时64,其中讲授与实践各32学时,便于教学安排与自学规划。资源包共1个PDF文件,压缩包大小为341KB,便于快速下载与查阅。目前已有2127人浏览学习,适合高校教师用于课程设计参考,也适合学生提前梳理知识框架、明确重难点,作为备考或实训的纲领性材料。
1. 大纲不是教程:一张 64 学时的 MySQL 学习地图
拿到这份《MySQL数据库基础实例教程(第2版)》教学大纲 PDF,我先确认了一下:它不是一本写满 SQL 示例的电子书,而是一份把数据库学习切成 64 学时的课程蓝本——讲授 32 学时、课内实践 32 学时,从 MySQL 安装一路排到用户权限、事务锁与日志恢复。数据库学习最容易栽在“东学一块、西学一块”,这份大纲的价值恰恰在于它把知识点排成了九条任务线,每个任务都挂着一个能验证的输出物,比如 PetStore 库、LibraryDB 五表、能调通的存储过程。你如果正在补 MySQL 基础,或者要带新人,照这条线走能省掉大量试错。下面我把它拆成可执行的练习方案,每个阶段标出最容易翻车的点。
2. 从建库到查询:把大纲前五章压实成一份可照跑的 SQL 路线
大纲的前五个模块——数据库基础知识、数据库设计、数据定义、数据操作、数据查询,本质上是一条完整的 SQL 主链路。很多人学 MySQL 卡住,不是卡在语法难,而是卡在顺序乱:还没搞懂外键约束就开始写多表查询,查出来的结果对不对都不知道怎么验证。这一章我按大纲的实际顺序重排成三个阶段,每个阶段都配可直接抄的 SQL。
2.1 设计先行:先画 E-R 图,再写 CREATE TABLE 才不会返工
大纲第二模块把数据库设计放在数据定义前面是有意安排的。很多人上来就 CREATE TABLE,后面发现外键漏了、字段冗余一堆,再回头改表结构,代价比想象中大得多。课程给的方法论是:先做概念模型(E-R 图),再把实体和关系转成关系模型,最后按第三范式校一遍。图书管理系统 LibraryDB 需要五张表——读者分类表、读者表、库存表、借阅表、图书表,这就是一个非常典型的练习。
我一般会先在纸上把实体画出来:读者分类与读者是一对多,读者与借阅是一对多,图书与借阅是一对多,库存是图书的补充信息。关系确定后再落成 SQL。以下是读者表、图书表和借阅表的建表核心写法:
CREATE TABLE reader_category ( category_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '读者分类ID', category_name VARCHAR(50) NOT NULL UNIQUE COMMENT '分类名称,UNIQUE作为替代键' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE reader ( reader_id INT PRIMARY KEY AUTO_INCREMENT, reader_name VARCHAR(30) NOT NULL, category_id INT NOT NULL, phone VARCHAR(20), -- 外键:读者从属于某个分类 CONSTRAINT fk_reader_category FOREIGN KEY (category_id) REFERENCES reader_category(category_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE borrow ( borrow_id INT PRIMARY KEY AUTO_INCREMENT, reader_id INT NOT NULL, book_id INT NOT NULL, borrow_date DATE NOT NULL, return_date DATE, CHECK (return_date IS NULL OR return_date >= borrow_date), -- CHECK完整性约束 FOREIGN KEY (reader_id) REFERENCES reader(reader_id), FOREIGN KEY (book_id) REFERENCES book(book_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这一段把大纲第三模块的四种完整性约束都覆盖了:主键约束用 PRIMARY KEY,替代键用 UNIQUE,参照完整性用 FOREIGN KEY,CHECK 直接写在字段约束里。注意 MySQL 8.0.16 之前 CHECK 约束实际上不强制校验,升级之后再依赖它才真正有效,老版本里更靠谱的做法是交给应用层或者用触发器兜底。
参数上,外键约束的 ON DELETE 子句我没写,默认是 RESTRICT,即父表有子记录引用时禁止删除。想做级联删除要显式加 ON DELETE CASCADE,但业务上我更推荐 RESTRICT 或 SET NULL,级联删除在数据量大了以后容易误伤。字符集用 utf8mb4 而不是 utf8,否则 emoji 和生僻字会存不进去;排序规则对应用 utf8mb4_general_ci,要求更高的场景可以用 utf8mb4_0900_ai_ci。
建表完成后,用 SHOW CREATE TABLE reader; 可以回看 MySQL 实际执行的 DDL,这是检查建表结果最直接的方式。我每次建完表都会跑一遍这条命令,确认 ENGINE 是 InnoDB、外键有没有被正确解析——很多翻车现场就是从这里开始暴露的。
2.2 数据操作三板斧:INSERT、UPDATE、DELETE 的边界与回滚习惯
大纲第四模块把插入、修改、删除数据单独拉出来练,配套要求掌握 SHOW 和 DESCRIBE 语句。数据操作本身不复杂,复杂的是边界:插入违反唯一约束报什么错、UPDATE 忘带 WHERE 会改掉整表、DELETE 大表的锁和日志问题。建议所有练习都放在事务里做,出错就回滚,这是最有用的兜底习惯。
-- 插入单行 INSERT INTO reader_category (category_name) VALUES ('本科生'); -- 插入多行 INSERT INTO reader (reader_name, category_id, phone) VALUES ('张三', 1, '13800000001'), ('李四', 1, '13800000002'); -- 把查询结果直接灌进另一张表 INSERT INTO reader_backup (reader_id, reader_name, category_id) SELECT reader_id, reader_name, category_id FROM reader WHERE category_id = 1; -- 修改:务必带 WHERE UPDATE reader SET phone = '13900000000' WHERE reader_id = 1; -- 删除:单条删和清表要分清 DELETE FROM reader WHERE reader_id = 2; -- 逐行删,走事务、记日志 TRUNCATE TABLE reader_backup; -- 整体清空,不逐行记日志,不可按行回滚先把事务包起来再操作,是我在这个模块强调的习惯:SET autocommit = 0; 之后执行 INSERT 或 UPDATE,用 SELECT 确认受影响行数没问题再 COMMIT,不对就 ROLLBACK。DELETE 和 TRUNCATE 的区别要特别记牢:DELETE 是 DML,逐行删除、走 binlog、能配合 WHERE;TRUNCATE 是 DDL,直接把表重置,自增列也归零,且不会逐行触发删除触发器。误操作后想恢复,DELETE 还有后悔药,TRUNCATE 基本没有。
插入数据前先用 DESCRIBE reader; 看字段列表和是否允许 NULL,能避免一半的字段写错问题。SHOW 语句则用来查看库表状态,比如 SHOW TABLES; 列出所有表,SHOW INDEX FROM reader; 查看索引,这些命令在大纲里被反复要求,实际工作中也确实是最常用的巡检手段。
2.3 数据查询:SELECT 子句执行顺序是排错的第一把尺子
数据查询是大纲第五模块,也是学时分配最重的一块。单表查询、多表查询、排序和分类汇总,最后落到 PetStore 综合实例。SELECT 语法本身不复杂,但排错时最有用的是记住各子句的执行顺序——它决定了 WHERE 里能不能用别名、HAVING 和 WHERE 有什么区别。
-- 单表聚合:统计每个分类下的读者数,过滤掉人数少于2的分类 SELECT category_id, COUNT(*) AS reader_cnt FROM reader WHERE reader_name IS NOT NULL GROUP BY category_id HAVING reader_cnt >= 2 ORDER BY reader_cnt DESC LIMIT 5; -- 多表连接:查出借阅记录对应的读者名和书名 SELECT r.reader_name, b.book_name, br.borrow_date FROM borrow br JOIN reader r ON br.reader_id = r.reader_id JOIN book b ON br.book_id = b.book_id WHERE br.return_date IS NULL ORDER BY br.borrow_date DESC;执行顺序是:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT。因为这个顺序,WHERE 里不能引用 SELECT 中定义的别名 reader_cnt,只能用 HAVING 过滤聚合结果;ORDER BY 阶段才能用别名排序。聚合函数 COUNT、SUM、AVG、MAX、MIN 作用于 GROUP BY 分组后的数据,COUNT(*) 和 COUNT(字段) 的区别在于后者不计 NULL。
多表连接优先用 INNER JOIN,需要左表全保留时用 LEFT JOIN。连接条件写 ON 而不是 WHERE,虽然结果有时相同,但 LEFT JOIN 条件下 WHERE 会把左表的空配行过滤掉,语义就变了。大纲在这里还要掌握 SHOW 和 DESCRIBE 语句——实际是建议你用它们去核对表结构,排查“字段名写错”“列名冲突”这类低级错误。我见过不少人在多表连接里不写表别名,两个表都有 id 字段时直接报 Column 'id' in where clause is ambiguous,解决方式就是给每个表起别名并且所有字段都带前缀。
3. 视图、索引与分区:三个能提效但容易被误用的进阶点
课程把索引与分区放在视图之后。这三个东西用好了查询效率明显提升,用不好就是给自己挖坑。视图的坑在于更新限制,索引的坑在于建了不用还占空间,分区的坑在于类型选错查询反而变慢。我按大纲的实践要求逐个拆开讲。
3.1 视图:本质是存储的查询,不是性能优化工具
视图在大纲第六模块,概念上是一张虚拟表,实际上只是保存了一条 SELECT 语句。它的价值在于封装复杂查询、隐藏敏感字段、给不同角色开不同的数据口径。需要强调一句:视图不会提升查询性能,查询视图时底层 SQL 照样执行,该全表扫描还是全表扫描。
-- 创建视图:只看未归还的借阅记录,隐藏读者电话等敏感字段 CREATE VIEW v_borrow_overdue AS SELECT br.borrow_id, r.reader_name, b.book_name, br.borrow_date FROM borrow br JOIN reader r ON br.reader_id = r.reader_id JOIN book b ON br.book_id = b.book_id WHERE br.return_date IS NULL; -- 查询视图 SELECT * FROM v_borrow_overdue WHERE borrow_date < '2024-01-01'; -- 修改视图定义 ALTER VIEW v_borrow_overdue AS SELECT br.borrow_id, r.reader_name, b.book_name, br.borrow_date, br.return_date FROM borrow br JOIN reader r ON br.reader_id = r.reader_id JOIN book b ON br.book_id = b.book_id; -- 删除视图 DROP VIEW IF EXISTS v_borrow_overdue;通过视图更新数据有严格限制:视图包含聚合函数、GROUP BY、DISTINCT、多表连接时,不能通过视图做 INSERT 或 UPDATE。单表且包含基表主键的视图可以更新,但不建议依赖它,我见过因为视图更新触发基表数据被改、排查半天才发现问题来源的案例。创建视图时加 WITH CHECK OPTION 可以约束插入数据必须满足视图 WHERE 条件,比如只允许插入 return_date IS NULL 的记录,这样视图内数据口径不会被破坏。
3.2 索引:联合索引的最左前缀原则和 EXPLAIN 验证
大纲索引部分要求的操作是创建和删除索引,并且要看索引对查询的影响。实践中最关键的是知道什么时候建索引、建在哪些字段上。索引不是建得越多越好,每个索引都要额外空间,写入时还要维护 B+ 树,写多读少的表建一堆索引是净亏。
-- 普通索引 CREATE INDEX idx_borrow_date ON borrow(borrow_date); -- 唯一索引 CREATE UNIQUE INDEX uk_reader_phone ON reader(phone); -- 联合索引:查询条件里同时出现 reader_id 和 borrow_date 时更合适 CREATE INDEX idx_reader_borrow_date ON borrow(reader_id, borrow_date); -- 用 ALTER TABLE 也能加索引 ALTER TABLE book ADD INDEX idx_book_name (book_name); -- 删除索引 DROP INDEX idx_borrow_date ON borrow;联合索引遵循最左前缀原则:idx_reader_borrow_date 对 WHERE reader_id = 1 有效,对 WHERE borrow_date = '2024-01-01' 无效。把等值查询的字段放前面,范围查询的字段放后面,是联合索引设计的常见经验。加完索引后不要靠感觉判断有没有生效,用 EXPLAIN 看一下执行计划:
EXPLAIN SELECT * FROM borrow WHERE reader_id = 1 AND borrow_date > '2024-01-01';关注 type 列和 key 列,key 显示实际用到的索引名,type 从 const、ref、range 一路到 ALL,看到 ALL 基本就是全表扫描,该考虑加索引了。覆盖索引是另一个优化点:让查询的所有字段都包含在索引里,连回表都省了。这些细节大纲没有展开,但吃透索引对查询的影响,是第七模块真正想让你掌握的能力。
3.3 分区:类型选错,查询可能比不分还慢
索引与分区放在同一个模块是有原因的:分区是表级别的物理存储拆分,索引是字段级别的加速。RANGE 分区适合按时间归档,LIST 分区适合按固定枚举值拆分,HASH 和 KEY 分区适合把数据均匀打散到多个分区。分区键必须包含在主键和唯一索引里,这是 MySQL 的硬性规定。
-- RANGE分区:按年份归档借阅记录 CREATE TABLE borrow_part ( borrow_id INT NOT NULL, reader_id INT NOT NULL, borrow_date DATE NOT NULL, PRIMARY KEY (borrow_id, borrow_date) ) PARTITION BY RANGE (YEAR(borrow_date)) ( PARTITION p2022 VALUES LESS THAN (2023), PARTITION p2023 VALUES LESS THAN (2024), PARTITION p2024 VALUES LESS THAN (2025), PARTITION p_future VALUES LESS THAN MAXVALUE ); -- 查看分区情况 SELECT PARTITION_NAME, TABLE_ROWS FROM information_schema.PARTITIONS WHERE TABLE_NAME = 'borrow_part';选错分区类型是常见问题。按时间字段做报表查询,用 RANGE 分区配合分区裁剪,查询条件带上时间范围时 MySQL 只扫对应分区;但如果分区键建立在对查询毫无帮助的字段上,每次查询都要扫描所有分区,性能反而不如单表。管理分区也需要注意业务影响:DROP PARTITION 会直接删掉整个分区的数据,比 DELETE 快得多,但也意味着不可恢复,执行前必须确认这个分区确实是废弃数据。大纲要求掌握分区的创建与管理,实际工作里我的建议是表数据量没到千万级、没有明确的时间归档需求之前,先用索引,别急着分区。
4. 存储过程、触发器与事件:数据库编程模块的三个易错点
第八模块进入数据库编程,是大纲里最像“写代码”的部分。MySQL 语言结构、存储过程、存储函数、触发器、事件五块内容,难度梯度明显。存储过程是基础,触发器是隐式逻辑,事件是定时任务。这一章我重点说三个我实际走过弯路的地方。
4.1 存储过程与函数:参数模式、事务与调用方式
存储过程的价值在于把一段多步骤的 SQL 逻辑封装起来,减少重复代码,也方便把权限按“调用过程”而不是“直接操作表”来授。大纲要求掌握创建、调用、删除以及流程控制语句。写存储过程前先改分隔符,否则客户端会把分号当成语句结束,这是新手最常踩的坑。
-- 修改分隔符为 //,避免与过程体内的分号冲突 DELIMITER // CREATE PROCEDURE sp_borrow_book( IN p_reader_id INT, IN p_book_id INT, OUT p_result VARCHAR(50) ) BEGIN DECLARE v_stock INT DEFAULT 0; SELECT stock INTO v_stock FROM book WHERE book_id = p_book_id; IF v_stock <= 0 THEN SET p_result = '无库存'; ELSE INSERT INTO borrow(reader_id, book_id, borrow_date) VALUES (p_reader_id, p_book_id, CURDATE()); UPDATE book SET stock = stock - 1 WHERE book_id = p_book_id; SET p_result = '借阅成功'; END IF; END // DELIMITER ; -- 调用 CALL sp_borrow_book(1, 3, @result); SELECT @result;IN 参数是传入值,OUT 参数是传出值,INOUT 双向。过程体内 IF/ELSE、WHILE、LOOP 这些流程控制语句,加上 DECLARE 声明局部变量,组成了 MySQL 编程的骨架。把事务放进过程体比在外部散着写更安全:整个借阅过程要么全部成功要么全部回滚,不会出现库存减了但借阅记录没插进去的中间状态。
存储函数和过程的区别在于函数必须有返回值,可以直接用在 SELECT 里,比如 SELECT cal_fine(borrow_id) FROM borrow。但函数里不能做会改变数据的事务操作,规范上也不建议在函数里写 DML。这里有个经验:存储过程适合封装“动作”,存储函数适合封装“计算”,别混用。
4.2 触发器:时机和粒度决定排查难度
触发器是在 INSERT、UPDATE、DELETE 操作前后自动执行的 SQL。它最大的特点是隐式——你只看到一条 UPDATE,背后可能还动了三张表。大纲要求掌握创建和删除,综合实例里常用于日志记录或自动维护汇总字段。写法上没有太多语法难点,难点在于规划触发时机和意识到它对性能的影响。
DELIMITER // CREATE TRIGGER trg_reader_insert AFTER INSERT ON reader FOR EACH ROW BEGIN INSERT INTO reader_log(reader_id, action, action_time) VALUES (NEW.reader_id, 'INSERT', NOW()); END // DELIMITER ; -- 删触发器 DROP TRIGGER trg_reader_insert;触发器里 NEW 表示新行,OLD 表示旧行,UPDATE 触发器里两者都能用,INSERT 只有 NEW,DELETE 只有 OLD。FOR EACH ROW 表示逐行触发,批量 UPDATE 一万行就执行一万次触发器,这也是批量操作时触发器拖慢速度的根源。我的血泪经验是:触发器里不要做复杂查询,更不要调用存储过程,否则排查问题时你会发现一行 UPDATE 引发的连锁反应像黑匣子一样难以追踪。
4.3 事件调度器:定时任务关掉等于白写
事件是 MySQL 的定时任务,可以用来做定期清理、定期统计、定期备份。很多人写了事件却不生效,先查 event_scheduler 是否开启。大纲第八模块专门列了事件的创建与管理,下面是一个每天凌晨执行归档清理的事件。
-- 查看调度器状态 SHOW VARIABLES LIKE 'event_scheduler'; -- 开启调度器(重启后失效,要持久化需写入配置文件) SET GLOBAL event_scheduler = ON; DELIMITER // CREATE EVENT ev_clean_expired_log ON SCHEDULE EVERY 1 DAY STARTS '2024-01-01 02:00:00' DO BEGIN DELETE FROM reader_log WHERE action_time < NOW() - INTERVAL 90 DAY; END // DELIMITER ; -- 事件管理 ALTER EVENT ev_clean_expired_log DISABLE; -- 临时停用 DROP EVENT IF EXISTS ev_clean_expired_log; -- 删除ON SCHEDULE EVERY 1 DAY 是频率,STARTS 指定首次执行时间。事件的权限要求比较明确:创建事件需要 EVENT 权限,操作的是 mysql.event 表。此外事件里的 SQL 默认不受事务保护,DELETE 这类操作要谨慎,最好也带上 LIMIT 分批删。触发器和事件的区别一句话说清:触发器由 DML 触发,事件由时间触发,二者都不是能随意堆砌的东西,都会让数据库的行为变得不透明。
5. 数据安全与备份恢复:权限、事务、日志的五类常见问题排查
最后这个模块是事故高发区。用户权限、备份恢复、事务与多用户,每块都有典型的翻车现场。我挑了五类实际工作中反复出现的问题,按现象、原因、解决三步说透。很多事故发生后才发现,数据库的后悔药其实早就准备好了,只是你没来得及吃。
5.1 用户与权限:最小授权和 GRANT 不生效
权限管理的目标不是功能多,而是权限小。大纲要求掌握用户增删、授权收权和界面操作。命令行方式如下:
-- 创建用户并设置密码 CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'StrongPass_2024'; -- 只授查询权限 GRANT SELECT ON LibraryDB.* TO 'app_user'@'localhost'; -- 授予增删改权限 GRANT SELECT, INSERT, UPDATE, DELETE ON LibraryDB.* TO 'app_user'@'localhost'; -- 授权存储过程执行权 GRANT EXECUTE ON PROCEDURE LibraryDB.sp_borrow_book TO 'app_user'@'localhost'; -- 回收权限 REVOKE DELETE ON LibraryDB.* FROM 'app_user'@'localhost'; -- 删除用户 DROP USER 'app_user'@'localhost';现象一:执行 GRANT 后客户端连进来仍然报权限不足。原因:如果之前直接用 INSERT/UPDATE 改过 mysql.user 表,需要刷新权限;或者是关联了新库新表但没加 FLUSH PRIVILEGES。解决:统一用 GRANT/REVOKE 语句操作,不要手动改权限表,改完后执行 FLUSH PRIVILEGES。MySQL 8.0 里默认认证插件 caching_sha2_password 导致老客户端连接报错也很常见,解决方式是升级客户端,或创建用户时显式指定 mysql_native_password。
5.2 备份与恢复:mysqldump 参数与 binlog 增量
备份是大纲第九模块的重头戏,要求掌握备份与恢复的各种方法。逻辑备份最常用的是 mysqldump,物理备份用 xtrabackup 的场景也有,但课程范围里 mysqldump 足够。我常用的一组参数如下:
# 单库逻辑备份,尽量少锁表 mysqldump -uroot -p --single-transaction --set-gtid-purged=OFF --default-character-set=utf8mb4 LibraryDB > LibraryDB_$(date +%F).sql # 恢复:先建库,再导入 mysql -uroot -p -e "CREATE DATABASE IF NOT EXISTS LibraryDB DEFAULT CHARSET utf8mb4;" mysql -uroot -p LibraryDB < LibraryDB_2024-06-01.sql--single-transaction 在 InnoDB 下用一致性快照备份,备份期间 DML 不影响一致性,但 DDL 仍可能造成问题;--set-gtid-purged=OFF 是为了让备份文件能恢复到非 GTID 或目标差异较大的实例;--default-character-set=utf8mb4 防止中文乱码。恢复前先建库再导入,顺序反了会直接报 No database selected。
现象二:备份恢复后,某张表的自增列又从头开始,产生主键冲突或业务错乱。原因:mysqldump 默认导出表结构和数据,但不重置 AUTO_INCREMENT 计数器,恢复到新库后计数器按现有数据重新计算,如果之前删过大量行,新插入的记录可能复用旧 ID。解决:恢复后对关键表执行 ALTER TABLE xxx AUTO_INCREMENT = N; 或者应用层不依赖自增 ID 的连续性。
需要补充的是,只有全量备份、没有 binlog 时,误删数据后最多只能恢复到上一次备份点。想恢复到误删前一刻,必须开启 binlog 并配合增量导入:
-- 查看 binlog 状态 SHOW VARIABLES LIKE 'log_bin'; SHOW BINARY LOGS; -- 导出指定时间段的 binlog 为 SQL mysqlbinlog --start-datetime='2024-06-01 00:00:00' --stop-datetime='2024-06-01 12:00:00' binlog.000023 > inc_20240601.sqlmysqlbinlog 导出的文件同样用 mysql 客户端导入。binlog 是数据库同步、主从复制的核心依赖,开启它会让写入有额外开销,但相比误删后没有后悔药可用,这笔开销很值得。
5.3 事务与多用户:元数据锁、锁等待与外键失败的排查
事务的 ACID 特性和多用户并发下的锁定机制是大纲第九模块的收尾内容。理论部分不多说,直接给三个高频事故的排查思路。
现象三:执行 ALTER TABLE 修改表结构时,一直卡在 Waiting for table metadata lock。原因:有长事务或者未提交的查询还握着这张表的元数据锁。解决:先查 information_schema.innodb_trx 找到事务 ID,再 kill 对应连接。
-- 查未结束的事务 SELECT trx_id, trx_state, trx_started, trx_mysql_thread_id FROM information_schema.innodb_trx; -- 找到线程ID后结束该会话 KILL 12345;现象四:UPDATE 语句执行缓慢,且大量会话阻塞在同一个状态。原因:更新条件没走索引,InnoDB 从行锁升级为大量行锁甚至间隙锁,互相等待形成锁链。解决:先杀掉阻塞源,再用 EXPLAIN 检查 UPDATE 的 WHERE 是否走索引,最后重试并控制事务里更新的行数。
现象五:删除借阅记录时提示 Cannot delete or update a parent row: a foreign key constraint fails。原因:子表里还有引用这条父表记录的数据。解决:先删子表记录,或者把外键改成 ON DELETE CASCADE,但后者要确认业务允许级联删除。这个错误在课程实验里几乎人手一次,看到外键约束失败的报错,第一步永远是查子表数据,而不是去看父表。
事务这里有个习惯建议:把事务做得短而明确,BEGIN 后尽快提交,不要在事务里做查询后停下来等用户确认。长事务是锁等待和 binlog 膨胀的温床,也是各类诡异死锁的源头。大纲要求“了解事务和多用户处理机制”,实际上这块内容决定了你在生产环境里能不能活过大促和并发高峰。
6. 把大纲转成两周训练闭环:一张自查清单和三个验证手段
大纲的价值在拆解,拆完之后怎么确认自己真的学会了?我会用三个手段做体检。第一个手段是查 information_schema,把每个表的数据量和空间占用拉出来,确认建表、数据操作后的现状符合预期:
SELECT table_name, table_rows, ROUND(data_length / 1024 / 1024, 2) AS data_mb, ROUND(index_length / 1024 / 1024, 2) AS index_mb FROM information_schema.tables WHERE table_schema = 'LibraryDB' ORDER BY data_mb DESC;如果在练习中给表加过索引,index_mb 会上升,这能直观看到索引的空间成本。第二个手段是 EXPLAIN 每一条你写的核心查询,确认 key 列有索引用、type 不是 ALL。第三个手段是把大纲的九个模块压缩成两周计划:第 1-2 天安装与建库建表,第 3-4 天数据操作与单表查询,第 5-6 天多表查询与视图,第 7-8 天索引和 EXPLAIN 调优,第 9-10 天存储过程与触发器,第 11 天事件与定时清理,第 12 天备份恢复演练,第 13-14 天事务与权限的多人协作测试。每一阶段完成就把 PetStore 实例对应的代码敲一遍,别只读。
| 模块 | 验证方法 |
|---|---|
| 安装配置 | Navicat 能连上本地 MySQL,命令行也能连接 |
| 数据库设计 | E-R 图能转成关系模型,范式检查无冗余 |
| 数据定义 | SHOW CREATE TABLE 输出与设计一致 |
| 数据操作 | 事务内增删改后 ROLLBACK/COMMIT 可控 |
| 数据查询 | 每类查询跑通并 EXPLAIN 验证过 |
| 视图 | 能建能查,知道哪些视图不可更新 |
| 索引分区 | key 列有值,分区裁剪生效 |
| 数据库编程 | 存储过程带 IN/OUT 参数跑通,事件能触发 |
| 数据安全 | 低权限用户只能访问授权库表 |
我带新人时吃过最大的亏是重讲解轻验证,讲完一大轮,一上机就在外键约束和权限上报错,后来强制每个模块收尾必须过一遍自查清单,翻车率立刻降下来。从那以后我每次带人学 MySQL,都会先把这份大纲的学时分配合并成计划表,再让他逐项打钩。课程大纲是别人的骨架,能不能变成你自己的肌肉,取决于每一步你有没有真的跑过。希望帮到你。
本文还有配套的精品资源,点击获取