复习这两章之前,我一直有一种错觉:MySQL的增删改查已经会写了,第三第四章随便看看就行。真正开始二刷黑马程序员这套课才反应过来,第三章和第四章是整个MySQL基础的分水岭——前面你是在“能跑”,这两章才决定你能不能“跑得对”。很多面试题里问到的分组查询、多表关联、外键设计,根子都在这里。这篇笔记我就顺着黑马这套课程第三章第四章的复习主线,把重点、坑点和自查方法整理出来,给正在学这套课、准备期末复习或者打算二刷补基础的同学做个参考。
1. 这两章到底在训练什么:先定位再复习
1.1 我的复习坐标图
黑马这套MySQL课程不同版本章节划分会有点差别,但第三第四章的大方向基本一致:第三章磨查询能力,第四章磨表结构设计能力。在我看来,这两章对应的是同一件事的两种角度——你既要会从表里把数据“拿出来”,也要会保证放进表里的数据“不会坏”。
具体展开的话,第三章的核心是DQL,也就是数据查询语言。前面学的CREATE、INSERT、UPDATE、DELETE都还算直白,到了SELECT这里才开始真正考验逻辑。GROUP BY怎么分组、HAVING和WHERE有什么区别、多表连接到底连的是哪几张表,这些一旦理解不到位,后面学索引优化会连题目都看不懂。
第四章的核心则是约束和表关系。如果只是会写CREATE TABLE,那叫搭了个空壳;只有把主键、外键、唯一约束、默认值这些用明白,才算是能把一张“随便记两笔的Excel表”升级成“有边界、有规则的结构化数据表”。很多同学学完这两章觉得散,是因为没有意识到:第三章是面向结果的,第四章是面向结构的,两者合起来才是完整的建表查询思维。
我的复习路线是两条线同时推进:一条线刷查询题,确保所有语法都能不看笔记写出来;另一条线拿着练习库里的实际表结构,逐个字段问自己为什么要加这个约束、删掉会出什么问题。一边练输出,一边练反思,比单纯背笔记扎实得多。
1.2 常见的错误复习姿势
复习这两章时最容易踩的第一个坑是“看课多、动手少”。黑马的课程讲得清楚,例题也顺,跟着敲一遍下来觉得很懂,等合上视频自己建两张表再写关联查询,立刻卡住。这个阶段必须强制自己脱离视频做练习,哪怕对着笔记抄SQL,也比你盯着屏幕看一节课有效。
第二个坑是“只记语法,不记语义”。比如知道GROUP BY后面跟的字段决定分组,但不知道SELECT后面能跟哪些列是受限于SQL模式的;知道ORDER BY可以排序,但没想过NULL值和中文按什么规则排。语法是壳,语义才是理解。复习时每看到一个语法点,都追问一句“MySQL为什么要这样设计”“这个行为在什么场景下会坑我”,这才是能带走的东西。
2. 第三章的重头戏:DQL查询的理解深度决定刷题速度
2.1 书写顺序和逻辑执行顺序:先记熟这张对照清单
DQL大概是SQL里最违反直觉的部分:你写的顺序和MySQL执行时真正采取的顺序根本不是一回事。书写顺序是SELECT、FROM、WHERE、GROUP BY、HAVING、ORDER BY、LIMIT,而逻辑执行顺序是FROM、WHERE、GROUP BY、HAVING、SELECT、ORDER BY、LIMIT。
这两者不一致会直接导致一些经典问题。比如你在SELECT里面给某个列起了别名,然后想在WHERE里用这个别名筛选,通常会报错说不存在该列。原因很简单:WHERE在SELECT之前执行,别名这时候还没生成。而HAVING就可以用别名,因为它排在SELECT后面。再比如GROUP BY分组之后,WHERE已经执行完了,所以WHERE里不能写聚合函数,只能写在HAVING里。
我复习时习惯把逻辑执行顺序抄在笔记本第一页,每次做题前先在脑子里走一遍这条管线。题目一旦涉及分组聚合,第一步就是确认“这个过滤条件到底该放WHERE还是HAVING”,想清楚了再动手写SQL。放错位置往往不报错,只是结果错,这种错误最难排查。
-- 正确写法:先过滤再分组 SELECT dept_id, COUNT(*) FROM emp WHERE salary > 3000 GROUP BY dept_id; -- 错误示范:拿聚合函数写进WHERE SELECT dept_id, COUNT(*) FROM emp WHERE COUNT(*) > 3 GROUP BY dept_id;第二个要重点理解的是执行顺序对数据量的影响。WHERE在分组前过滤,意味着可以大幅减少后续分组处理的数据量;HAVING在分组后过滤,天然就要消耗更多计算资源。能用WHERE解决的优先用WHERE,这不仅是正确性问题,也是性能意识的第一课。
2.2 聚合、分组、去重:最容易产出“错误但看着合理”结果的地方
聚合函数是第三章里坑最多的区域,而且坑得相当隐蔽。先说COUNT,很多人习惯在统计行数时写COUNT(1)或COUNT(*),这两者在InnoDB下的实际行数统计基本等价,不必纠结。真正需要小心的是COUNT(某字段),它不统计NULL值。也就是说,如果你统计员工表的入职日期,想数一数有多少人填了入职日期,那用COUNT(hire_date)没问题;但如果你想数员工总数,里面恰好有人的入职日期是NULL,你拿COUNT(hire_date)就会少算。
再说GROUP BY和DISTINCT的关系。它们都能去重,但语义不同。DISTINCT是“对查询结果里的整行去重”,GROUP BY是“按指定字段分组后做聚合”。常见错误写法是这样:
-- 在MySQL 5.7及以上默认开启ONLY_FULL_GROUP_BY的情况下会直接报错 SELECT dept_id, emp_name FROM emp GROUP BY dept_id;这个报错信息是[Err] 1055,原因是SELECT后面出现了既不在GROUP BY里、也没有包在聚合函数里的字段。MySQL没法决定到底该显示哪个emp_name,干脆拒绝执行。这是面试和考试里的高频考点,理解它比背报错编码更重要:分组之后,组内其他字段已经失去了“单行确定性”,不能随意出现在SELECT列表里。真想展示组内某个信息,要么用聚合函数处理,要么配合GROUP_CONCAT之类的函数把信息串起来。
-- 正确的思路:分组后只能看分组字段+聚合结果 SELECT dept_id, COUNT(*), MAX(salary), GROUP_CONCAT(emp_name) FROM emp GROUP BY dept_id;顺带提一个经常被问到的冷门问题:“OR能去重吗?”答案是不能。OR是条件连接符,负责“满足任一条件即返回”,它本身完全不参与去重。去重要靠DISTINCT或GROUP BY。比如你想查出“部门编号为1或者2的员工姓名,且姓名不重复”,正确写法是SELECT DISTINCT emp_name FROM emp WHERE dept_id = 1 OR dept_id = 2。如果你指望OR顺便把相同名字合并掉,结果一定不会如你所愿。搜索引擎里经常有人搜这个,说明很多新人把条件筛选和去重搞混了。
2.3 连接查询:先画关系再写SQL
多表查询是第三章的压轴戏,分数占比和面试出现率都极高。我学的时候犯过一个典型错误:看见题目就急着写JOIN,写完连自己都不知道为什么要用LEFT JOIN而不是INNER JOIN。后来摸索出来的正确流程是先画表关系图,再确定连接类型,最后才是写SQL。
内连接只返回两边都能匹配上的行,适合“只要有关系存在的数据”;左外连接保留左表全部行,右表没有对应内容就补NULL,适合“以左表为主体,附带右表信息”的场景;右外连接反过来。这里有个细节值得反复体会:LEFT JOIN的结果里,右表字段为NULL的那些行,恰恰就是“右表里不存在关联记录”的行。利用这个特征,就能写出“找出没有部门的人”“找出从未下单的用户”这类反直觉查询。
-- 找出没有对应部门的员工 SELECT e.* FROM emp e LEFT JOIN dept d ON e.dept_id = d.id WHERE d.id IS NULL;这个写法的巧妙之处在于,如果直接写WHERE e.dept_id NOT IN (SELECT id FROM dept),碰到dept_id为NULL的员工时会出问题,因为SQL里NULL不参与NOT IN判断,结果可能漏行。用LEFT JOIN配合IS NULL判断,逻辑上更严密。复习的时候我建议把“什么时候用子查询、什么时候用连接查询”也做一次对比,二者在很多场景可以互换,但理解各自的语义边界才是关键。自连接也是必须亲手练一遍的,典型场景是员工表里同时存了经理ID和员工ID,需要查“每个员工的经理姓名”,本质上就是把同一张表当成两张不同的表用。
2.4 排序、分页和NULL处理
排序这块看起来简单,但细节很多。ORDER BY默认升序,NULL值在升序时排在最前,降序时排在最后,不同版本和排序规则下可能还会变动。如果你希望把NULL统一放到末尾,MySQL 8.0可以直接写ORDER BY字段名 IS NULL, 字段名;这种技巧在分页输出报表时特别实用。
分页要理解LIMIT的偏移计算。LIMIT 10, 5的含义是跳过10行取5行,而不是第10行到第5行。很多新人把LIMIT 0, 10和LIMIT 10, 10搞混,翻页翻着翻着数据就重了或丢了。在真正的大数据量场景里,深分页会导致MySQL扫描大量无用的行,不过那是性能优化章节的事,现阶段能写对偏移量就合格了。
还有一个容易被忽略的点:模糊查询时LIKE的%位置。'张%'是查姓张的,'%张%'是查任意位置含张的,前者能用索引加速,后者基本用不上。这一点在学完索引回头看会特别有感触,但复习阶段就要养成习惯——写LIKE前先想想,这个通配符放的位置真的合理吗?
3. 第四章的核心:约束不是“加个限制”,而是把表设计成有结构的样子
3.1 五大约束的边界
第四章的约束体系其实是“给数据立规矩”。很多人把约束背成了名词解释,考试能默写,建表时却不知道该加什么。我的理解是每个约束都在回答一个具体问题:
- 主键约束:这张表靠什么字段唯一标识一行?答案通常是自增ID。
- 唯一约束:哪些业务字段不允许重复?比如身份证号、手机号。
- 非空约束:哪个字段是必填项?比如用户表的账号。
- 默认约束:用户不填时给什么值?比如创建时间默认当前时间、状态默认1。
- 外键约束:这张表和别的表的关系怎么兜底?
这里有个易错点:UNIQUE约束允许存在多个NULL值。MySQL认为NULL表示“未知”,未知与未知不相等,所以一张表的唯一索引列可以出现多行NULL。这个行为在业务上要考虑清楚,如果业务要求“手机号要么不填,填了就不能重复”,UNIQUE加在手机号字段上恰好合适;如果要求“每行都必须有且唯一的号码”,那还得叠加NOT NULL。
主键也不一定只有一个字段,联合主键由多个字段共同决定唯一性。多对多关系里的中间表通常就用联合主键,例如(teacher_id, student_id)共同构成主键,可以防止同一条关系被插入两次。这是第四章里和实践结合最紧密的知识点之一,复习时可以亲手建一张中间表感受一下。
3.2 外键约束:学习时必建,项目里慎用
外键是第四章里最值得讨论的约束。黑马课程里讲外键时一般会给ON UPDATE和ON DELETE的选项:CASCADE表示父表更新或删除时子表同步;SET NULL表示父表删除时子表对应字段置空;RESTRICT和NO ACTION都表示拒绝操作。这个设计本身很好理解,但真正要琢磨的是“该不该用”。
我个人的体会分两个阶段:学习阶段建议建,因为外键能帮你直观感受到引用完整性,删父表数据报错时你才知道什么叫做“关系约束”;但到了项目开发阶段,物理外键要非常谨慎。真实项目里外键会带来几个麻烦:插入和更新时MySQL需要额外检查关联表,性能有损耗;在分库分表环境下外键根本没法跨库生效,反而给扩容挖坑;很多大厂实践里宁愿用逻辑外键——也就是只在子表存一个parent_id字段,不建物理外键,靠应用层代码维护关系。
这不是说外键没用,而是说你要知道外键的适用边界。考试题目问你“外键的作用”时,答“保证数据的完整性、防止非法数据插入”是对的;面试官问你“项目里为什么不建外键”时,再说同样的话就会被追问到怀疑人生。这两个场景的逻辑要分开。
3.3 三种表关系:一对多、多对多、一对一的建表落点
表关系的设计是第四章的落地环节。一对多关系里,“一”方是主表,“多”方是从表,外键放在“多”方。典型的员工和部门就是一对多:多个员工属于一个部门,所以员工表保存dept_id。
多对多关系必须引入中间表。学生和课程是多对多,解决办法是建一张选课表,里面存student_id和course_id,再把这两个字段设为联合主键或者单独加一个自增主键。如果觉得抽象,可以把中间表理解成“记账本”——它不存储实体本身,只存储实体之间的关系。中间表里还可以扩展额外字段,比如选课时间、成绩,这就是多对多关系在设计上的灵活性。
一对一关系比较少见,常见场景是“用户表”和“用户详情表”拆分,或者大字段单独存一张表。实现方式是在任意一方加外键并设置为唯一约束,保证只会匹配一条记录。复习这三种关系时,我强烈建议自己画一张ER图,用框和连线把主键、外键标出来,画完再建表,思路会清晰很多。第四章学的不是语法,是建模思维,这部分练得越好,后面学项目阶段的表设计就越轻松。
4. 我复习时踩过的坑和排查思路
4.1 1055错误:默认模式改变的连锁反应
复习分组查询时我一度很崩溃:老师在视频里写的SQL能跑出结果,我照着写却报错[Err] 1055。后来查资料才发现,MySQL 5.7之后默认开启了ONLY_FULL_GROUP_BY这个SQL模式,它要求SELECT后面的非聚合列必须出现在GROUP BY里。老师视频可能是在低版本录的,或者服务器修改过配置,所以行为不一样。
这个坑的排查思路值得记一下:遇到报错先看错误码,然后想“是不是版本或者SQL模式的问题”。可以用SELECT @@sql_mode;查看当前模式。在学习和考试环境下,不建议为了跑通SQL去关闭ONLY_FULL_GROUP_BY,因为面试和大厂实际环境基本都是默认开启的,你应该改写SQL去适应规则,而不是让规则迁就你。这也是我把执行顺序和分组语义理解放在第三章第一优先级的原因——你只要真正理解了分组之后字段的确定性要求,这类题根本不会慌。
4.2 删除父表数据失败:外键行为的实际表现
第四章练习外键时,我建了一张部门表和一张员工表,员工表通过dept_id外键关联部门表。然后我执行DELETE FROM dept WHERE id = 1,直接报错,提示有外键约束冲突。当时我第一反应是“外键真麻烦”,后来才意识到这正是外键在履行自己的职责。
排查这个问题可以先看外键创建时的行为定义。如果ON DELETE是RESTRICT,那父表有子记录就删不掉,这是默认且安全的;如果你希望删除部门时员工数据跟着内迁,可以写ON DELETE SET NULL,但前提是dept_id字段允许NULL;如果业务上确定要连坐删除,才用CASCADE。实际工作里CASCADE要尤其谨慎,一个不留神删一张父表,整棵关联树的数据全没了,恢复成本极高。复习时给自己的任务是:每种行为都建一次表、执行一次删改,亲眼看看结果,比死记选项含义直观得多。
4.3 查询结果和直觉不一致:NULL和连接条件是最常见元凶
做查询练习时有一类问题特别伤脑筋:SQL没报错,但结果跟业务预期差了几行。排查下来大概率是两种原因。一种是NULL参与计算导致预期偏差,比如WHERE salary > 3000并不会包含salary为NULL的员工,你以为是漏了,实际上是NULL和任何比较运算的结果都是未知,不会被筛选出来。另一种是连接条件写少了造成的笛卡尔积,比如两张表JOIN时忘了写ON条件,MySQL直接把所有行两两组合,结果数量瞬间变成两表行数之积。
排查这类问题的通用思路是分步验证:先去掉WHERE看全表数据,再单独验证连接条件,再逐步加过滤条件。不要一次性写出一长串SQL后盯着结果发呆。SQL是声明式语言,排查越细,定位越快。刷题时我还会故意写几个带NULL数据的表做测试,专门验证COUNT、SUM、WHERE、JOIN在NULL参与时的行为,练过一次就有肌肉记忆了。
5. 配合这套课程资源的复习节奏参考
5.1 三轮复习的具体安排
我自己用的是三轮复习法,适合以黑马课程为主线的学习节奏。第一轮是跟着视频倍速过,重点看第三章DQL和第四章约束部分,看完立刻把例题重新写一遍,不许看答案。这一轮你要做到“所有内置函数的用法能想起来、所有约束的语法能写出来”。第二轮不碰视频,直接翻讲义笔记和自己在第一轮整理的错题,把每章的知识点按“概念—语法—案例—坑点”四个维度复盘。这一轮我会给自己做一份表格,把WHERE和HAVING、内连接和外连接、物理外键和逻辑外键这类成对概念放到一起对照记忆。第三轮是纯刷题,重点做综合场景题和历年考试模拟题。
三轮下来,基本能把知识从“老师讲过的”变成“我自己能输出的”。这个转化过程没有捷径,就是重复和检验。
5.2 用一张业务表把两章题型串起来
复习到后期,我发现最有效的方法不是对着题目练习,而是设计一张业务表,把两章的内容全部串进去。比如设计一张“学生选课系统”,包含学生表、课程表、选课表三张表。在选课表里建联合主键;在学生表里给手机号加唯一约束;在选课表里建外键关联学生表和课程表;然后写各种查询:查每门课的选课人数、查选课最多的学生、查没有选课的学生、查选了超过两门课的学生。这么一张图,几乎覆盖了第三章和第四章80%的考点。
做完建表题后,再给自己增加修改表的练习。比如给表加一个字段、修改字段类型、给已有数据的表加唯一约束时如果数据重复会报什么错。每遇到一个报错,就追问一句“为什么会报错”,这个习惯让我的复习效果比单纯看教程好很多。
5.3 值得用的配套资源
黑马这套课程本身就很完整,但复习阶段不能只抱着视频。我实际用下来有效的配套资源有三类:第一类是市面上整理好的课程讲义笔记,通常会把重点语法、执行顺序、常见错误整理成表,比视频方便检索;第二类是SQL刷题网站,比如牛客的SQL题库和力扣的数据库题库,上面有很多和第三章多表查询、分组统计高度重合的题目;第三类是数据库官方文档里关于SQL模式和执行顺序的说明章节,遇到拿不准的行为直接查文档比问AI更踏实。
另外强烈建议自己准备一个练习库,不用太大,三张表左右就够了,关键是数据要故意塞进一些边界值:空字符串、NULL、重复记录、超长文本。只有数据够“脏”,你才能检验约束和查询语句是否真的合格。我自己的练习库里还有一张表的设计是故意有问题的——缺外键、缺唯一约束,然后我会用查询暴露数据冗余,再用ALTER TABLE去修。这个过程比任何模拟题都让人长记性。
最后再分享一个小技巧:复习完每一章后,把这一章你觉得最晦涩的五个概念写成一页纸,用大白话解释给一个完全没学过数据库的人听。如果你能让他听懂,说明你是真懂了。我每次做完这件事都会发现,有些我以为早就明白的概念,其实还停留在“会读不会说”的程度。如果觉得哪一章写起来特别费劲,别犹豫,回去把那节课重新过一遍,这往往是查漏补缺效率最高的时候。