需要学习的内容
🧱 MySQL 面试必学内容清单(速通版)
1️⃣ 基础 SQL(能用)
- CRUD 操作:
SELECT / INSERT / UPDATE / DELETE - 条件过滤:
WHERE / AND / OR / IN / BETWEEN / LIKE - 排序:
ORDER BY - 聚合函数:
COUNT / AVG / SUM / MAX / MIN - 分组:
GROUP BY / HAVING -
多表查询:
INNER JOIN / LEFT JOIN / RIGHT JOIN / FULL JOIN- 子查询(单行 / 多行 / EXISTS / IN)
常用函数:
IFNULL / COALESCE / CONCAT / DATE_FORMAT
2️⃣ 表设计
- 数据类型(INT / BIGINT / VARCHAR / DATE / DATETIME / TEXT / BLOB)
- 主键(PRIMARY KEY)
- 外键(FOREIGN KEY)
- 唯一约束(UNIQUE)
- 非空约束(NOT NULL)
- 一对多 / 多对多建表思路
-
索引类型基础:
- 单列索引 / 复合索引 / 唯一索引 / 全文索引
3️⃣ 索引(核心面试点)
- B+ 树原理(树结构、叶子节点存储)
- 聚簇索引 vs 非聚簇索引
- 联合索引与最左前缀原则
- 索引失效场景(函数、范围查询、类型不匹配)
- 覆盖索引(Covering Index)
4️⃣ 事务与锁(核心面试点)
-
事务 ACID 四特性:
- Atomicity(原子性)
- Consistency(一致性)
- Isolation(隔离性)
- Durability(持久性)
-
隔离级别:
- READ UNCOMMITTED / READ COMMITTED / REPEATABLE READ / SERIALIZABLE
脏读 / 不可重复读 / 幻读
-
锁机制:
- 行锁 / 表锁 / 间隙锁 / 意向锁
常见死锁问题及解决思路
5️⃣ SQL 性能优化(核心面试点)
- 使用
EXPLAIN查看执行计划 - 判断索引是否命中
- 分页优化(LIMIT + OFFSET vs keyset pagination)
- 慢查询分析与优化
- 避免 SELECT * / 减少不必要 join / 数据量大时拆表
6️⃣ 存储引擎基础
-
InnoDB vs MyISAM
- InnoDB:支持事务、行级锁、外键
- MyISAM:不支持事务、表级锁、查询快
主流应用场景
7️⃣ 面试题必备问答
- “索引为什么能提高查询效率?”
- “联合索引最左前缀原则是什么?”
- “事务四大特性是什么?隔离级别有什么区别?”
- “行锁和表锁有什么区别?间隙锁是干嘛的?”
- “SQL 慢了,你会怎么排查?”
- “InnoDB 和 MyISAM 的区别?”
💡 学习原则
- 学会写 SQL + 能解释原理 = 面试 90% 问题搞定
- 重点记住:索引 / 事务 / 锁 / 性能优化 / 多表 join
- 可以先把基础写通,再用视频 + 实例快速理解索引和事务
例子1
WHERE 和 HAVING 都是 SQL 中用于“过滤数据”的关键字,但两者的执行时机、作用对象、使用场景有本质区别
WHERE 在分组前执行,HAVING 在分组后执行。
1. 执行时机与作用对象
| 关键字 | 执行阶段 | 作用对象 | 能否使用聚合函数? |
|---|---|---|---|
WHERE |
分组前(GROUP BY 之前) |
原始数据中的“行”(如过滤年龄<18的用户) | 不能(此时还未分组,聚合函数无意义) |
HAVING |
分组后(GROUP BY 之后) |
分组后的“组”(如过滤订单数<2的用户组) | 能(基于分组后的聚合结果过滤) |
2. 直观例子对比
假设有 user 表(id、name、age)和 order 表(id、user_id、amount),需求:“查询年龄>18,且订单总金额>1000的用户”。
用 WHERE 过滤行(分组前):
SELECT u.name, SUM(o.amount) AS total_amount
FROM `user` u
JOIN `order` o ON u.id = o.user_id
WHERE u.age > 18 -- 分组前过滤:只保留年龄>18的用户的行
GROUP BY u.name;
-
WHERE u.age > 18的作用:在关联表后、分组前,先把年龄≤18的用户的所有记录过滤掉,减少后续分组的数据量。
用 HAVING 过滤组(分组后):
SELECT u.name, SUM(o.amount) AS total_amount
FROM `user` u
JOIN `order` o ON u.id = o.user_id
WHERE u.age > 18
GROUP BY u.name
HAVING total_amount > 1000; -- 分组后过滤:只保留总金额>1000的用户组
-
HAVING total_amount > 1000的作用:在分组并计算出每个用户的总金额后,过滤掉总金额≤1000的组。
3. 核心区别总结
| 区别点 | WHERE |
HAVING |
|---|---|---|
| 作用阶段 | 分组前(GROUP BY 之前) |
分组后(GROUP BY 之后) |
| 过滤对象 | 原始数据中的“行” | 分组后的“组” |
| 聚合函数支持 | 不支持(不能用 SUM()、COUNT() 等) |
支持(可以用分组后的聚合结果,如 total_amount) |
| 使用场景 | 过滤不需要的行,减少分组的数据量 | 过滤不符合条件的组,基于聚合结果筛选 |
| 性能影响 | 能提前过滤数据,减少后续计算量,性能更高 | 需等待分组完成后过滤,性能相对较低 |
4. 面试高频考点
问:“
WHERE和HAVING都能过滤数据,什么时候必须用HAVING?”
答:当过滤条件需要用到聚合函数(如SUM()、COUNT())时,必须用HAVING(因为WHERE不支持聚合函数)。例如“筛选订单数>5的用户”,必须用HAVING COUNT(order_id) > 5。问:“
HAVING可以替代WHERE吗?”
答:语法上可以(比如HAVING age > 18),但不推荐。因为HAVING在分组后执行,会导致更多数据参与分组计算,性能更低;而WHERE提前过滤,效率更高。问:“
WHERE里为什么不能用SELECT中的别名?而HAVING可以?”
答:因为WHERE在SELECT之前执行(此时别名还未定义),而HAVING在SELECT之后执行(能识别别名)。
一句话记住
WHERE 是“先筛行,再分组”,HAVING 是“先分组,再筛组”;聚合条件用 HAVING,普通条件用 WHERE。
例子2:SQL 语句的执行顺序(与书写顺序不同),理解它能帮你搞清楚“为什么有些语法能生效,有些会报错”,以及“如何写出更高效的 SQL”。
先看一个直观的例子
假设你写了这样一条 SQL:
SELECT username, COUNT(*) AS order_count -- 步骤6:选择最终要显示的字段
FROM `user` u -- 步骤1:确定主表
LEFT JOIN `order` o ON u.id = o.user_id -- 步骤2:关联其他表
WHERE u.age > 18 -- 步骤3:过滤行(分组前)
GROUP BY u.username -- 步骤4:按用户名分组
HAVING order_count > 2 -- 步骤5:过滤分组(分组后)
ORDER BY order_count DESC -- 步骤7:排序
LIMIT 10; -- 步骤8:限制结果数量
书写顺序是:SELECT → FROM → JOIN → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT
但执行顺序是:FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
为什么执行顺序是这样?
SQL 的执行逻辑类似“流水线加工数据”:先准备原材料(表数据),再一步步过滤、加工,最后输出结果。
FROM+JOIN:先确定“数据源”
数据库首先需要知道“从哪些表取数据”,以及“如何关联这些表”。这一步会生成一个“临时中间表”,包含所有关联后的原始数据(类似把多张表“合并”成一张大表)。WHERE:过滤“不合格的行”
在分组之前,先对中间表的数据进行过滤(比如排除年龄<18的用户)。这一步能减少后续处理的数据量(比如分组时不需要处理被过滤掉的行)。GROUP BY:按规则“分组”
把过滤后的行按指定字段分组(比如按用户名分组),每组会聚合为一行(后续聚合函数如COUNT()就是基于分组计算的)。HAVING:过滤“不合格的组”
分组后,对每个组的聚合结果进行过滤(比如只保留订单数>2的用户组)。注意:HAVING只能用在分组后,且可以使用GROUP BY中定义的聚合函数(如order_count)。SELECT:选择“最终要显示的字段”
到这一步,数据已经经过过滤和分组,数据库才会确定“最终返回哪些字段”(比如只显示用户名和订单数)。
为什么SELECT在后面?
因为前面的步骤(WHERE、GROUP BY)需要基于原始字段处理,而SELECT可能会重命名字段(如COUNT(*) AS order_count),如果SELECT先执行,前面的步骤就无法识别原始字段了。ORDER BY:对结果“排序”
排序需要基于SELECT确定的最终字段(比如按order_count降序),所以必须在SELECT之后。LIMIT:限制“返回的行数”
最后一步截取结果(比如只返回前10条),因为排序后才能确定“哪些行是前10”。
这个顺序对实际开发的影响
WHERE比HAVING更高效
WHERE在分组前过滤,能减少参与分组的数据量;HAVING在分组后过滤,所有数据都要先分组计算。
例:同样是“筛选年龄>18的用户”,用WHERE age>18比GROUP BY ... HAVING age>18效率高(后者会先对所有用户分组,再过滤)。ORDER BY可以用SELECT中的别名,WHERE不能
因为ORDER BY在SELECT之后执行(能识别别名),而WHERE在SELECT之前(无法识别别名)。
错误示例:WHERE order_count > 2(order_count是SELECT中定义的别名,WHERE不认识)。SELECT中的字段必须在GROUP BY中,或用聚合函数
因为GROUP BY会将每组聚合为一行,如果SELECT中的字段不在GROUP BY中,也没有聚合(如SELECT username, age ... GROUP BY username),数据库无法确定age取组内哪一行的值(MySQL 有非标准扩展允许这种写法,但不推荐,其他数据库如 PostgreSQL 会直接报错)。问:“
WHERE里为什么不能用SELECT中的别名?而HAVING可以?”
答:因为WHERE在SELECT之前执行(此时别名还未定义),而HAVING为MySQL 特例:允许用 SELECT 中的别名,其他数据库不行(需写原始聚合函数)。
事务
事务指逻辑上的一组操作,要么都执行要么都不执行
-- 1. 开启事务(关闭自动提交)
START TRANSACTION; -- 或 BEGIN;
-- 2. 执行SQL操作(如增删改)
UPDATE account SET balance = balance - 100 WHERE id = 1; -- A扣钱
UPDATE account SET balance = balance + 100 WHERE id = 2; -- B加钱
-- 3. 提交事务(所有操作生效,持久化到磁盘)
COMMIT;
-- 4. 若出错,回滚事务(所有操作撤销,恢复到开启前状态)
ROLLBACK;
Top comments (0)