MySQL面试题实战:从索引原理到慢SQL优化全解析

发布时间:2026/10/14 17:52:08
MySQL面试题实战:从索引原理到慢SQL优化全解析
简介面向准备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) ) ENGINEInnoDB;这里的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 之后才有的参数老版本请改用存储过程循环。然后跑 EXPLAINmysql 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就要怀疑索引没走成。如果结果是typerange、keyidx_user_created、rows几百行、ExtraUsing index condition说明走的是索引条件下推ICPMySQL 把created_at的过滤条件下推到索引层减少回表行数。这是一个小而准的面试得分点。再补一个常见陷阱如果把查询改成WHERE DATE(created_at) 2024-06-01 AND user_id 123DATE()函数作用于索引列MySQL 无法直接按 B 树顺序比较索引就会失效。很多面试题就卡在这一层。2.3 索引失效的四个高频场景结合执行计划才答得全面试题里“索引为什么失效”比“索引怎么建”更难。常见场景有四个。第一个是隐式类型转换WHERE phone 18812345678。如果phone是 varcharMySQL 会把数字转成字符串再比较等于给索引列套了转换函数索引失效。反过来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 释放 200B 等 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 线程负责拉取 binlogSQL 线程负责重放。延迟的根源通常在 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 --whereid 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 看到 typeALL索引列被函数包了改成范围条件后从 3 秒降到 20 毫秒。”这种叙述比背十个参数有用得多。5. 面试回答避坑清单这五个错我当年都犯过这一章是我自己准备面试时真实踩过的坑也是我做面试官时最常听到的错误。每条按“现象 → 原因 → 解决”写方便你自查。5.1 把回表说成“二次查询”现象面试官问“二级索引查数据为什么比主键慢”答“要先查索引再回表二次查询”。原因没有说清二级索引叶子节点只存主键回表是拿主键再找一次聚簇索引不是模糊的“二次查询”。解决回答时先讲结构二级索引定位到主键 → 聚簇索引按主键取整行 → 如果二级索引已经覆盖查询字段就不用回表。可以补一个例子SELECT order_no FROM t WHERE user_id 1如果联合索引包含order_noExtra 会出现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、ab、abc不能走b单独、c单独、bc。最后补一句 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你翻的不是别人的面经而是自己跑过的真实案例。希望帮到你。本文还有配套的精品资源点击获取