简介:面向准备MySQL后端开发岗位面试的求职者,这份文档以问答形式系统梳理了数据库面试中的高频考点。内容涵盖关系型与非关系型数据库区别、一条SQL语句的完整执行流程、索引底层数据结构(哈希表/有序数组/B+树)、主键索引与非主键索引、InnoDB与MyISAM的B+树实现差异、为什么选择B+树、普通索引与唯一索引的选择、覆盖索引与索引下推、索引失效场景,并延伸至change buffer、redo log与binlog等日志机制,知识点组织清晰,便于考前快速回顾与查漏补缺。资料共1个docx文件,压缩包大小约40KB,轻量易用,适合碎片化时间学习。目前已有251人学习下载,对于正在系统梳理MySQL知识体系的开发者来说,是一份高性价比的冲刺笔记。
1. MySQL面试题:为什么背了答案还是过不了面试
面试官抛出一条慢 SQL,问“你怎么加索引”。你背过“最左前缀”,却答不出为什么这条 SQL 走了全表扫描。这不是你记性差,而是你把 MySQL 面试题当成了填空题:分不清 B+ 树、回表、覆盖索引之间的因果关系,更没拿真实数据验证过。这个标题的本质不是题库清单,而是教你用工程视角把索引、事务、锁、主从、分库分表串成一条线,每一类题都能落到“数据怎么存、执行计划怎么走、并发怎么卡”。适合准备跳槽的开发者、带团队出题的面试官,以及刚入门想系统梳理 MySQL 底层的人。
2. 索引与执行计划:面试官怎么用一条慢 SQL 考你
MySQL 面试题里,索引相关占比最高。原因很简单:索引直接决定查询性能,而且牵涉 B+ 树、执行计划、优化器、数据分布,能快速看出候选人是不是只会背概念。这一章我们不背八股,直接用一张订单表把索引讲透。
2.1 聚簇索引与二级索引:先答清楚数据存在哪
面试聊到索引,别只丢出“B+ 树”三个字。面试官真正想听的是 InnoDB 的聚簇索引和二级索引怎么分工。聚簇索引的叶子节点上挂着完整行数据,主键就是它的搜索键;二级索引的叶子节点只保存索引列和主键值。所以当你用二级索引查数,正常流程是先拿主键,再回到聚簇索引取整行,这个动作就叫回表。如果查询需要的字段恰好都在二级索引里,比如SELECT order_no FROM t WHERE user_id = 1,而(user_id, order_no)是联合索引,MySQL 发现不需要回表就能拿到结果,就叫覆盖索引。
| 索引类型 | 叶子节点内容 | 查询路线 |
|---|---|---|
| 聚簇索引 | 整行数据 | 主键直接定位 |
| 二级索引 | 索引列 + 主键值 | 索引检索 → 回表 → 聚簇索引取行 |
面试加分点是说清“为什么用 B+ 树”。哈希索引适合等值查询但不支持范围扫描;跳表能做范围查询,但 B+ 树页和磁盘 IO 配合更稳,页内顺序读、叶子节点链表串行,能减少随机 IO。当然,这个对比在 Redis 等内存场景里跳表也有优势,面试题考的是 MySQL 的磁盘场景。
注意:不要只说“B+ 树快”,要说清楚“对于范围查询和排序,B+ 树比哈希更适合磁盘场景”。很多候选人死在这一句上。
2.2 用 EXPLAIN 拆解一条慢 SQL:现场分析的固定套路
面试官如果要手写 SQL 优化,通常不会给表数据,但你作为候选人要主动问清楚:字段类型、数据量、WHERE 条件分布。我一般会先把表结构和查询场景建立起来,再分析。下面给一套最小可复现的练习。
先建一张订单表:
CREATE TABLE `user_orders` ( `id` bigint NOT NULL AUTO_INCREMENT, `user_id` int NOT NULL, `order_no` varchar(32) NOT NULL, `status` tinyint NOT NULL DEFAULT '0', `created_at` datetime NOT NULL, PRIMARY KEY (`id`), KEY `idx_user_created` (`user_id`, `created_at`) ) ENGINE=InnoDB;这里的KEY idx_user_created是联合索引,顺序是user_id、created_at。原因是等值条件要放在索引前面,范围条件放在后面,才能最大化利用索引;AUTO_INCREMENT主键保证聚簇索引插入基本有序,减少页分裂。
接下来造 10 万行数据。MySQL 8.0 可以用递归 CTE 生成序列,比老版本的变量写法更不容易踩求值顺序的坑:
-- 先调大递归上限,否则默认 1000 就停 SET SESSION cte_max_recursion_depth = 200000; INSERT INTO user_orders (user_id, order_no, status, created_at) WITH RECURSIVE seq AS ( SELECT 1 AS n UNION ALL SELECT n + 1 FROM seq WHERE n < 100000 ) SELECT FLOOR(RAND() * 5000), CONCAT('NO', LPAD(n, 10, '0')), FLOOR(RAND() * 4), DATE_ADD('2024-01-01', INTERVAL FLOOR(RAND() * 365) DAY) FROM seq;逻辑说明:递归 CTE 生成 1 到 100000 的序列,外层 SELECT 用RAND()让user_id随机分布到 5000 个用户,日期随机分布在一年内,这样 EXPLAIN 结果才接近真实线上数据分布。cte_max_recursion_depth是 MySQL 8.0.16 之后才有的参数,老版本请改用存储过程循环。
然后跑 EXPLAIN:
mysql> EXPLAIN SELECT order_no FROM user_orders WHERE user_id = 123 AND created_at > '2024-06-01'\G\G是让结果竖排显示,字段太多时更好核对。重点看四列:type、key、rows、Extra。type常见值从好到坏:system、const、eq_ref、ref、range、index、ALL。面试题里看到 range 和 ref,基本说明索引用上了;看到 ALL,就要怀疑索引没走成。
如果结果是type=range、key=idx_user_created、rows几百行、Extra=Using index condition,说明走的是索引条件下推(ICP):MySQL 把created_at的过滤条件下推到索引层,减少回表行数。这是一个小而准的面试得分点。
再补一个常见陷阱:如果把查询改成WHERE DATE(created_at) > '2024-06-01' AND user_id = 123,DATE()函数作用于索引列,MySQL 无法直接按 B+ 树顺序比较,索引就会失效。很多面试题就卡在这一层。
2.3 索引失效的四个高频场景:结合执行计划才答得全
面试题里“索引为什么失效”比“索引怎么建”更难。常见场景有四个。
第一个是隐式类型转换:WHERE phone = 18812345678。如果phone是 varchar,MySQL 会把数字转成字符串再比较,等于给索引列套了转换函数,索引失效。反过来,int 列和字符串比较也一样。
第二个是函数包裹索引列:WHERE DATE(created_at) = '2024-06-01'。即便created_at有索引,函数也会阻断 B+ 树的范围查找。正确写法是created_at >= '2024-06-01' AND created_at < '2024-06-02'。
第三个是like通配符在前:WHERE order_no LIKE '%123'。前导模糊无法走索引,因为 B+ 树是按前缀排序的,没法从中间开始搜索。但'123%'可以走索引。
第四个最有误导性:优化器判断全表扫描更优。当数据量很小,或者回表代价太高时,优化器可能放弃索引,走 ALL。这不是索引失效,是代价估算结果。出现这种场景,要看表统计信息和数据分布,而不是盲目加索引。
面试题里最常被问到的 EXPLAIN 参数就是type、key、rows、Extra,回答时用一条真实 SQL 的EXPLAIN结果带出来,比背定义有说服力。面试官问“你有没有排查过慢 SQL”时,你顺手画一遍这个流程,就能看出有没有实际经验。
3. 事务与锁:并发问题的三个层次,面试官在追问什么
索引回答的是“查询能不能快”,事务和锁回答的是“并发对不对”。MySQL 面试题里只要聊到并发扣减库存、重复下单,多半会落到隔离级别、MVCC、行锁。这一章按三个层次拆开。
3.1 隔离级别与快照读:别只背四个名字
事务四大隔离级别的名字背起来很容易,但要讲清楚“InnoDB 怎么实现”才是关键。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交 | 可能 | 可能 | 可能 |
| 读已提交 | 不会 | 可能 | 可能 |
| 可重复读(InnoDB) | 不会 | 不会 | 快照读不会,当前读大多避免 |
| 串行化 | 不会 | 不会 | 不会 |
InnoDB 默认是可重复读,重点在于它不靠锁实现快照,而是靠 MVCC。每一行记录上有一个trx_id,修改时生成新版本,旧版本通过 undo log 串成版本链。普通 SELECT 是快照读,读的时候用 ReadView 判断哪个版本可见。判断条件大致是:trx_id小于 ReadView 里最小活跃事务 ID 的可见;在活跃事务列表里的不可见;大于最大事务 ID 的不可见。
面试官追一句“读已提交和可重复读的快照有什么区别”,答案是 ReadView 的生成时机不同。读已提交每次 SELECT 都生成新的 ReadView,所以同一事务两次 SELECT 可能看到不同版本;可重复读只在事务第一次 SELECT 时生成 ReadView,之后复用。这就是“可重复读”这层语义的实现。很多候选人背得出隔离级别,却卡在这里。
3.2 MVCC 与当前读:为什么可重复读还有幻读
上面说的快照读只是普通 SELECT。InnoDB 还有当前读,包括SELECT ... FOR UPDATE、UPDATE、DELETE。当前读读的是最新版本,不能用旧的 ReadView。
幻读的经典场景:事务 A 先快照读查不到user_id = 99的行,事务 B 插入一行user_id = 99并提交,A 再执行当前读SELECT * FROM t WHERE user_id = 99 FOR UPDATE,就可能读到 B 插入的行。InnoDB 用 next-key lock(记录锁 + 间隙锁)防止 B 插入,但前提是 B 要往这个索引区间里插。如果查询条件没有匹配到任何索引行,就会锁一个较大的间隙;如果表没有索引,甚至要锁全表。
所以说“可重复读没有幻读”是错的。准确说法是:InnoDB 的 RR 下,快照读用 MVCC 避免了幻读,当前读靠 next-key lock 在大多数场景避免了幻读,但严格来说不绝对。面试官听到这个回答,通常眼睛会亮。
3.3 锁的类型与死锁:面试现场怎么拆一条加锁语句
行锁里,记录锁锁单行,间隙锁锁开区间,next-key lock 是记录锁 + 间隙锁的组合。表锁有意向锁,主要用来协调行锁和表锁的兼容性。死锁是面试高频题,因为它能考察对加锁顺序和索引范围的理解。
下面是一个典型的死锁场景:
-- 事务A START TRANSACTION; UPDATE orders SET status = 2 WHERE user_id = 100; UPDATE orders SET status = 3 WHERE user_id = 200; COMMIT;-- 事务B START TRANSACTION; UPDATE orders SET status = 3 WHERE user_id = 200; UPDATE orders SET status = 2 WHERE user_id = 100; COMMIT;如果两个事务并发执行,A 先锁user_id = 100的行,B 先锁user_id = 200的行,然后 A 等 B 释放 200,B 等 A 释放 100,就死锁了。这里的前提是user_id上有索引,如果没索引,InnoDB 会锁全表,死锁概率反而低一些,但并发度也废了。
排查死锁用这条命令:
SHOW ENGINE INNODB STATUS\G重点关注LATEST DETECTED DEADLOCK段,里面会记录冲突的两条 SQL、持有和等待的锁。线上如果死锁频繁,可以打开参数innodb_print_all_deadlocks = ON,把每次死锁都写进错误日志,便于事后分析。面试时主动提这个参数,比只说“看日志”更有说服力。
解决方案有三条路径,按优先级讲:第一,所有事务对多个资源的加锁顺序保持一致;第二,缩小锁范围,尽量先锁更少的行;第三,评估是否能用读已提交隔离级别,减少间隙锁的持有时间。注意,加锁顺序一致是最低成本方案。
4. 场景题:主从延迟、分库分表、慢 SQL,怎么答才有工程味
准备跳槽时,我发现纯原理题大家都能背,但一旦问“线上主从延迟怎么办”“十万订单表怎么拆”,很多人就语塞。这章把三个高频场景按“判断现象 → 定位原因 → 给方案”的顺序写。
4.1 主从延迟:先查监控,再配参数,最后聊架构
面试问主从延迟,很多人的答案是“换半同步复制”或“开启并行复制”,这是方案,不是排查流程。工程味要先回答怎么判断延迟:
SHOW SLAVE STATUS\G重点看四个字段:Slave_IO_Running、Slave_SQL_Running、Seconds_Behind_Master、Read_Master_Log_Pos。前两个是 Yes 表示 IO 线程和 SQL 线程都在跑;Seconds_Behind_Master是延迟秒数;Read_Master_Log_Pos表示从库读到主库 binlog 的位置。
IO 线程负责拉取 binlog,SQL 线程负责重放。延迟的根源通常在 SQL 线程,因为重放往往比拉取慢。常见原因按概率排:大事务长时间跑、从库机器性能差、binlog 格式是 STATEMENT 导致重放时每次都要重新计算、主库写入并发太高。
解决方案要按顺序讲:先看是不是大事务,再看能不能升级从库硬件,然后才是调整参数。并行复制是常见做法,但注意slave_parallel_workers开启的前提是 binlog 格式为 ROW,否则并行度上不去。面试里主动提这个前提条件,能明显和背答案的人拉开差距。
4.2 分库分表:拆键怎么选、数据怎么迁
分库分表问多了,会发现面试官不是要你背中间件,而是考察你有没有做过容量规划。拆键选错的典型例子:用单表主键取模分 4 个库,数据涨到要扩成 8 个库时,大部分行都要重新映射,迁移成本极高。
拆键选择的第一原则:尽量让业务主查询条件带上拆键。比如用户中心的主查询一定带user_id,那就用user_id做拆键;订单中心的主查询可能带order_no,就要权衡是否用order_no做拆键,或者引入映射表。如果多个高频查询条件不是同一个键,优先选业务上最核心的维度,其他维度靠冗余或查询路由解决。
数据迁移顺序是另一个高频考点。我一般会按这个顺序答:全量导出 → 增量追 binlog → 数据校验 → 切读流量 → 切写流量。全量阶段的最简命令是这样:
# 导出全量,where 条件控制分批 mysqldump -h源库 -uroot -p dbname orders --where="id < 5000000" > orders_full.sql # 导入目标库 mysql -h新库 -uroot -p dbname < orders_full.sql参数说明:--where用于分批导出,避免一次性锁太多行;说明里没加--single-transaction是因为 InnoDB 下做一致性快照也需要额外确认参数,生产环境更常用物理备份工具,这里只讲思路。真正生产环境通常先用物理备份做全量,再用 binlog 补增量,最后跑对账任务校验行数和关键字段。切读流量时要先灰度,切写流量要在低峰期做,并且要有回滚预案。
4.3 慢 SQL 排查:一条命令定位问题的完整流程
慢 SQL 排查是面试题里最容易讲“虚”的部分。很多人说“我开慢日志”,但一说参数就含糊。下面这套是可以用本机验证的流程:
-- 当前会话生效;生产环境建议改 my.cnf 再做重启 SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 2; SET GLOBAL log_queries_not_using_indexes = ON;参数说明:long_query_time = 2表示超过 2 秒的 SQL 记入慢日志;log_queries_not_using_indexes = ON会把没走索引的 SQL 也记进去,方便发现隐患。注意这些是全局变量,线上不能用 SET GLOBAL 直接改,要落到配置文件里。
拿到慢日志后,看三条关键信息:Query_time是总耗时,Rows_examined是扫描行数,Rows_sent是返回行数。扫描 1000 行返回 1 行,典型的“扫描量过大”,多半是索引缺失或索引失效。接下来就用第 2 章的EXPLAIN分析执行计划。
面试回答时,不要只讲命令,要讲一个完整故事:“某天线上订单查询变慢,慢日志显示扫描 20 万行、返回 50 行,EXPLAIN 看到 type=ALL,索引列被函数包了,改成范围条件后从 3 秒降到 20 毫秒。”这种叙述比背十个参数有用得多。
5. 面试回答避坑清单:这五个错我当年都犯过
这一章是我自己准备面试时真实踩过的坑,也是我做面试官时最常听到的错误。每条按“现象 → 原因 → 解决”写,方便你自查。
5.1 把回表说成“二次查询”
现象:面试官问“二级索引查数据为什么比主键慢”,答“要先查索引,再回表二次查询”。
原因:没有说清二级索引叶子节点只存主键,回表是拿主键再找一次聚簇索引,不是模糊的“二次查询”。
解决:回答时先讲结构:二级索引定位到主键 → 聚簇索引按主键取整行 → 如果二级索引已经覆盖查询字段,就不用回表。可以补一个例子:SELECT order_no FROM t WHERE user_id = 1,如果联合索引包含order_no,Extra 会出现Using index,说明走了覆盖索引,这才是完整的回答。
5.2 说“可重复读下没有幻读”
现象:斩钉截铁说“可重复读隔离级别不会幻读”。
原因:把快照读和当前读混为一谈,只背了结论,没理解 MVCC 和锁的边界。
解决:先说普通 SELECT 走 MVCC 快照读,所以看不到其他事务提交的新行;再说当前读(FOR UPDATE / UPDATE)走最新版本,InnoDB 用 next-key lock 解决大多数场景,但仍有间隙锁覆盖不到的情况。结论是“RR 在 InnoDB 下基本避免幻读,但不绝对”。这个说法才经得起追问。
5.3 把最左前缀当联合索引的全部规则
现象:面对(a, b, c)联合索引,看到WHERE b = 1 AND c = 2就说走索引。
原因:只背了“最左前缀”,不知道 B+ 树先按a排序,b和c只能在a确定时才有序。
解决:先讲排序逻辑,再举“可走”和“不可走”的 SQL。(a,b,c)索引能走a、a+b、a+b+c;不能走b单独、c单独、b+c。最后补一句 MySQL 8.0 的 Index Skip Scan:某些场景下优化器可以跳过前导列,但依赖表统计信息和代价估算,不是所有情况都能绕过最左前缀。这一个补充就能碾压多数候选人。
5.4 分库分表只谈分片不谈扩容
现象:聊到分库分表,直接答“用 user_id 取模,分成 16 个库”。
原因:没有考虑数据增长后的扩容,取模分片在扩节点时几乎要重分布全部数据。
解决:先确认瓶颈是读还是写,是单表数据量还是连接数;拆键要靠近主查询;扩容方案用一致性哈希或 range + hash 混合,同时考虑双写和迁移校验。面试里更重要的是说出代价权衡:取模路由简单但扩容难,一致性哈希迁移少但路由复杂度高。
5.5 说“CHAR 比 VARCHAR 快”被追问存储机制
现象:被问“CHAR 和 VARCHAR 怎么选”,答“CHAR 定长,更快”。
原因:把旧文章里 MyISAM 的经验搬到了 InnoDB,没有区分存储引擎和行格式。
解决:在 InnoDB 中,COMPACT 和 DYNAMIC 行格式下,VARCHAR(255) 以内用额外 1 字节记录长度,查询性能差异远小于索引设计的差异。选型主要看是否需要保留尾部空格、字段长度波动大小。面试里可以说“我更关注索引列长度和字符集”,把话题引到实际设计上。
6. 用自问自答加 EXPLAIN 验证:把复习变成一次压测
最后一章分享一个我一直在用的准备方法:不要只看面经,而是把每道 MySQL 面试题变成自己能跑的实验。
具体做法是:建一张表,造一批数据,然后列 10 个“自问自答”的问题。比如“同一个查询,把联合索引顺序反过来,执行计划会变吗”“数据量从 10 万涨到 1000 万,优化器会不会换选择”“加上覆盖索引后,Extra 会不会出现 Using index”。这些问题只用看书是得不到答案的,但用 EXPLAIN 一跑,立刻见分晓。
MySQL 8.0 还提供了EXPLAIN ANALYZE,比普通 EXPLAIN 更进一步,能输出实际执行时间:
EXPLAIN ANALYZE SELECT order_no FROM user_orders WHERE user_id = 123 AND created_at > '2024-06-01';输出里会有actual time和actual rows,和预估的rows对比就能发现统计信息偏差。面试现场能背出这个命令的人不多,但一旦用出来,意思就是“我真的在自己的机器上调过执行计划”,说服力远超过“我读过某篇文章”。
我的自问自答清单一般保持五个方向:这条 SQL 走没走索引;没走是函数、类型转换还是代价估算;把条件位置换一下执行计划变不变;两个事务同时更新同一行谁会先锁;主从延迟时先看哪条命令。每道题都要求自己能白板画出执行计划,再落到实际命令验证。
我当年准备某公司的面试时,把自己整理的 20 道高频题全部在本机 MySQL 里跑了一遍,结果发现至少有 5 道题的“标准答案”在真实执行计划里站不住脚。从那以后,我坚持把面试题当实验做:先给结论,再跑 EXPLAIN,最后说数据分布。面试官看到你能自己验证结论,通常不会只考记忆。
这套方法的另一个好处是,即使面试被刷,你留下的笔记也是一个真正属于自己的排障手册。下一次遇到线上慢 SQL,你翻的不是别人的面经,而是自己跑过的真实案例。希望帮到你。
本文还有配套的精品资源,点击获取