前言

MySQL 面试题看起来很多,但真正绕不开的主线其实很清楚:一条 SQL 是怎么执行的,数据是怎么存的,索引为什么能加速查询,事务如何保证一致性,并发冲突如何处理,系统变慢时应该怎么定位。

如果只是背概念,很容易遇到追问就卡住;如果能把这些知识点串成一条链路,回答就会自然很多。本文按面试中最常见的模块梳理 MySQL 要点,适合作为面试前的复习清单。

MySQL 整体架构

MySQL 可以粗略分为 Server 层和存储引擎层。

flowchart TD
    A[客户端连接] --> B[连接器]
    B --> C[解析器]
    C --> D[优化器]
    D --> E[执行器]
    E --> F[存储引擎]
    F --> G[(磁盘数据)]

Server 层负责连接管理、权限校验、SQL 解析、查询优化、执行调度等通用能力。

存储引擎层负责真正的数据读写。常见引擎包括 InnoDB、MyISAM、Memory 等。现在生产环境最常用的是 InnoDB,因为它支持事务、行级锁、外键、崩溃恢复和 MVCC。

面试回答可以这样概括:

MySQL 的 SQL 执行不是直接读表,而是先经过连接、解析、优化、执行,最后由存储引擎读取或修改数据。Server 层负责 SQL 生命周期,存储引擎层负责数据组织和持久化。

一条 SQL 的执行流程

以一条查询语句为例:

1
select name from user where id = 10;

大致流程如下:

  1. 客户端与 MySQL 建立连接。
  2. 连接器校验账号、密码、权限。
  3. 解析器做词法和语法分析,判断 SQL 是否合法。
  4. 优化器选择执行计划,例如是否使用索引、使用哪个索引、表连接顺序等。
  5. 执行器调用存储引擎接口获取数据。
  6. 存储引擎从 Buffer Pool 或磁盘读取数据页。
  7. 执行器过滤结果并返回给客户端。

更新语句还会涉及 undo log、redo log、binlog、锁和事务提交。

面试中常见追问:

问题 回答重点
优化器一定会选择最优索引吗? 不一定。优化器基于统计信息估算成本,统计信息不准时可能选错索引
查询缓存还存在吗? MySQL 8.0 已移除 Query Cache,不建议再作为优化手段
执行计划怎么看? 使用 EXPLAIN 观察 type、key、rows、Extra 等字段

InnoDB 存储结构

InnoDB 的数据按页组织,页是磁盘和内存交互的基本单位,默认大小通常是 16KB。

表数据不是简单按行散落在磁盘上,而是组织成 B+ 树。对于 InnoDB 来说:

  • 如果表有主键,数据会按主键组织成聚簇索引。
  • 如果表没有显式主键,InnoDB 会优先选择非空唯一索引作为聚簇索引。
  • 如果仍然没有合适索引,InnoDB 会生成隐藏 row_id。

聚簇索引的叶子节点存放完整行数据,二级索引的叶子节点存放二级索引字段和主键值。

这也是为什么 InnoDB 表建议一定要有主键,而且主键最好短、稳定、递增。

索引核心要点

索引的本质是用额外的数据结构换取查询效率。MySQL 中最常见的是 B+ 树索引。

为什么用 B+ 树

回答这个问题时,最好先把数据库索引的运行环境讲清楚:数据库的数据量通常远大于内存,索引和数据最终要落到磁盘上。一次查询的成本不只是比较多少次,更重要的是需要读多少个数据页,也就是磁盘 IO 次数。

InnoDB 默认页大小通常是 16KB,B+ 树的一个节点正好可以对应一个或多个数据页。一次从根节点走到叶子节点的过程,本质上就是读少量索引页并定位目标记录。因为 B+ 树是多叉树,一个节点可以存很多 key,所以树高通常很低。即使是千万级、亿级数据,索引树高度也可能只有 3 到 4 层,查询时需要访问的数据页数量比较可控。

B+ 树适合数据库索引,核心原因可以概括为:

  • 树高低:多叉结构让每层容纳大量 key,减少从根节点到叶子节点的层数,从而减少磁盘 IO。
  • 非叶子节点更“轻”:非叶子节点只存索引 key 和子节点指针,不存完整行数据,一个页能放更多索引项,进一步降低树高。
  • 叶子节点有序且相连:叶子节点之间通过链表连接,非常适合范围查询、排序扫描和分页扫描。
  • 查询性能稳定:所有数据都在叶子节点,单点查询路径长度相对稳定,不会像普通二叉树那样因为数据分布极端而退化。
  • 适配磁盘预读:范围扫描时,叶子节点逻辑上连续,数据库可以更好利用顺序读和预读能力。

可以用一个简化例子理解树高差异。假设有 1000 万条数据:

1
2
二叉树:每个节点最多 2 个分支,理想情况下也需要二十多层
B+ 树:每个节点可能有几百到上千个分支,通常只需要三四层

在内存里,多比较几次问题可能不大;但在数据库里,每多一层就可能多一次磁盘页访问,这就是 B+ 树比很多传统内存数据结构更适合数据库索引的关键。

常见索引结构对比

二叉查找树

二叉查找树的规则很简单:左子树比当前节点小,右子树比当前节点大。

它的问题是太依赖插入顺序。如果数据接近有序插入,普通二叉查找树可能退化成链表。

1
2
3
4
5
6
7
1
\
2
\
3
\
4

这种情况下查询复杂度会从 O(log n) 退化到 O(n)。对于数据库索引来说,数据量越大,退化风险越不能接受。

面试表达:

二叉查找树结构简单,但极端情况下会退化成链表,查询性能不稳定,而且树高较高,不适合磁盘索引。

平衡二叉树和红黑树

平衡二叉树、红黑树解决了普通二叉树容易退化的问题。它们会通过旋转维持相对平衡,因此在内存数据结构中很常见,例如 Java 的 TreeMap、HashMap 链表树化后使用红黑树。

但它们仍然不适合做数据库主流磁盘索引,原因是每个节点最多只有两个子节点。数据量很大时,树高仍然明显高于 B+ 树。树高越高,从根节点查到叶子节点需要访问的磁盘页越多。

对内存来说,红黑树多走几层只是多几次指针跳转;对磁盘来说,多走几层就可能多几次 IO,成本完全不一样。

面试表达:

红黑树适合内存查找,因为它能保持平衡;但数据库索引更关心磁盘 IO,红黑树分叉太少,树高偏高,所以不如多叉的 B+ 树。

Hash 索引

Hash 索引通过哈希函数把 key 映射到桶里,等值查询非常快。

例如:

1
where id = 1001

这类查询如果使用 Hash 索引,理论上可以很快定位。

但 Hash 索引的问题也很明显:

  • 不支持范围查询,例如 where age > 20
  • 不支持按索引顺序排序,例如 order by age
  • 不支持最左前缀匹配。
  • 哈希冲突需要额外处理。
  • 只适合等值查询场景。

MySQL 中 Memory 引擎支持 Hash 索引,InnoDB 也有自适应哈希索引,但 InnoDB 的普通索引主要还是 B+ 树。原因是业务查询不只有等值查询,还大量存在范围查询、排序、分组、分页、联合索引匹配等场景。

面试表达:

Hash 索引等值查询很快,但它破坏了原始 key 的有序性,所以不适合范围查询和排序。MySQL 的通用索引需要同时兼顾等值查询和范围查询,因此 B+ 树更合适。

B 树

B 树也是多叉平衡树,比二叉树更适合磁盘环境。它的特点是非叶子节点和叶子节点都可以存放数据。

这带来一个问题:非叶子节点如果存了完整数据,每个节点能容纳的 key 数量就会减少,树的分叉数降低,树高可能增加。同时,范围查询时需要在不同层级之间中序遍历,顺序扫描不如 B+ 树的叶子链表直接。

面试表达:

B 树已经具备多叉和平衡的特点,但节点既存索引又存数据,会降低单页能存的 key 数量;范围查询也不如 B+ 树叶子节点链表高效。

B+ 树

B+ 树可以看作是对 B 树更适合数据库场景的改造。

它的特点是:

  • 非叶子节点只做索引导航,不存完整行数据。
  • 所有数据都在叶子节点。
  • 叶子节点之间按 key 顺序连接。
  • 从根到任意叶子节点的路径长度基本一致。

这几个特点让 B+ 树同时兼顾了单点查询和范围查询。

例如:

1
2
3
select * from user where id = 1001;
select * from user where id between 1000 and 2000;
select * from user where age = 20 order by id limit 20;

第一类可以从根节点快速定位到叶子节点;第二类定位到范围起点后,沿叶子节点链表向后扫描即可;第三类可以利用索引的有序性减少额外排序。

综合对比:

结构 优点 缺点 更适合的场景
二叉查找树 实现简单,支持有序查找 可能退化成链表,性能不稳定 小规模内存结构
平衡二叉树 严格平衡,查询稳定 旋转维护成本高,树高仍偏高 内存查找
红黑树 近似平衡,插入删除效率较好 分叉少,磁盘 IO 多 内存有序集合
Hash 等值查询很快 不支持范围、排序、前缀匹配 纯等值查询
B 树 多叉平衡,树高低 数据分布在所有节点,范围扫描不够顺滑 文件系统、部分数据库场景
B+ 树 树高低,范围查询强,磁盘 IO 可控 需要从根走到叶子节点才能拿到数据 数据库索引

所以,MySQL 使用 B+ 树不是因为它在所有场景下都最快,而是因为它在磁盘 IO、等值查询、范围查询、排序、分页和稳定性之间取得了最好的综合平衡。

聚簇索引和二级索引

InnoDB 中主键索引是聚簇索引。

1
2
主键索引叶子节点:完整行数据
二级索引叶子节点:索引列 + 主键值

如果通过二级索引查询,但要拿的字段不在二级索引中,就需要先查二级索引拿到主键,再回到主键索引查完整行,这个过程叫回表。

例如:

1
select name from user where age = 20;

如果只有 age 索引,而 name 不在索引里,就可能发生回表。

覆盖索引

覆盖索引是指查询需要的字段都能从索引中拿到,不需要回表。

1
select id, name from user where age = 20;

如果存在联合索引:

1
idx_age_name(age, name)

那么这条查询可以直接从索引中返回 idname,减少回表成本。这里 id 是主键,二级索引叶子节点本身会包含主键值。

最左前缀原则

联合索引遵循最左前缀原则。

例如索引:

1
idx_a_b_c(a, b, c)

能有效使用索引的情况:

1
2
3
4
where a = 1
where a = 1 and b = 2
where a = 1 and b = 2 and c = 3
where a = 1 and b > 2

可能无法完整使用索引的情况:

1
2
3
where b = 2
where c = 3
where a = 1 and c = 3

其中 where a = 1 and c = 3 一般只能较好利用 a,中间缺少 b,后续列难以按联合索引顺序继续匹配。

索引失效常见场景

面试中经常会问“哪些情况索引会失效”,重点记这些:

场景 示例 原因
对索引列使用函数 where date(create_time) = '2026-08-05' 索引列被计算,难以按原值查找
隐式类型转换 where phone = 13800138000 字符串列和数字比较可能触发转换
左模糊查询 where name like '%jun' 无法从索引左侧开始定位
联合索引跳过最左列 where b = 2 不满足最左前缀
or 条件不合理 where a = 1 or b = 2 某些分支无索引时可能放弃索引
低选择性字段 where gender = 1 扫描大量数据,优化器可能认为全表扫描更划算

但要注意:索引是否使用最终以执行计划为准,不要绝对化。

事务与 ACID

事务是数据库保证一组操作要么全部成功、要么全部失败的机制。

ACID 分别是:

特性 含义
Atomicity 原子性 一个事务中的操作要么都成功,要么都失败
Consistency 一致性 事务前后数据满足业务和数据库约束
Isolation 隔离性 并发事务之间互不干扰到指定程度
Durability 持久性 事务提交后数据不会因故障丢失

在 InnoDB 中:

  • 原子性主要依赖 undo log。
  • 持久性主要依赖 redo log。
  • 隔离性主要依赖锁和 MVCC。
  • 一致性是前三者共同配合,再加上业务约束共同保证。

事务隔离级别

SQL 标准定义了四种隔离级别:

隔离级别 脏读 不可重复读 幻读
Read Uncommitted 可能 可能 可能
Read Committed 不会 可能 可能
Repeatable Read 不会 不会 一般可避免
Serializable 不会 不会 不会

MySQL InnoDB 默认隔离级别是 Repeatable Read。

几个概念要区分清楚:

  • 脏读:读到了其他事务还没提交的数据。
  • 不可重复读:同一个事务中两次读取同一行,结果不一致。
  • 幻读:同一个事务中按相同条件查询,第二次出现了第一次不存在的新行。

InnoDB 在 Repeatable Read 下通过 MVCC 解决普通快照读的一致性问题,通过 next-key lock 等机制处理当前读中的幻读问题。

MVCC

MVCC 是多版本并发控制。它的目标是让读写并发时尽量少互相阻塞。

InnoDB 的 MVCC 主要依赖:

  • undo log:保存数据的历史版本。
  • Read View:判断当前事务能看到哪个版本。
  • 隐藏字段:例如事务 id、回滚指针等。

普通 select 是快照读,读取的是符合当前 Read View 的历史版本。

加锁读、更新、删除属于当前读,读取最新版本并加锁。

1
2
3
select * from user where id = 1;
select * from user where id = 1 for update;
update user set name = 'junly' where id = 1;

第一条通常是快照读,后两条是当前读。

面试回答可以这样组织:

MVCC 通过保存数据多个历史版本,让普通读不阻塞写、写不阻塞普通读。事务读取数据时,会根据 Read View 判断某个版本是否可见。如果当前版本不可见,就沿着 undo log 版本链寻找可见版本。

锁机制

MySQL 的锁可以从不同维度分类。

按粒度看:

  • 表锁:锁整张表,粒度大,并发低。
  • 行锁:锁具体行,粒度小,并发高。
  • 间隙锁:锁索引记录之间的范围。
  • 临键锁:记录锁和间隙锁的组合。

按模式看:

  • 共享锁:读锁,多个事务可以同时持有。
  • 排他锁:写锁,与其他锁互斥。

常见语句:

1
2
select * from user where id = 1 lock in share mode;
select * from user where id = 1 for update;

需要注意的是,InnoDB 的行锁是加在索引上的。如果更新条件没有命中索引,可能扫描大量记录并造成更大范围的锁冲突。

redo log、undo log、binlog

MySQL 面试中日志非常高频,关键是说清楚三类日志的职责。

日志 所属层 主要作用
undo log InnoDB 回滚和 MVCC
redo log InnoDB 崩溃恢复,保证持久性
binlog Server 层 主从复制、数据恢复、审计

redo log

redo log 是物理日志,记录数据页做了什么修改。事务提交时先写 redo log,宕机后可以根据 redo log 恢复已经提交的数据。

这就是 WAL 思想:先写日志,再写数据页。

undo log

undo log 记录的是逻辑上的反向操作。比如插入一行,对应的 undo 可以删除这行;更新一行,对应的 undo 可以恢复旧值。

它有两个重要作用:

  • 事务回滚。
  • 支撑 MVCC 的历史版本读取。

binlog

binlog 是 Server 层日志,常用于主从复制和基于时间点的数据恢复。

常见格式:

  • Statement:记录 SQL 语句。
  • Row:记录行级变更,最常用,复制更准确。
  • Mixed:混合模式。

两阶段提交

为了保证 redo log 和 binlog 的一致性,MySQL 使用两阶段提交。

1
2
3
1. redo log prepare
2. 写 binlog
3. redo log commit

如果没有两阶段提交,可能出现 InnoDB 已提交但 binlog 没写成功,或者 binlog 写成功但 InnoDB 数据没提交,导致主库和从库数据不一致。

主从复制

MySQL 主从复制通常基于 binlog。

sequenceDiagram
    participant M as 主库
    participant I as 从库 IO 线程
    participant R as Relay Log
    participant S as 从库 SQL 线程

    M->>M: 写入 binlog
    I->>M: 拉取 binlog
    I->>R: 写入 relay log
    S->>R: 读取 relay log
    S->>S: 重放日志并更新从库

复制延迟的常见原因:

  • 主库写入压力大,从库重放跟不上。
  • 大事务执行时间长。
  • 从库硬件性能弱。
  • SQL 线程单线程或并行度不足。
  • 网络抖动。

常见优化思路:

  • 避免大事务。
  • 合理开启并行复制。
  • 读写分离时注意延迟问题。
  • 对强一致读走主库。
  • 监控主从延迟和复制错误。

SQL 优化思路

SQL 优化不能只盯着“加索引”,更合理的顺序是:

  1. 先确认慢 SQL。
  2. 使用 EXPLAIN 看执行计划。
  3. 判断是否走了合适索引。
  4. 看扫描行数是否过大。
  5. 检查是否有回表、排序、临时表。
  6. 优化 SQL、索引或表结构。
  7. 必要时考虑缓存、归档、分库分表。

常见优化方向:

问题 优化方式
查询字段太多 避免 select *,只查必要字段
没有合适索引 建立符合查询条件的联合索引
回表太多 使用覆盖索引
深分页慢 使用延迟关联或基于游标分页
大量排序 利用索引顺序,减少 filesort
单表数据太大 归档历史数据或分表
写入频繁 控制索引数量,批量写入,削峰

深分页优化示例:

1
2
3
4
5
6
7
8
-- 普通深分页,越往后越慢
select * from order_info order by id limit 100000, 20;

-- 基于上一页最大 id 的游标分页
select * from order_info
where id > 100000
order by id
limit 20;

EXPLAIN 重点字段

EXPLAIN 是分析 SQL 的入口。

字段 关注点
id 查询执行顺序
select_type 查询类型
table 当前访问表
type 访问类型,越接近 const 越好
possible_keys 可能使用的索引
key 实际使用的索引
key_len 使用索引的长度
rows 预估扫描行数
filtered 过滤比例
Extra 额外信息,如 Using index、Using filesort

常见 type 性能大致从好到差:

1
system > const > eq_ref > ref > range > index > all

看到 type = allUsing filesortUsing temporary、扫描行数很大时,需要重点关注。

慢查询排查

线上遇到 MySQL 慢,排查不要一上来就改参数。可以按这个顺序:

  1. 看应用侧是否有接口集中变慢。
  2. 看数据库 CPU、内存、磁盘 IO、连接数。
  3. 看慢查询日志,找最耗时 SQL。
  4. EXPLAIN 分析执行计划。
  5. 看是否存在锁等待。
  6. 看是否有主从延迟。
  7. 看最近是否有数据量突增、索引变更或发布变更。

常用命令:

1
2
3
4
show processlist;
show engine innodb status;
show variables like 'slow_query_log';
show variables like 'long_query_time';

如果是锁等待,可以重点看事务是否长时间未提交,是否有大事务、批量更新、无索引更新等问题。

分库分表

分库分表不是性能优化的第一选择,它通常是单库单表已经难以支撑数据量或并发量之后的架构手段。

常见拆分方式:

方式 含义 适用场景
垂直分库 按业务模块拆库 用户、订单、支付等业务边界清晰
垂直分表 按字段冷热或大小拆表 大字段、低频字段单独存储
水平分库 同一张表按规则分到多个库 写入压力大
水平分表 同一张表按规则分成多张表 单表数据量大

常见分片键:

  • 用户 id。
  • 订单 id。
  • 租户 id。
  • 时间字段。

分库分表会带来新问题:

  • 跨库 join 复杂。
  • 分布式事务复杂。
  • 全局唯一 ID 需要单独设计。
  • 跨分片分页和排序成本高。
  • 数据迁移和扩容复杂。

所以面试中不要只说“数据多了就分库分表”,更好的回答是:

先做 SQL 和索引优化、冷热数据分离、归档、读写分离和缓存。如果单表数据量、写入压力或存储容量仍然成为瓶颈,再结合业务查询模式选择合适的分片键做分库分表。

高频面试题速记

MySQL 为什么推荐使用 InnoDB

InnoDB 支持事务、行级锁、MVCC、崩溃恢复、外键和高并发读写,是生产环境中最常用的通用存储引擎。

为什么主键建议自增

自增主键写入时基本按顺序插入,可以减少页分裂和随机 IO。主键越短,二级索引中存储的主键值也越小,整体索引空间更可控。

但分布式系统中自增主键可能带来全局唯一性和数据热点问题,需要结合业务选择雪花算法、号段模式或其他 ID 方案。

为什么不建议建太多索引

索引会占用额外空间,写入、更新、删除时也要维护索引。索引越多,写入成本越高,优化器选择成本也可能增加。

count(*)、count(1)、count(字段) 有什么区别

在 InnoDB 中,count(*)count(1) 通常都表示统计行数,优化器会选择合适方式执行。count(字段) 只统计该字段不为 null 的行。

面试中更重要的是说明语义差异,不必执着于简单结论。

varchar 和 char 怎么选

char 是定长,适合长度固定的数据,比如状态码、固定长度标识。varchar 是变长,适合长度不固定的字符串,比如名称、备注。

实际设计中还要考虑字符集、最大长度、索引长度和是否允许为空。

delete、truncate、drop 区别

操作 作用
delete 删除符合条件的数据,可带 where,是 DML
truncate 清空整表数据,通常速度更快,是 DDL
drop 删除整张表结构和数据,是 DDL

delete 可以按条件删除,事务语义更细;truncate 会重建表,不能带 where;drop 会直接删除表对象。

什么是回表

通过二级索引找到主键后,再根据主键去聚簇索引查完整行数据,这个过程叫回表。覆盖索引可以减少回表。

什么是索引下推

索引下推是指在存储引擎层尽量使用索引中的字段先过滤数据,减少回表次数。它适合联合索引中部分字段可以在索引层判断的场景。

什么是幻读

幻读指一个事务中按同样条件读取,第二次读到了第一次不存在的新行。InnoDB 在 Repeatable Read 下对快照读通过 MVCC 保证一致视图,对当前读通过 next-key lock 等机制避免幻读。

MySQL 如何保证崩溃恢复

主要依赖 redo log。事务提交时 redo log 会先持久化,数据页可以稍后刷盘。数据库异常重启后,根据 redo log 重放已提交修改,从而保证持久性。

主从复制为什么会延迟

可能是主库写入太快、从库重放慢、大事务、网络问题、从库机器性能不足或并行复制能力不足。业务上如果要求强一致读,不能直接读延迟从库,应读主库或做一致性控制。

面试回答方法

MySQL 问题很容易被问深。建议按照“现象、原因、机制、方案”的顺序回答。

例如问“慢查询怎么优化”:

  1. 先定位慢 SQL,而不是直接加索引。
  2. 用执行计划看是否走索引、扫描多少行、是否排序或临时表。
  3. 结合业务判断索引设计、SQL 写法、分页方式和数据量。
  4. 最后再考虑缓存、读写分离、归档、分库分表。

例如问“事务隔离怎么实现”:

  1. 先说隔离级别解决什么问题。
  2. 再说 InnoDB 默认 Repeatable Read。
  3. 然后讲 MVCC 解决快照读,锁解决当前读。
  4. 最后补充 undo log、Read View、next-key lock。

总结

MySQL 面试不是单点背诵,而是要建立一条完整主线:

1
2
3
4
5
6
7
SQL 执行流程
-> 存储引擎
-> 数据页和索引
-> 事务与日志
-> 锁与 MVCC
-> 主从复制
-> 性能优化和架构演进

真正能拉开差距的,不是知道某个名词,而是能解释这个机制解决了什么问题、依赖哪些组件、在真实业务中应该怎么取舍。

面试前建议重点复习三类问题:索引为什么有效,事务并发如何保证一致性,慢查询如何定位和优化。能把这三类问题讲清楚,MySQL 的大部分追问就都有了回答框架。