每年一到“金三银四”或“金九银十”,MySQL 面试题都是后端开发绕不过去的一道坎。我在带项目、做技术分享时也发现一个规律:很多候选人能背出create index的语法,也能答出“B+树”三个字母,但一旦问到“为什么回表是性能瓶颈”“联合索引为什么要遵守最左前缀原则”“MySQL 8.0 里索引下推到底做了什么”,就开始含糊其辞。本文会把 MySQL 面试里高频出现的问题整理成一套完整体系,重点拆解数据库索引调优背后的原理,再配合 EXPLAIN 实战、事务日志原理和大量避坑建议,帮助你把碎片化的面试题理解成一条清晰的技术主线。
这篇文章适合两类读者:一类是准备后端、Java 开发、测试开发岗位面试的同学,另一类是已经在项目里写过 SQL、但总觉得性能调优无从下手的开发者。学完之后,你不仅能回答“索引为什么能加速查询”,还能在真实业务里用EXPLAIN分析慢 SQL,知道哪些索引设计是无效的,哪些写法规避了索引失效,甚至能应对面试官连环追问事务、锁、MVCC 等底层问题。
1. 为什么 MySQL 面试题总爱问索引调优
先看一个很典型的场景。线上某张订单表数据量到了千万级,业务反馈某个列表接口越来越慢。DBA 分析慢查询日志,发现一条 SQL 执行了 3 秒,走了全表扫描,扫描行数接近千万:
SELECT order_id, user_id, status FROM t_order WHERE user_id = 10086 ORDER BY create_time DESC LIMIT 20;这个案例基本还原了 MySQL 面试题最常出现的“剧情”:表数据量大、SQL 执行慢、索引设计不合理。面试官通过这些场景,考察的并不是你是否见过这张表,而是以下三件事:
- 你是否理解索引的底层数据结构,能否解释 B+ 树为什么适合磁盘存储。
- 你是否知道 InnoDB 的聚簇索引和二级索引如何协作,能否判断一条 SQL 是否需要“回表”。
- 你是否会使用
EXPLAIN分析执行计划,并针对索引失效场景给出优化方案。
所以,索引调优不只是面试考点,它直接决定了真实业务系统的稳定性。MySQL 官方文档和大量实践都提到,一个合适的索引可以让查询从全表扫描变成树搜索,性能往往能提升几个数量级。反过来,索引建多了会拖慢写入,建错了会让优化器选错执行计划。索引调优的目标是在查询速度和写入性能之间找到平衡点。
2. 一条 SQL 的执行流程:先看索引为什么生效
想要理解索引,必须先理解一条 SQL 在 MySQL 内部是怎么走的。面试高频题第一问通常就是“一条查询语句在 MySQL 中是如何执行的”,很多人一上来就背“连接器、分析器、优化器、执行器”,但没把这条链路和索引关联起来。
简化后的流程如下:
- 客户端通过 MySQL 协议与服务器建立连接,这一步由连接器负责,包括认证和权限检查。
- 查询缓存。MySQL 8.0 之前有查询缓存,但命中率低且并发场景下弊大于利,8.0 之后已移除,所以现在面试提到查询缓存,直接说“8.0 已废弃”即可。
- 分析器对 SQL 做词法分析和语法分析,检查 SQL 语法是否正确。
- 优化器决定执行计划,其中一个核心工作就是选择走哪个索引。也就是说,索引是否生效,并不是 SQL 写完就决定的,而是优化器根据统计信息和成本模型决定的。
- 执行器调用存储引擎接口,读取数据并返回结果。
这里有个关键点:很多开发者在索引不生效时第一反应是“SQL 写错了”,但真正的原因往往是优化器判断走索引成本更高,或者统计信息不准确。这在面试中是个很好的加分回答:优化器不一定总会选择开发者认为“最优”的索引。
另一个面试常问点是“MyISAM 和 InnoDB 的区别”。从索引模型角度,最关键的话题是 InnoDB 支持事务、行级锁、崩溃恢复,而 MyISAM 不支持事务且表级锁为主。现在的业务开发基本都是 InnoDB,所以后面文章内容默认围绕 InnoDB 展开。
3. 存储引擎与索引模型:聚簇索引、二级索引和回表
3.1 聚簇索引(Clustered Index)
InnoDB 的数据文件本身按主键索引结构存储,这张表里的数据行实际上是放在主键索引的叶子节点上的。主键索引就是聚簇索引。
- 如果表定义了主键,主键索引就是聚簇索引。
- 如果没有定义主键,InnoDB 会选择第一个非空的唯一索引作为聚簇索引。
- 如果连唯一索引都没有,InnoDB 会隐式生成一个
row_id作为聚簇索引。
叶子节点直接存储整行数据,所以通过主键查询的 SQL,比如WHERE id = 10,只需要一次 B+ 树搜索就能拿到完整行数据,效率高。这也是为什么 InnoDB 表强烈建议显式定义主键,而且主键尽量使用自增整数或趋势递增的值,因为随机主键会导致页分裂,增加磁盘碎片。
3.2 二级索引(Secondary Index)
除了聚簇索引,其他索引都叫二级索引,也叫辅助索引、非聚簇索引。二级索引的叶子节点存储的是索引列的值和主键值。这里需要特别记忆:二级索引并不直接存储数据行地址,而是存储主键值。
面试追问通常是这样:
问:二级索引叶子节点里存的是什么? 答:索引列值 + 主键值。
问:那SELECT * FROM t WHERE user_id = 10086且user_id上有普通索引时,查询过程是怎样的? 答:先走二级索引找到user_id = 10086对应的主键值,再根据主键值回到聚簇索引里查整行数据,这个过程叫回表。
问:回表一定发生吗? 答:不一定。如果查询需要的列都已经在二级索引里,就不需要回表,这是覆盖索引的优化思路。
3.3 覆盖索引与索引下推
覆盖索引的定义非常容易理解:一条查询语句需要读取的所有列,恰好都包含在某个二级索引中。此时 InnoDB 可以直接从二级索引叶子节点返回数据,而不需要回表查聚簇索引。
举个例子:
CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, user_id VARCHAR(32) NOT NULL, user_name VARCHAR(64), age INT, INDEX idx_user_id_name(user_id, user_name) );这条查询就可以使用覆盖索引:
SELECT user_id, user_name FROM t_user WHERE user_id = 'u10086';因为查询字段user_id、user_name都在联合索引idx_user_id_name中,MySQL 不需要回表。
索引下推(Index Condition Pushdown,ICP)是 MySQL 5.6 引入的优化。它允许在索引遍历过程中,对索引中包含的字段先做过滤,减少回表次数。举个例子,联合索引(name, age),查询:
SELECT * FROM t_user WHERE name LIKE '张%' AND age = 20;在没有 ICP 的情况下,存储引擎会根据name LIKE '张%'找到多个主键,然后逐一回表,再把age = 20的判断放到 Server 层做。有了 ICP 之后,存储引擎在索引遍历时就直接用age = 20过滤,回表次数大大减少。
面试时可以这么答:索引下推不是让 SQL 不走回表,而是把部分过滤条件下推到存储引擎层,减少回表次数。这是 MySQL 索引调优中容易被忽略、但收益明显的优化点。
4. 为什么非得是 B+ 树:索引底层数据结构对比
索引调优面试的问法经常是:“为什么 MySQL 的索引选择 B+ 树,而不是哈希表、红黑树或者 B 树?”这个问题看起来是数据结构题,实际是考察候选人对磁盘 IO、数据范围查询和树高控制的理解。
4.1 为什么不选哈希索引
哈希索引的查询效率理论上非常高,等值查询时间复杂度接近 O(1)。但哈希索引有两个明显缺点:
- 不支持范围查询。
WHERE age > 20这种条件无法通过哈希索引高效完成,必须全表扫描。 - 不支持排序。哈希表是无序的,
ORDER BY操作无法利用索引,只能额外 filesort。
MySQL 的 InnoDB 支持自适应哈希索引,但它是由存储引擎内部自动维护的,目的是加速等值查询,我们无法手动指定哈希索引。面试里提到哈希索引,重点指出“等值查询快、范围查询弱、无法排序”即可。
4.2 为什么不选红黑树
红黑树是平衡二叉搜索树,在内存中查找效率不错。但 MySQL 数据最终落在磁盘上,树的高度直接决定磁盘 IO 次数。数据量达到几千万时,红黑树的高度也相对较高,磁盘 IO 次数更多,性能下降明显。而且红黑树的叶子节点互不相连,范围查询还是要走中序遍历,效率不够理想。
4.3 B+ 树的三个核心优势
B+ 树的精髓可以总结成三点:
- 矮胖结构。B+ 树的非叶子节点不存数据,只存索引键值,因此每个节点能容纳更多键,树高度通常只有 3 到 4 层。千万级数据量下,从根节点到叶子节点也只需要 3 到 4 次磁盘 IO。
- 叶子节点双向链表。B+ 树的叶子节点通过链表相连,非常适合范围查询和排序。
WHERE id BETWEEN 100 AND 200只需要找到起点,然后顺着链表顺序横扫即可。 - 叶子节点存储完整数据或主键值。InnoDB 聚簇索引的叶子节点存整行数据,二级索引的叶子节点存主键值,这与 InnoDB 的存储模型天然契合。
对比 B 树,B 树的非叶子节点也会存储数据,因此单个节点能存储的索引键数量更少,相同数据量下树更高,磁盘 IO 次数更多。面试中经常问“B+ 树和 B 树的区别”,答出“非叶子节点是否存数据”和“叶子节点链表是否支持范围扫描”这两点就够了。
5. 索引分类与设计规范:别再闭眼建索引
索引设计,是 MySQL 索引调优面试中最容易暴露水平的环节。候选人话说得再多,不如给出一个有设计依据的建表方案。
5.1 索引的常见分类
从使用功能上分,MySQL 索引包括:
- 主键索引:数据唯一,一个表只能有一个主键索引,也是聚簇索引。
- 唯一索引:索引列的值不能重复,但允许 NULL,可以有多个唯一索引。
- 普通索引:最基本的索引,只为了加速查询,没有唯一性限制。
- 联合索引:多个列组合成一个索引,遵循最左前缀原则。
- 全文索引:用于文本搜索,
LIKE '%keyword%'无法走普通索引时可以考虑全文索引。 - 空间索引:MySQL 8.0 里用于地理坐标等空间数据的索引,普通业务较少使用。
从物理存储角度,又分为聚簇索引和二级索引,前面已经讲过。
5.2 联合索引和最左前缀原则
联合索引(a, b, c),本质上先按列a排序,再按列b排序,最后按列c排序。因此,索引的生效规则是最左前缀原则:查询条件必须包含联合索引的最左侧列,才能用上该索引。
以下 SQL 能用上联合索引:
WHERE a = 1 WHERE a = 1 AND b = 2 WHERE a = 1 AND b = 2 AND c = 3 WHERE b = 2 AND a = 1 AND c = 3 -- MySQL 优化器会调整顺序以下 SQL 无法使用联合索引(a, b, c):
WHERE b = 2 WHERE c = 3 WHERE b = 2 AND c = 3面试很容易追问:WHERE a = 1 AND c = 3能走索引吗?答案是能走,但只用到索引中的a列,c = 3无法利用索引,因为中间隔了b列。这对应 B+ 树中索引键的排列顺序。
这里还需要补充一个调优细节:如果要创建WHERE a = 1 AND c = 3这类高频查询的索引,可以考虑把联合索引改成(a, c, b),让c能被索引利用,或者直接建立(a, c)索引。索引不是越多越好,要根据真实业务查询组合来设计。
5.3 前缀索引
如果某个字段是长字符串,比如user_agent、description,整个字段建索引会占用大量空间,而且索引树深度增加。此时可以考虑前缀索引,只取字段的前 N 个字符建立索引。
ALTER TABLE t_log ADD INDEX idx_user_agent(user_agent(32));前缀索引的缺点是无法用于ORDER BY和GROUP BY,也无法做覆盖索引扫描,因为存储的只是前缀字符。查询时还会多一步回表验证完整值。实际使用时,需要评估选择性:取多长的前缀才能让重复率足够低。
5.4 索引设计的最佳实践
这里先给出一版适合写在简历项目和业务实践里的索引设计规则:
- 经常出现在
WHERE、JOIN ON、ORDER BY、GROUP BY里的列,优先考虑加索引。 - 区分度太低的列不适合单独加索引,比如性别字段只有男女两类,走索引还不如全表扫描。
- 联合索引字段顺序有讲究,一般把等值查询的列放前面,范围查询的列放后面;区分度更高的列放前面通常效果更好。
- 不要给一张表盲目建十几个索引,写入性能会明显下降,因为每次插入、更新都要维护所有索引。
- 长字符串考虑前缀索引,但要根据选择性评估长度。
- 更新非常频繁的列,要谨慎加索引,因为索引会拖慢 update。
6. EXPLAIN 实战:索引调优的照妖镜
索引设计得再好,也要通过实际执行计划验证。MySQL 中分析 SQL 执行计划的工具是EXPLAIN。面试场景下,面试官可能直接抛出一段 SQL,让你分析它为什么慢;也可能给出一个EXPLAIN结果,让你指出问题在哪里。
6.1 EXPLAIN 输出关键字段
下面用一个简化例子演示:
EXPLAIN SELECT order_id, user_id, status FROM t_order WHERE user_id = 'u10086' AND create_time > '2025-01-01 00:00:00' ORDER BY create_time DESC LIMIT 20;重点关注这些字段:
type:访问类型,从好到坏大致是system、const、eq_ref、ref、range、index、ALL。ALL是全表扫描,一般需要重点优化。key:实际使用的索引名称。如果是NULL,说明这条 SQL 没有使用索引。rows:预估扫描行数,值越小通常越好,但只是估算。Extra:额外信息,常见的有:Using index:使用了覆盖索引,不回表,是理想情况。Using index condition:使用了索引下推,已做部分过滤。Using where:存储引擎返回数据后,Server 层还要再做条件过滤。Using filesort:需要额外排序,无法直接利用索引排序。数据量大时,这里通常是性能瓶颈。Using temporary:使用了临时表,常见于GROUP BY、DISTINCT等操作,需要特别警惕。
6.2 常见索引失效场景
面试中几乎必问“哪些情况会导致索引失效”。整理一套完整的回答模板非常重要,我建议分条说,不要只说一两点:
- 对索引列做了函数运算。比如
WHERE YEAR(create_time) = 2025,即使create_time有索引,在索引列上套函数后也会失效。可以改成范围查询create_time >= '2025-01-01' AND create_time < '2026-01-01'。 - 隐式类型转换。比如索引列是字符串类型,但查询条件写成
WHERE phone = 13800138000,MySQL 会把字符串转为数字再去比较,导致索引失效。反过来,用字符串条件查数字列同样可能失效。 - 模糊查询以
%开头。LIKE '%abc'无法走索引,因为 B+ 树无法从中间开始查找;LIKE 'abc%'可以走范围扫描。 OR连接的条件,只要其中一个列没有索引,整个查询就可能不走索引。可以考虑改成UNION ALL,或者把涉及的列都建上索引。- 联合索引不满足最左前缀原则。例如联合索引
(a, b, c),直接查b或c,索引失效。 - 对索引列进行运算或类型转换,比如
WHERE id + 1 = 10。 - 用
IS NULL、IS NOT NULL是否走索引取决于优化器判断,有时扫描整个索引比回表更快,优化器会选择全表扫,这不能简单说“一定失效”。
这里要特别提醒:面试回答索引失效时,不要机械背列表,要说一句“其实很多情况下,是优化器估算成本后选择不走索引”。这样既显得理解深刻,也能应对追问。
6.3 排序与分页优化
ORDER BY create_time DESC LIMIT 20这类分页查询,在数据量大时很容易出现慢 SQL。如果排序字段不能利用索引,MySQL 需要filesort,数据量大时性能很差。
常用的优化方向:
- 排序字段和查询过滤字段组成联合索引,让排序直接走索引。比如
(user_id, create_time)。 - 深分页问题。
LIMIT 1000000, 20会让 MySQL 扫描前面 100 万行再丢弃,性能极差,可以使用延迟关联或传入上次查询的最大 id:
-- 延迟关联思路:先查主键,再回表 SELECT t.order_id, t.user_id, t.status FROM t_order t INNER JOIN ( SELECT id FROM t_order WHERE user_id = 'u10086' ORDER BY create_time DESC LIMIT 1000000, 20 ) tmp ON t.id = tmp.id;-- 基于上一页最大 id 的翻页,适合排序字段稳定且唯一场景 SELECT order_id, user_id, status FROM t_order WHERE user_id = 'u10086' AND create_time < '2025-06-01 12:00:00' ORDER BY create_time DESC LIMIT 20;第二种方式更多是思路演示,实际业务中还需要处理create_time重复等边界情况。
7. MySQL 事务、锁与 MVCC 高频连环问
面试官只要继续追问索引后面的原理,多半会引到事务和锁。原因是:索引分析的是查询性能,而事务和锁决定了数据正确性和并发能力。两个维度结合,才能真正判断一个后端开发是否具备数据库内功。
7.1 事务四大特性 ACID
ACID 是面试必背,但要避免只背缩写,要能展开:
- 原子性(Atomicity):事务内的操作要么全部成功,要么全部失败回滚。InnoDB 通过 undo log 实现。
- 一致性(Consistency):事务执行前后,数据完整性约束不被破坏。一致性是最终目标,其他三个特性是支撑手段。
- 隔离性(Isolation):并发事务之间不能互相干扰。通过锁和 MVCC 实现。
- 持久性(Durability):事务提交后,修改永久保存。通过 redo log 实现。
7.2 四种隔离级别与 MySQL 默认隔离级别
SQL 标准定义的隔离级别从低到高:
- 读未提交(Read Uncommitted)
- 读已提交(Read Committed)
- 可重复读(Repeatable Read)
- 串行化(Serializable)
MySQL InnoDB 的默认隔离级别是“可重复读”。这里有个高频考点:很多人以为可重复读只解决脏读,但 InnoDB 的可重复读通过 MVCC 解决了快照读下的幻读问题;而对于当前读,在可重复读级别下,还需要通过间隙锁和临键锁来避免幻读。
7.3 MVCC 如何工作
MVCC,全称 Multi-Version Concurrency Control,多版本并发控制。它让普通的读操作和写操作不互相阻塞。核心思路是:每一行记录可能存在多个版本,每次事务更新会产生新的版本,旧版本通过 undo log 保留。
对于普通的SELECT快照读,InnoDB 根据 ReadView 判断当前事务能看到哪个版本。ReadView 中记录了一组当前活跃事务的 id,主要判断规则是:
- 如果版本的事务 id 比 ReadView 中最早活跃事务 id 还小,说明该版本在本次事务开始前已提交,可见。
- 如果版本的事务 id 属于 ReadView 中的活跃事务,说明该版本由未提交事务生成,不可见。
- 如果版本的事务 id 大于 ReadView 中最大的活跃事务 id,说明该版本在当前事务之后生成,不可见。
理解 MVCC 的关键在于:可重复读和读已提交的差别,实际上就是 ReadView 生成时机的差别。可重复读在事务内第一次执行普通SELECT时生成 ReadView,之后整个事务复用这个 ReadView;读已提交则每次SELECT都会生成新的 ReadView。
7.4 InnoDB 锁机制与死锁
InnoDB 常用锁包括:
- 共享锁(S 锁)和排他锁(X 锁)。
SELECT ... LOCK IN SHARE MODE加共享锁,SELECT ... FOR UPDATE加排他锁。 - 行锁是 InnoDB 相对 MyISAM 的一大优势。行锁又细分为记录锁、间隙锁、临键锁。
- 记录锁只锁索引记录;间隙锁锁两个记录之间的区间,范围查询时防止幻读;临键锁是记录锁和间隙锁的组合。
面试高频追问是“如何排查死锁”。死锁是两个事务互相持有对方需要的锁,谁都不释放。排查思路:
- 查看错误日志中的死锁信息,InnoDB 会打印最近一次死锁的详细信息。
- 使用
SHOW ENGINE INNODB STATUS;查看最近死锁的锁等待关系。 - 根据日志分析事务加锁顺序,看是否存在交叉。
避免死锁的最佳实践是:多个事务访问多张表时,尽量保持相同顺序;事务尽量短,减少锁持有时间;合理使用索引,因为行锁是基于索引实现的,如果无法命中索引,可能退化为表锁。
8. 日志系统:redo log、binlog、undo log
数据库面试的另一座大山是日志。MySQL 提交一个事务时,并不是每次都要把数据页刷盘,而是依赖 WAL 机制(Write-Ahead Logging),也就是说先写日志,再写数据文件。
8.1 redo log 解决崩溃恢复
redo log 是 InnoDB 存储引擎层日志,记录的是物理修改,比如“在某个数据页的某个偏移量上写入了什么数据”。它主要用于崩溃恢复:即使数据页还没刷盘,数据库宕机重启后,也可以通过重放 redo log 恢复已提交事务的修改。
redo log 是固定大小、循环写入的。写入策略由innodb_flush_log_at_trx_commit控制:
- 值为 0 时,每次提交不主动刷盘,性能最好但可能丢失最近一秒数据。
- 值为 1 时,每次事务提交都刷盘,不会丢数据,性能最慢但最安全。
- 值为 2 时,每次提交写入操作系统缓存,由操作系统决定何时刷盘,性能和安全折中。
生产环境通常根据业务要求配置。
8.2 binlog 解决主从复制和数据归档
binlog 是 Server 层日志,记录的是逻辑修改,包括所有导致数据变更的 SQL 语句或行变更。它有两个重要作用:
- 主从复制:主库把 binlog 发给从库,从库重放实现同步。
- 数据恢复:通过 binlog 可以恢复到某个时间点。
binlog 的格式常见有三种:STATEMENT、ROW和MIXED。MySQL 8.0 默认是ROW格式,相比STATEMENT更安全,但日志量更大。
8.3 两阶段提交
redo log 和 binlog 都用于持久化和恢复,但两者是不同组件的日志。为了保证数据一致性,InnoDB 使用两阶段提交:
- InnoDB 先将 redo log 写入,状态为 prepare。
- Server 层写入 binlog。
- InnoDB 将 redo log 状态改为 commit。
这样在崩溃恢复时,如果 binlog 没写成功,事务会回滚;如果 binlog 写成功了,即使 redo log 还没 commit,也会重放事务让数据不丢失。面试时能讲清楚两阶段提交的顺序和目的,基本就能通过日志这一关。
8.4 undo log 与回滚
undo log 记录的是逻辑变更的反向操作,用于事务回滚。同时它也是 MVCC 实现中数据多版本链的底层支撑。之前讲 MVCC 时说每一行记录可能有多个版本,这些旧版本就是靠 undo log 串联起来的。
9. MySQL 面试 50 问速查清单
为了贴近“50问”这个主题,我把面试中最常见的 50 个问题整理成一张速查表。这份清单不追求逐字答案,而是作为自测和检索目录。如果你能对着每个问题讲出 30 秒以上的完整答案,MySQL 面试基本不会有太大问题。
| 序号 | 问题 | 核心回答要点 |
|---|---|---|
| 1 | 一条 SQL 的执行流程? | 连接器、分析器、优化器、执行器、存储引擎 |
| 2 | InnoDB 和 MyISAM 区别? | 事务、行锁、崩溃恢复、外键 |
| 3 | 为什么用 B+ 树? | 磁盘 IO、树高、范围查询、叶子节点链表 |
| 4 | B 树和 B+ 树的区别? | 非叶子节点是否存数据、范围查询方式 |
| 5 | 聚簇索引是什么? | InnoDB 主键索引的叶子节点存整行 |
| 6 | 二级索引是什么? | 叶子节点存索引列值和主键值 |
| 7 | 什么是回表? | 先查二级索引再查聚簇索引 |
| 8 | 什么是覆盖索引? | 查询列都在二级索引中,不需要回表 |
| 9 | 什么是索引下推? | 存储引擎层先过滤索引字段,减少回表 |
| 10 | 哪些列适合建索引? | WHERE、JOIN、ORDER BY、GROUP BY 高频列 |
| 11 | 哪些列不适合建索引? | 区分度低、更新频繁、长文本 |
| 12 | 联合索引设计原则? | 最左前缀、等值列放前、区分度高的靠前 |
| 13 | 最左前缀原则是什么? | 查询必须从联合索引最左列开始 |
| 14 | 条件顺序会影响索引吗? | 优化器会调整,一般不影响 |
| 15 | 联合索引a=1 and c=3走索引吗? | 能走部分,只用到 a 列 |
| 16 | 前缀索引怎么用? | 长字符串取前 N 个字符 |
| 17 | 前缀索引的缺点? | 不能排序,不能覆盖扫描,可能要回表 |
| 18 | 什么是索引失效? | 函数、隐式转换、LIKE %、OR、违反最左前缀 |
| 19 | LIKE 查询什么时候走索引? | LIKE 'abc%' 可走,LIKE '%abc' 一般不走 |
| 20 | 隐式类型转换为什么失效? | 列上发生类型转换,破坏索引有序性 |
| 21 | WHERE 中 OR 的优化? | 所有列都建索引或改用 UNION ALL |
| 22 | 什么是 EXPLAIN? | 查看 SQL 执行计划的工具 |
| 23 | EXPLAIN 的 type 的含义? | system、const、eq_ref、ref、range、index、ALL |
| 24 | Extra 中 Using filesort 怎么优化? | 排序字段建立联合索引 |
| 25 | Extra 中 Using temporary 怎么优化? | 减少临时表,拆解 GROUP BY 或 DISTINCT |
| 26 | 深分页怎么优化? | 延迟关联、基于上一页最大 id |
| 27 | 什么是回表性能瓶颈? | 二级索引查找多、回表 IO 次数增加 |
| 28 | 主键为什么推荐自增? | 减少页分裂,保证插入顺序 |
| 29 | 唯一索引和普通索引选哪个? | 业务需要唯一性才用唯一索引,否则普通索引 |
| 30 | 索引是不是越多越好? | 不是,写入慢、占空间 |
| 31 | ACID 分别怎么实现? | undo log、redo log、锁、MVCC |
| 32 | MySQL 默认隔离级别? | 可重复读 |
| 33 | 四种隔离级别? | 读未提交、读已提交、可重复读、串行化 |
| 34 | 脏读、不可重复读、幻读? | 读未提交有脏读,读已提交解决脏读,可重复读解决不可重复读 |
| 35 | MVCC 是什么? | 多版本并发控制,利用 undo log 版本链 |
| 36 | ReadView 怎么判断可见性? | 活跃事务 id 与版本事务 id 比较 |
| 37 | 快照读和当前读? | 普通 SELECT 是快照读,加锁读写是当前读 |
| 38 | 可重复读为什么还能防幻读? | 当前读靠间隙锁和临键锁 |
| 39 | 行锁是基于什么实现的? | 索引 |
| 40 | 间隙锁是什么? | 锁住记录之间的区间,防止插入 |
| 41 | 死锁怎么排查? | SHOW ENGINE INNODB STATUS、错误日志 |
| 42 | 避免死锁经验? | 固定加锁顺序、缩短事务、保证索引命中 |
| 43 | redo log 作用? | 崩溃恢复,物理日志 |
| 44 | binlog 作用? | 主从复制和数据归档,逻辑日志 |
| 45 | undo log 作用? | 事务回滚和多版本链 |
| 46 | 两阶段提交是什么? | prepare redo log、写 binlog、commit redo log |
| 47 | WAL 是什么? | 先写日志,再写数据页,提升性能 |
| 48 | 慢查询日志怎么开启? | set global slow_query_log = on; 等配置 |
| 49 | 读写分离注意事项? | 主从延迟、数据一致性、路由策略 |
| 50 | 分库分表怎么选型? | 先考虑索引优化,单表数据量过大再拆分 |
这张表作为目录已经够用。下面再补充几条真正能拿高分的答题技巧和排查经验。
10. 高频追问应对技巧与实战经验
10.1 慢 SQL 排查完整流程
真实面试场景里,面试官可能不直接问理论,而是给出一个线上事故:“某条 SQL 突然变慢,你怎么排查”。推荐按下面顺序回答,既有逻辑又能体现工程经验:
- 先确认是否真的有慢 SQL。查看慢查询日志,或使用
SHOW FULL PROCESSLIST;查看当前正在执行的线程。 - 对目标 SQL 使用
EXPLAIN分析执行计划,重点看type、key、rows、Extra。 - 如果
type是ALL且key为 NULL,先检查是否有可用索引,以及 SQL 写法是否导致索引失效。 - 如果索引存在但没用上,可能是优化器低估了成本,可以尝试
ANALYZE TABLE更新统计信息,或者使用FORCE INDEX测试效果。 - 如果单条 SQL 执行很快,但接口整体慢,还要考虑连接池、网络、锁等待、大事务等因素。
- 如果 SQL 必须处理超大范围数据,思考是否可以把大查询拆成多个小查询,或者调整分页逻辑。
10.2 如何回答“你是怎么优化这个慢 SQL 的”
项目经历里被问到 SQL 优化时,不要只说“加了索引”。更好的回答套路是:
- 先说背景:表数据量、业务场景、慢 SQL 现象。
- 再说定位:通过慢查询日志和
EXPLAIN看到全表扫描,rows很大。 - 再说优化动作:确定高频查询条件后,建立联合索引,调整 SQL 写法,避免函数运算和隐式转换。
- 最后说效果:扫描行数从百万降到几百,响应时间从 2 秒降到 50 毫秒,同时观察写入性能和索引空间。
这个模板能体现完整闭环,面试官会认为你是真的动手排查过问题,而不是只背过八股。
10.3 从 8.0 角度看优化
MySQL 8.0 有几点容易成为面试加分项:
- 查询缓存被移除。
- 默认字符集从 latin1 变为 utf8mb4。
- 支持窗口函数,部分复杂分组排序 SQL 可以写得更简洁。
- 支持不可见索引(invisible index),可以用来在不删除索引的情况下测试索引对执行计划的影响。
- 支持降序索引,部分场景下应对
ORDER BY DESC更友好。 - 新增
EXPLAIN ANALYZE,可以输出实际执行时间和行数,比普通EXPLAIN更接近真实执行情况。
说到版本时记得说明:这些特性以实际安装版本为准,生产环境升级前要充分测试。
11. MySQL 索引调优最佳实践汇总
结合前面的内容,这里给出一份偏工程的检查清单。项目开发或面试准备时,可以照着逐条核对:
- 建表时明确主键,优先选择自增整数或趋势递增列,不推荐用随机 UUID 直接做主键。如果业务必须用 UUID,可以额外加一个自增主键,UUID 用唯一索引维护。
- 联合索引设计前,先拿业务中最常见的查询条件做测试,确认最左前缀匹配。
- 核心查询尽量做到覆盖索引。
SELECT不要无脑SELECT *,只查需要的列,让索引覆盖查询字段,减少回表。 - 满足需求的前提下,索引列越短越好。能用
INT不用BIGINT,能用前缀索引就不整列建索引。 - 更新频繁的索引列数量和长度都保持克制,减少写入维护成本。
- SQL 写法规范:不在索引列上做函数运算,注意字段类型统一,避免隐式转换,模糊查询避免首部通配符。
- 上线前必须用
EXPLAIN验证关键 SQL 的执行计划,养成看到慢 SQL 就分析Extra的习惯。 - 数据量增长后,关注统计信息是否准确,必要时执行
ANALYZE TABLE。 - 大事务拆小,避免一个事务里做太多写操作,减少锁持有时间,降低死锁概率。
- 生产环境删除或新增索引,在低峰期执行,并通过
SHOW CREATE TABLE或数据库运维平台确认当前结构。
12. 总结
MySQL 面试题看似又多又杂,但核心主线非常清晰:先理解 SQL 执行流程,再理解 InnoDB 的聚簇索引与二级索引,然后掌握 B+ 树带来的查询优势,接着通过联合索引、覆盖索引、索引下推做调优,最后用事务、锁、MVCC 和日志系统解释底层的一致性保障。索引调优之所以被称为“天花板”,是因为它把数据结构、存储引擎、SQL 优化器和业务设计串在了一起。
如果基础还不太扎实,建议先在自己电脑上装一个 MySQL 8.0,建一张几十万行数据量的测试表,手动跑几条EXPLAIN,改一改 SQL 和索引,观察type和Extra的变化。面试题背得再熟,都不如亲手验证一次执行计划变化带来的理解深刻。希望这篇文章能成为你准备 MySQL 面试和日常索引调优的参考文件,遇到问题可以随时翻到对应小节对照排查。