Administrator 25 阅读 面试

MySQL 高频面试题(含口述版回答)


1. MySQL 常见存储引擎有哪些?InnoDB 和 MyISAM 的区别?

口述版:

MySQL 最常用的是 InnoDB 和 MyISAM。

InnoDB 是目前默认引擎,支持事务、行级锁、外键,有崩溃恢复能力,适合绝大多数在线业务。

MyISAM 不支持事务,只有表级锁,查询相对简单场景下可能更快,但不适合高并发写入和需要事务的业务。

核心区别可以这样记:

对比点 InnoDB MyISAM
事务 支持 不支持
锁粒度 行级锁 表级锁
外键 支持 不支持
崩溃恢复 支持 不支持
计数 需要扫描 有保存值
适用场景 绝大多数业务 只读或低并发

现在基本都用 InnoDB,MyISAM 已经很少用了。


2. 什么是索引?有哪些类型?

口述版:

索引是帮助 MySQL 高效获取数据的数据结构,可以简单理解成书的目录。

常见类型:

  • 主键索引:主键自动创建的聚簇索引,叶子节点存完整行数据。
  • 唯一索引:保证列值唯一,允许 NULL。
  • 普通索引:最基本的索引,没有任何约束。
  • 联合索引:多个列组成的索引。
  • 全文索引:用于文本搜索(InnoDB 也支持)。

从数据结构看,InnoDB 主要用 B+Tree 索引,还有哈希索引(自适应)、全文索引等。

面试重点说清:聚簇索引 vs 非聚簇索引。InnoDB 的主键是聚簇索引,叶子节点存整行;二级索引叶子节点存的是主键值,需要回表。


3. 为什么 InnoDB 选择 B+Tree 作为索引结构?

口述版:

主要原因有三点:

  1. B+Tree 非叶子节点不存数据,只存键值,同样大小的磁盘页能放下更多索引,树更矮,IO 次数更少。
  2. 叶子节点用双向链表连接,范围查询、排序非常友好,直接顺着链表走就行。
  3. 查询更稳定,所有数据都在叶子节点,查询路径长度一致。

对比 B Tree:B Tree 每个节点都存数据,树更高,范围查询也不方便。

哈希索引虽然单点查询快,但不支持范围查询和排序,所以 InnoDB 主要用 B+Tree。


4. 联合索引的最左前缀原则是什么?

口述版:

联合索引(比如对列 a,b,c 建索引)遵循最左前缀原则:查询条件必须从索引的最左边列开始,并且不能跳过中间列,才能有效使用索引。

举例:索引 (a, b, c)

  • 能用到索引:a / a,b / a,b,c
  • 不能很好用到:只有 b、只有 c、b,c(没有 a)

优化时要注意:

  • 把区分度高的列放在前面
  • 常见查询条件尽量满足最左前缀
  • 可以用 EXPLAIN 看实际用了哪个索引

5. 什么是回表?什么是覆盖索引?

口述版:

回表:二级索引叶子节点只存主键值,找到主键后还要再回主键索引(聚簇索引)去取完整行数据,这个过程叫回表。多一次 IO,性能有损耗。

覆盖索引:查询的列刚好都在某个索引里,不需要回表就能拿到结果。比如索引 (a,b),查询 SELECT a,b FROM t WHERE a=1,直接从索引就能返回。

覆盖索引能减少回表,是常见的优化手段。用 EXPLAIN 看,如果 Extra 出现 Using index,通常就是用了覆盖索引。


6. 事务的四大特性(ACID)是什么?

口述版:

  • A(原子性):事务中的操作要么全成功,要么全失败回滚。靠 undo log 实现。
  • C(一致性):事务前后数据保持一致的业务规则(比如转账前后总金额不变)。是最终目标,由其他三个特性共同保证。
  • I(隔离性):多个事务并发时互不干扰。靠锁和 MVCC 实现。
  • D(持久性):事务提交后,数据永久保存,即使宕机也不丢。靠 redo log 实现。

7. MySQL 有哪些事务隔离级别?分别解决什么问题?

口述版:

从低到高四个级别:

隔离级别 脏读 不可重复读 幻读 说明
读未提交 可能 可能 可能 最低,基本不用
读已提交(RC) 不会 可能 可能 Oracle 默认
可重复读(RR) 不会 不会 可能 MySQL 默认
串行化 不会 不会 不会 最高,性能最差

MySQL 默认是 可重复读(RR)。InnoDB 在 RR 下通过 MVCC + Next-Key Lock 很大程度上避免了幻读,所以实际使用体验很好。


8. 什么是 MVCC?怎么实现的?

口述版:

MVCC 是多版本并发控制,用来实现高并发下的读写不阻塞。

核心思想是:每行数据有多个版本,读的时候读快照,写的时候生成新版本,互不影响。

InnoDB 实现主要靠:

  1. 隐藏字段:每行有事务ID(DB_TRX_ID)和回滚指针(DB_ROLL_PTR)。
  2. undo log:保存旧版本数据,形成一个版本链。
  3. Read View:事务启动时生成一个视图,决定当前事务能看到哪些版本的数据。

在可重复读隔离级别下,一个事务全程用同一个 Read View,所以两次读到的结果一致。


9. redo log、undo log、binlog 的区别?

口述版:

这三个日志非常重要,面试常考:

日志 作用 层面 特点
redo log 保证持久性,崩溃恢复 InnoDB 物理日志,循环写
undo log 回滚 + MVCC 多版本 InnoDB 逻辑日志,存旧数据
binlog 主从复制、数据恢复 Server层 逻辑日志,追加写

执行更新时的大致流程(两阶段提交):

  1. 先写 undo log(方便回滚)
  2. 更新内存数据
  3. 写 redo log(prepare 状态)
  4. 写 binlog
  5. 提交 redo log(commit)

两阶段提交是为了保证 redo log 和 binlog 的一致性。


10. MySQL 有哪些锁?行锁、表锁、间隙锁是什么?

口述版:

从粒度分:

  • 表锁:锁整张表,开销小,但并发差。MyISAM 主要用表锁。
  • 行锁:锁一行,并发高。InnoDB 支持。

InnoDB 行锁的具体实现:

  • Record Lock:锁住某一行。
  • Gap Lock(间隙锁):锁住一个范围的间隙,防止其他事务插入,解决幻读。
  • Next-Key Lock:Record Lock + Gap Lock,锁住行本身 + 前面的间隙。这是 RR 隔离级别下的默认算法。

另外还有意向锁(表级,帮助快速判断是否有行锁冲突)。

死锁通常发生在多个事务互相持有对方需要的行锁,可以用 SHOW ENGINE INNODB STATUS 查看死锁信息。


11. 怎么排查和优化慢查询?

口述版:

步骤一般是:

  1. 开启慢查询日志,设置合理的 long_query_time。
  2. 用 mysqldumpslow 或自己分析日志,找出最慢、最频繁的 SQL。
  3. 对可疑 SQL 执行 EXPLAIN,重点看:
    • type:最好是 ref、range,避免 ALL(全表扫描)
    • key:有没有用到索引
    • rows:扫描行数
    • Extra:有没有 Using filesort、Using temporary
  4. 优化手段:
    • 加合适索引(注意最左前缀)
    • 避免 SELECT *,只查需要的列(方便覆盖索引)
    • 优化 WHERE 条件,避免对索引列使用函数
    • 大数据量分页用延迟关联或记录上次位置
    • 实在不行考虑分表、缓存、架构调整

12. EXPLAIN 中重要字段怎么看?

口述版:

重点关注这几个:

  • type:连接类型,从好到差大致是:system > const > eq_ref > ref > range > index > ALL。日常至少要到 range 或 ref。
  • key:实际用到的索引。
  • rows:估算要扫描的行数,越小越好。
  • Extra:
    • Using index:覆盖索引,好
    • Using where:有回表或额外过滤
    • Using filesort:需要额外排序,尽量优化掉
    • Using temporary:用了临时表,通常性能较差

能看懂这几个,基本就能判断一条 SQL 好不好。


13. 主从复制的原理是什么?

口述版:

主从复制主要靠 binlog:

  1. 主库把更新操作写入 binlog。
  2. 从库的 IO 线程 拉取主库的 binlog,写成自己的 relay log。
  3. 从库的 SQL 线程 读取 relay log,重放执行,保持数据一致。

常见模式:

  • 异步复制(默认):主库写完 binlog 就返回,可能丢数据。
  • 半同步复制:至少有一个从库收到日志才返回。
  • 组复制 / MGR:更强的一致性方案。

主从延迟是常见问题,原因可能是从库硬件差、SQL 线程单线程回放(后期有并行复制)、大事务等。


14. 什么是死锁?怎么处理?

口述版:

死锁是两个或多个事务互相持有对方需要的锁,形成环形等待。

InnoDB 有死锁检测机制,发现死锁后会回滚代价较小的那个事务,让另一个继续。

减少死锁的方法:

  1. 按相同顺序访问资源(比如都按主键顺序更新)。
  2. 尽量短事务,减少锁持有时间。
  3. 为常查询和更新的条件加合适索引,减少锁范围。
  4. 业务层做好重试机制。

排查用 SHOW ENGINE INNODB STATUS 可以看到最近的死锁日志。


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

口述版:

在 InnoDB 下:

  • count(*) 和 count(1) 几乎一样,都会统计所有行,优化器一般会选最小的索引来扫描。
  • count(字段) 会忽略该字段为 NULL 的行,语义不同。

MyISAM 对 count(*) 有优化(直接存了值),InnoDB 没有,所以大数据量下 count(*) 还是比较慢,需要的话可以考虑缓存计数或汇总表。


面试加分点

  1. 能结合项目讲:你们怎么排查慢 SQL、有没有遇到死锁、主从延迟怎么处理。
  2. 清楚 MVCC、redo/undo/binlog 的作用和关系。
  3. 会看 EXPLAIN,能说出优化思路。
  4. 知道覆盖索引、最左前缀、间隙锁这些实战概念。
  5. 对分库分表、读写分离有基本了解(可和之前分表经验结合)。

快速记忆框架:

MySQL 面试核心围绕:

存储引擎 → 索引(B+Tree、最左前缀、覆盖索引)→ 事务与隔离级别 → MVCC 与日志 → 锁机制 → 慢查询优化 → 主从复制

把每块原理和常见问题说清楚,再结合自己项目经验,基本就能应对大多数面试。