📖 本篇简介:本篇是面试系列的第一篇,聚焦 MySQL 基础高频题,覆盖索引、事务、隔离级别、JOIN、存储引擎这五大块。前面 28 篇你已经把语法和原理都过了一遍,这一篇帮你把"会用"升级成"能答"。学完你应该能在白板上画出 B+Tree 索引结构、说清 ACID 与四种隔离级别、讲明白 LEFT JOIN 与 ON/WHERE 的坑。所有示例基于 MySQL 8.0 语法书写,本示例基于 MySQL 8.0 环境。
目录
- 本篇导读与答题思路
- 题目 1:为什么索引能加速查询,常见索引有哪几种
- 题目 2:事务的 ACID 分别是什么,靠什么实现
- 题目 3:四种隔离级别分别解决什么问题,MySQL 默认是哪个
- 题目 4:INNER JOIN LEFT JOIN 和 RIGHT JOIN 的区别
- 题目 5:MyISAM 和 InnoDB 有什么区别,为什么默认用 InnoDB
- 题目 6:什么是回表,什么是覆盖索引
- 题目 7:主键用自增 ID 还是 UUID,为什么
- 题目 8:NULL 值对索引和查询有什么影响
- 速查表:基础高频结论速查
- 注意事项:面试答题的常见丢分点
- 练习题:动手自测与参考
- 复习清单
本篇导读与答题思路
面试考基础题,不是考你背定义,而是考你能不能"一句话说清本质 + 举一个例子 + 说一个坑"。建议答题用三段式:
1. 先给结论:一句话回答题干,别绕弯子。
2. 再讲原理:用一句话解释为什么,可以带一个结构类比。
3. 最后给例子/坑:手写一段 SQL 或说一个线上踩过的坑,体现实战感。
基础题最怕"只会背书"。比如问你隔离级别,你把四个级别背出来只值一半分,能说出"RR 级别下 InnoDB 靠临键锁解决幻读,但仍然有半一致性读的坑"才是加分项。下面每道题都按这个思路给参考答案。
另外,答题时手里要有"图":索引题在脑子里画 B+Tree,事务题画 undo log 版本链,JOIN 题画韦恩图。画出来比说出来更有说服力。
关于答题时间:基础题一般 1~2 分钟答完,别拖。如果某个点你拿不准,宁可说"这块我理解是……,可能需要再确认",也别硬编——面试官一追问就露馅,坦诚反而加分。
最后提醒一句:基础题里凡是涉及"默认值"的(默认隔离级别、默认引擎、默认索引结构),都要条件反射式报出来,这是送分题,送分题丢了最可惜。
题目 1:为什么索引能加速查询,常见索引有哪几种
考点
- 索引的本质:用空间换时间,把无序的堆组织成有序的查找结构。
- InnoDB 默认索引结构 B+Tree 的特点。
- 聚簇索引与二级索引、主键索引与唯一索引、普通索引、联合索引的区别。
- 哈希索引、全文索引、前缀索引的适用场景。
参考答案
核心结论:索引相当于给排好序的"目录"。没有索引时找一条数据要全表扫描(一行行比对),有了 B+Tree 索引后,通过树的高度 3~4 层就能把千万级数据定位到几行,时间复杂度从 O(n) 降到 O(log n)。
常见索引分类:
- 按数据结构分:B+Tree 索引(InnoDB 默认,适合范围查询和排序)、Hash 索引(Memory 引擎默认,等值查询 O(1),但不支持范围和排序)。
- 按逻辑分:主键索引(聚簇索引,叶子节点存整行数据)、唯一索引、普通索引、联合索引(多列组合,遵守最左前缀)。
- 按存储分:聚簇索引(数据即索引,一张表只有一个)与二级索引(叶子节点存主键值,需要回表)。
建索引的可运行示例(MySQL 8.0):
-- 建一张用户表,主键即聚簇索引
CREATE TABLE t_user (
id BIGINT NOT NULL AUTO_INCREMENT, -- 主键,自带聚簇索引
name VARCHAR(50) NOT NULL,
id_card VARCHAR(18) NOT NULL, -- 身份证号
city VARCHAR(30) NOT NULL,
age INT NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_id_card (id_card), -- 唯一二级索引
KEY idx_city_age (city, age) -- 联合二级索引
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
为什么 InnoDB 选 B+Tree 而不是二叉树或哈希:B+Tree 矮胖(扇出大,磁盘 IO 次数少)、叶子节点用双向链表串联(天然支持范围查询和 ORDER BY)、非叶子节点不存数据(单页能放更多键,树更矮)。
追问
- 追问:索引是不是建得越多越好? 不是。索引加速读但拖慢写(每次 INSERT/UPDATE 都要同步维护所有索引),还占磁盘。一般单表索引控制在 5 个以内。
- 追问:联合索引 (city, age),查询 WHERE age = 20 能用上吗? 不能。违反最左前缀,跳过了前导列 city,优化器通常退化为全表扫描。
题目 2:事务的 ACID 分别是什么,靠什么实现
考点
- 原子性、一致性、隔离性、持久性四个特性的含义。
- 它们分别由 InnoDB 的哪些机制保证。
- 事务并发下的三大问题:脏读、不可重复读、幻读。
参考答案
先一句话给结论:ACID 是事务的四个承诺,InnoDB 用 undo log、redo log、锁与 MVCC 分别兜底。
| 特性 | 含义 | InnoDB 靠什么实现 |
|---|---|---|
| 原子性 Atomicity | 事务要么全成功,要么全回滚,不允许停在中间 | undo log(回滚日志),失败时反向回滚 |
| 持久性 Durability | 提交后即使宕机也不丢数据 | redo log(WAL 预写日志)+ 双写缓冲 |
| 隔离性 Isolation | 并发事务互不干扰 | 锁机制 + MVCC(多版本并发控制) |
| 一致性 Consistency | 事务前后数据满足约束、业务规则 | 是前三者的最终结果,由 AID 共同保证 |
重点说清楚一个常见误区:一致性是目的,AID 是手段。很多人把一致性单独归给某个日志,其实不对——它是原子性、持久性、隔离性加上外键、约束共同达成的状态。
一段最经典的转账事务(MySQL 8.0 可跑):
-- 转账事务:A 扣 100,B 加 100,必须同时成功或同时失败
START TRANSACTION; -- 开启事务
UPDATE account SET balance = balance - 100 WHERE id = 1; -- A 扣款
UPDATE account SET balance = balance + 100 WHERE id = 2; -- B 加款
COMMIT; -- 全部成功才提交
-- 若中途报错则执行 ROLLBACK;,两条 UPDATE 一起撤销,靠 undo log 回滚
追问
- 追问:undo log 和 redo log 哪个是逻辑日志、哪个是物理日志? redo log 是物理日志(记录"某页某偏移改成什么值"),undo log 是逻辑日志(记录反向操作,如 INSERT 的 undo 是一条 DELETE)。
- 追问:事务没提交就宕机,数据会丢吗? 不会。未提交事务的修改在 redo log 里也有记录,但崩溃恢复时会根据 undo log 把未提交事务回滚掉,最终效果等于这个事务从没执行过。
题目 3:四种隔离级别分别解决什么问题,MySQL 默认是哪个
考点
- 脏读、不可重复读、幻读三个并发问题的定义。
- 四种隔离级别:读未提交 RU、读提交 RC、可重复读 RR、串行化。
- InnoDB 默认 RR,且在 RR 下通过临键锁很大程度解决了幻读。
参考答案
先背一张对照表(面试必考,务必默写出来):
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交 READ UNCOMMITTED | 会出现 | 会出现 | 会出现 |
| 读提交 READ COMMITTED | 解决 | 会出现 | 会出现 |
| 可重复读 REPEATABLE READ | 解决 | 解决 | InnoDB 基本解决 |
| 串行化 SERIALIZABLE | 解决 | 解决 | 解决 |
三个问题先讲清楚:
- 脏读:读到了别的事务还没提交的数据,对方一回滚你就读了一堆"不存在的数据"。
- 不可重复读:同一事务内两次读同一行,结果不一样(被别人 UPDATE 了),侧重"值变了"。
- 幻读:同一事务内两次按范围查询,第二次多出来几行(被别人 INSERT 了),侧重"行数变了"。
MySQL InnoDB 的默认隔离级别是 REPEATABLE READ(RR),可以用下面语句查看和修改:
-- 查看当前全局与会话级隔离级别(MySQL 8.0 默认输出 REPEATABLE-READ)
SELECT @@GLOBAL.transaction_isolation, @@SESSION.transaction_isolation;
-- 把会话级别改成读提交(很多互联网公司线上用 RC,因为 RR 锁范围更大、并发略低)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
加分点:RC 是 Oracle、PostgreSQL 的默认级别,互联网公司常用 RC 是因为它锁更松、并发性更好,而幻读在"业务重试"面前本来就需要自己用乐观锁兜底。
可以用下面这组 SQL 在两个会话里亲手验证"不可重复读"(MySQL 8.0):
-- 会话 A:先开事务,查一次余额
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; -- RC 级别
BEGIN;
SELECT balance FROM account WHERE id = 1; -- 第一次查,比如 1000
-- 会话 B:此时把余额改成 800 并提交
UPDATE account SET balance = 800 WHERE id = 1;
COMMIT;
-- 回到会话 A:第二次查同一行,值变了 -> 不可重复读
SELECT balance FROM account WHERE id = 1; -- RC 下看到 800
COMMIT;
-- 如果把会话 A 换成 REPEATABLE READ,第二次仍看到 1000,即可重复读
追问
- 追问:RR 真的完全解决幻读了吗? 没有 100%。InnoDB 靠临键锁(Next-Key Lock)防止别的事务在我的范围里 INSERT,但"自己在本事务内先 INSERT 再 SELECT"这种场景仍可能看到幻影,彻底解决要靠串行化或业务层加唯一约束。
- 追问:怎么查看当前会话隔离级别? 用上面的
transaction_isolation变量(MySQL 8.0),5.x 时代叫tx_isolation。
题目 4:INNER JOIN LEFT JOIN 和 RIGHT JOIN 的区别
考点
- 内连接与外连接的集合语义(韦恩图)。
- LEFT JOIN 的过滤条件写在 ON 和写在 WHERE 的本质区别。
- USING、自然连接、自连接的概念。
参考答案
一句话结论:INNER JOIN 只保留两表匹配上的行;LEFT JOIN 左表全保留,右表匹配不上补 NULL;RIGHT JOIN 反之。
建两张小表演示(MySQL 8.0 可跑):
-- 学生表与成绩表,注意有的学生没成绩,有的成绩没有对应学生
CREATE TABLE t_student (stu_id INT PRIMARY KEY, name VARCHAR(20));
CREATE TABLE t_score (stu_id INT, subject VARCHAR(20), score INT);
INSERT INTO t_student VALUES (1, '张三'), (2, '李四'), (3, '王五');
INSERT INTO t_score VALUES (1, '数学', 90), (1, '语文', 85), (2, '数学', 78);
-- 王五没有成绩;成绩里没有 stu_id=3 的行(演示外连接补 NULL)
三种连接的结果差异:
-- INNER JOIN:只保留两边都匹配的行,王五不会出现
SELECT s.name, sc.subject, sc.score
FROM t_student s
INNER JOIN t_score sc ON s.stu_id = sc.stu_id;
-- LEFT JOIN:左表学生全保留,王五的科目和成绩补 NULL
SELECT s.name, sc.subject, sc.score
FROM t_student s
LEFT JOIN t_score sc ON s.stu_id = sc.stu_id;
高频坑:LEFT JOIN 后想"只要没成绩的学生",条件必须写在 ON 里对右表过滤,或写在 WHERE 里判断 NULL;如果把右表的过滤写进 WHERE,会把被补 NULL 的行直接过滤掉,等于退化成 INNER JOIN:
-- 错误写法:右表过滤写在 WHERE,左表那行 NULL 被筛掉,LEFT JOIN 失效
SELECT s.name, sc.score FROM t_student s
LEFT JOIN t_score sc ON s.stu_id = sc.stu_id
WHERE sc.subject = '数学'; -- sc.subject 为 NULL 的王五被过滤
-- 正确写法:右表的过滤条件放 ON,左表行仍保留
SELECT s.name, sc.score FROM t_student s
LEFT JOIN t_score sc ON s.stu_id = sc.stu_id AND sc.subject = '数学';
追问
- 追问:LEFT JOIN 和 LEFT OUTER JOIN 什么关系? 完全等价,OUTER 可以省略,写法习惯问题。
- 追问:多表 JOIN 优化器会按书写顺序吗? 不一定。MySQL 优化器会做基于代价的重排,小表驱动大表,但你手写时仍应让驱动表结果集尽量小。
题目 5:MyISAM 和 InnoDB 有什么区别,为什么默认用 InnoDB
考点
- 事务支持、锁粒度、外键、崩溃恢复、索引结构差异。
- MySQL 5.5 之后默认引擎从 MyISAM 切到 InnoDB 的历史背景。
参考答案
一张表讲清区别(面试常考):
| 对比项 | MyISAM | InnoDB |
|---|---|---|
| 事务支持 | 不支持 | 支持 |
| 锁粒度 | 表锁 | 行锁(默认)+ 间隙锁 |
| 外键 | 不支持 | 支持 |
| 崩溃恢复 | 几乎没有,易坏 | 有 redo log,crash-safe |
| 索引结构 | 非聚簇,数据与索引分开 | 聚簇索引,主键即数据 |
| COUNT(*) | 存了行数,很快 | 需要扫描或走二级索引 |
| 适用场景 | 只读分析、历史遗留 | 绝大多数线上 OLTP 业务 |
为什么默认 InnoDB:现代业务几乎都要事务(下单、转账),要并发写(行锁),要宕机不丢数据(崩溃恢复),这三点 MyISAM 都给不了。MyISAM 唯一的优势是 COUNT(*) 快和体积小,但在 InnoDB 里用二级索引或汇总表就能替代。
查看和指定引擎的 SQL:
-- 查看表当前用的引擎
SHOW TABLE STATUS LIKE 't_user';
-- 建表时显式指定引擎(8.0 默认就是 InnoDB,写出来更清晰)
CREATE TABLE t_order (id BIGINT PRIMARY KEY) ENGINE = InnoDB;
追问
- 追问:MyISAM 的 COUNT(*) 为什么快? 因为它在元数据里存了精确行数,直接返回;InnoDB 要 MVCC 下数到底哪些行对当前事务可见,所以得扫索引。
- 追问:现在还能用 MyISAM 吗? MySQL 8.0 里 MyISAM 还在但只建议做临时表或系统表,新业务别用。
题目 6:什么是回表,什么是覆盖索引
考点
- 二级索引叶子节点存的是主键值,不是整行。
- 回表:先查二级索引拿到主键,再回聚簇索引取整行,两次 B+Tree 查找。
- 覆盖索引:SELECT 的列全都在索引里,不用回表,Extra 显示 Using index。
参考答案
先画个流程:二级索引 idx_city(city) 的叶子节点存的是 (city 值, 主键 id)。当你执行 SELECT * FROM t_user WHERE city='北京' 时:
1. 先在 idx_city 这棵 B+Tree 上找到 city='北京' 的记录,拿到主键 id 列表;
2. 再拿着每个 id 回到聚簇索引(主键那棵树)上查整行数据。
第 2 步就是回表,一次查询两棵树,回表次数越多越慢。
覆盖索引就是把需要查询的列都塞进索引,让第 2 步整个省掉:
-- 需求:只按 city 查 name,不需要整行
-- 建一个覆盖索引:把 name 也放进索引叶子
ALTER TABLE t_user ADD INDEX idx_city_name (city, name);
-- 这条查询的所有列(city、name)都在索引里,无需回表
SELECT name FROM t_user WHERE city = '北京';
-- EXPLAIN 的 Extra 会显示 "Using index",即覆盖索引命中
怎么验证有没有回表:EXPLAIN 看 Extra 列,出现 Using index 说明覆盖索引生效;如果还是 Using where 且 key 是二级索引,多半回表了。
-- 用 EXPLAIN 实际看一次(MySQL 8.0)
EXPLAIN SELECT name FROM t_user WHERE city = '北京';
-- 若命中覆盖索引 idx_city_name,Extra 列显示 "Using index",不回表
EXPLAIN SELECT * FROM t_user WHERE city = '北京';
-- SELECT * 多取了 city/name 以外的列,覆盖索引不够用,要回表
追问
- 追问:联合索引 (a,b,c),查询 WHERE a=? 且 c=? 能覆盖吗? 能用上 a,但 c 不在最左连续前缀上,索引只能用到 a 这部分;想覆盖还得看 SELECT 的列。
- 追问:覆盖索引和最左前缀矛盾吗? 不矛盾。最左前缀管"WHERE 能不能走索引",覆盖索引管"SELECT 的列够不够不用回表",两个条件都满足才最理想。
题目 7:主键用自增 ID 还是 UUID,为什么
考点
- 聚簇索引的物理有序性。
- 自增主键带来的顺序插入、页分裂少、缓存友好。
- UUID 的无序导致页分裂、碎片、二级索引更大。
参考答案
结论:业务库主键优先用自增 BIGINT(或雪花算法),不要用 UUID 做主键。
原因要从聚簇索引说起:InnoDB 表数据本身就是按主键顺序组织的 B+Tree。
- 自增主键:新行总是追加到当前索引页的末尾,顺序写,不会破坏页的平衡,页分裂少,磁盘顺序 IO,缓冲池命中率高。而且 BIGINT 只占 8 字节,每个二级索引叶子都存一份主键,越小越省空间。
- UUID 主键:UUID 是随机的,新行插入位置不可预测,可能插到树中间,频繁触发页分裂(page split),产生磁盘碎片,写性能差;而且 UUID 占 16~36 字节,每个二级索引都跟着变大。
对比示例:
-- 推荐:自增 BIGINT 主键,顺序插入,聚簇索引友好
CREATE TABLE t_goods (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
goods_no VARCHAR(64) NOT NULL,
UNIQUE KEY uk_goods_no (goods_no) -- 业务唯一号放唯一索引,别当主键
) ENGINE = InnoDB;
-- 不推荐:UUID 当主键,随机插入导致页分裂和碎片
CREATE TABLE t_goods_bad (
id CHAR(36) NOT NULL PRIMARY KEY, -- 占空间且无序
goods_no VARCHAR(64) NOT NULL
) ENGINE = InnoDB;
互联网公司的折中:分库分表场景下单机自增会冲突,常用雪花算法 Snowflake 生成趋势递增的 64 位 ID——既全局唯一,又大体有序,兼顾了两者优点。
追问
- 追问:UUID 真的一无是处吗? 不是。不想暴露业务量(用户数、订单数)时可以用 UUID 对外,但内部主键仍建议自增或雪花。
- 追问:联合主键有什么不好? 联合主键更大,且每个二级索引都要存这一组列,体积膨胀明显,还可能影响最左前缀。
题目 8:NULL 值对索引和查询有什么影响
考点
- InnoDB 的 B+Tree 索引能否存 NULL。
- COUNT、比较运算、IS NULL 在三值逻辑下的坑。
- 工程上为什么推荐 NOT NULL + 默认值。
参考答案
一句话结论:InnoDB 的 B+Tree 索引能存 NULL,但 NULL 会让比较和聚合变得绕,工程上建议关键列尽量 NOT NULL 加默认值。
几个具体表现:
- COUNT(col) 会忽略 NULL:
COUNT(列名)自动跳过该列为 NULL 的行,只有COUNT(*)才统计全部行。这是高频面试坑。 - 三值逻辑:
NULL = NULL、NULL <> 1都返回 NULL(既不是 true 也不是 false),所以查空值必须用IS NULL,写= NULL永远查不到数据。 - 索引可存 NULL 但排序特殊:B+Tree 把 NULL 当成最小值排在最前面;联合索引里某列允许 NULL 时,最左前缀的利用也会打折扣。
-- 错误写法:= NULL 永远查不到任何行(结果是 NULL,不是 true)
SELECT * FROM t_user WHERE phone = NULL;
-- 正确写法:用 IS NULL / IS NOT NULL
SELECT * FROM t_user WHERE phone IS NULL;
-- COUNT 的坑:phone 为 NULL 的行不会被 COUNT(phone) 统计进去
SELECT COUNT(phone) FROM t_user; -- 忽略 phone 为 NULL 的行
SELECT COUNT(*) FROM t_user; -- 统计所有行,不管列是不是 NULL
建表时的工程建议:
-- 推荐:关键列 NOT NULL 并给默认值,避免三值逻辑坑
CREATE TABLE t_user (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL DEFAULT '', -- 空串而非 NULL
age INT NOT NULL DEFAULT 0, -- 0 而非 NULL
phone VARCHAR(20) NOT NULL DEFAULT ''
) ENGINE = InnoDB;
追问
- 追问:InnoDB 索引里 NULL 排在最前还是最后? 在 B+Tree 中 NULL 被当作最小值,排在索引最左边。
- 追问:NULL 和空字符串 '' 一样吗? 不一样。NULL 表示"未知/不存在",'' 是一个确定的空串,两者占用空间和比较语义都不同,不能混用。
速查表:基础高频结论速查
| 考点 | 一句话结论 |
|---|---|
| 索引结构 | InnoDB 默认 B+Tree,矮胖、叶子链表、支持范围查询 |
| ACID | 原子性靠 undo,持久性靠 redo,隔离性靠锁+MVCC,一致性是结果 |
| 默认隔离级别 | InnoDB 默认 RR(可重复读),互联网常下调为 RC |
| 左连接坑 | 右表过滤写 WHERE 会让 LEFT JOIN 退化成 INNER JOIN |
| 默认存储引擎 | 5.5 起默认 InnoDB,支持事务、行锁、崩溃恢复 |
| 回表 | 二级索引拿到主键后再回聚簇索引查整行 |
| 覆盖索引 | 查询列全在索引里,Extra 显示 Using index |
| 主键选型 | 自增 BIGINT 最优;分库分表用雪花算法 |
注意事项:面试答题的常见丢分点
1. 把"不可重复读"和"幻读"说反:不可重复读是同一行的值被改了(UPDATE),幻读是范围查询的行数变了(INSERT/DELETE)。答题前先在纸上写"值变 vs 行数变"。
2. 说 InnoDB 完全解决幻读:不准确。要说"RR 级别下通过临键锁很大程度解决幻读,但并非 100%,彻底解决要串行化"。
3. 认为索引越多越好:一定要补一句"索引拖慢写入、占用空间,单表建议 5 个以内"。
4. 回表和覆盖索引分不清:回表是"要回主键树再查一次",覆盖索引是"索引里啥都有,不用回去"。
5. MyISAM 和 InnoDB 对比漏了崩溃恢复:只说"MyISAM 表锁、InnoDB 行锁"不够,要补上 redo log 带来的 crash-safe。
6. COUNT(*) 和 COUNT(列) 混为一谈:COUNT(*) 统计所有行,COUNT(列) 自动忽略该列为 NULL 的行,面试常拿这个挖坑。
7. 把主键和聚簇索引说成两个东西:InnoDB 里主键索引就是聚簇索引,一张表只有一个;别答成"主键索引和聚簇索引是两种索引"。
练习题:动手自测与参考
练习 1:给定表 t_order(id, user_id, status, create_time),写一条 SQL 查询"用户 1001 的所有已支付订单,按时间倒序",并说出应该建什么联合索引。
<details>
<summary>参考答案</summary>
-- 联合索引把等值列放前、排序列放后:(user_id, status, create_time)
SELECT id, user_id, status, create_time
FROM t_order
WHERE user_id = 1001 AND status = 'PAID'
ORDER BY create_time DESC;
-- 建索引
ALTER TABLE t_order ADD INDEX idx_uid_status_time (user_id, status, create_time);
</details>
练习 2:为什么 SELECT * FROM t_user WHERE name = '张三' 在 name 有普通索引时,优化器有时仍走全表扫描?
<details>
<summary>参考答案</summary>
当 name='张三' 命中的行占比很高(比如超过 20%~30%),优化器认为走二级索引再回表的代价比直接全表扫描还大,于是放弃索引。这说明索引不是"建了就一定用",优化器基于代价(cost)决策。
</details>
练习 3:用一句话解释脏读、不可重复读、幻读。
<details>
<summary>参考答案</summary>
脏读:读到别人未提交的数据;不可重复读:同一事务内同一行两次读值不同(被 UPDATE);幻读:同一事务内同一范围两次查行数不同(被 INSERT)。
</details>
练习 4:有表 t_emp(id, dept_id, name, salary),查询 WHERE dept_id = 5 ORDER BY salary DESC LIMIT 10,怎么建索引最省?
<details>
<summary>参考答案</summary>
建联合索引 (dept_id, salary):等值列 dept_id 在前排好序,salary 跟着排好序,ORDER BY 直接借索引顺序消除 filesort,还能覆盖 name 以外的需求。如果 SELECT 只要 id/dept/salary,可加 (dept_id, salary, id) 形成覆盖索引。
</details>
练习 5:为什么 WHERE name = 123(name 是 varchar)走不上 name 上的索引?
<details>
<summary>参考答案</summary>
发生了隐式类型转换:字符串列和数字比较时,MySQL 会把列转成数字再比,相当于 WHERE CAST(name AS SIGNED) = 123,索引列被函数包裹,索引失效。正确写法是 WHERE name = '123'。
</details>
复习清单
| 题目 | 核心结论 | 易错点 |
|---|---|---|
| 1 索引为什么加速 | B+Tree 把 O(n) 扫描降到 O(log n),矮胖+叶子链表 | 以为索引建了就一定走,忽略优化器代价决策 |
| 2 ACID | 原子 undo、持久 redo、隔离锁+MVCC,一致性是结果 | 把一致性单独归给某一个日志 |
| 3 隔离级别 | 默认 RR,RU/RC/RR/串行化逐级解决脏读/不可重复读/幻读 | 说 InnoDB 100% 解决幻读 |
| 4 JOIN 区别 | INNER 取交集,LEFT 保左表,右表过滤写 WHERE 会退化 | 右表条件错放 WHERE 导致 LEFT JOIN 失效 |
| 5 MyISAM vs InnoDB | InnoDB 事务+行锁+崩溃恢复,MyISAM 仅 COUNT(*) 快 | 漏说 redo log crash-safe |
| 6 回表与覆盖索引 | 二级索引存主键,覆盖索引免回表(Using index) | 混淆回表与覆盖索引的触发条件 |
| 7 主键选型 | 自增 BIGINT 顺序写优,UUID 随机导致页分裂 | 认为 UUID 全局唯一就该当主键 |
| 8 NULL 影响 | 索引能存 NULL,但 =NULL 查不到,COUNT(列) 忽略 NULL | 写 WHERE phone = NULL 当条件 |
下一篇预告:第 30 章 MySQL面试题精讲:进阶
文章回复
0 条公开回复