跳转至

01. 数据库与缓存:MySQL、Redis 面试八股(深挖版)

数据库和缓存题特别容易被答成"名词清单":

  • MySQL 用 B+ 树
  • Redis 基于内存所以快
  • 有缓存穿透、击穿、雪崩
  • 事务有 ACID,隔离级别有四种

这种答法在真正的后端面试里通常不够。因为面试官真正关心的是:

  • 为什么是这个设计,不是别的设计?
  • 这个机制解决了什么问题,又带来什么代价?
  • 项目里什么时候会踩坑?

这一章先把 MySQL 和 Redis 的高频基础题拉到"能继续被追问"的深度。


先把这一章的知识骨架搭起来

MySQL 和 Redis 放在一起问,核心不是让你背两个产品的特性,而是看你能不能说明 持久化存储和内存缓存分别解决什么问题,以及它们如何协作。MySQL 更强调持久化、一致性、复杂查询;Redis 更强调低延迟、简单结构、热点访问和临时状态承载。

因此这一章最好沿着“为什么需要缓存—缓存能解决什么—缓存又带来哪些一致性和可用性问题”的主线去读。MySQL 部分要知道数据如何落盘、索引如何支持查询;Redis 部分要知道单线程事件循环、内存结构、过期淘汰和缓存场景;而 MySQL + Redis 组合部分,则要重点理解穿透、击穿、雪崩、双写一致性这些工程问题。

这才是后端面试真正想听到的层次。

进入问答前,先把最小前置知识补齐

MySQL 和 Redis 被放在同一章,最重要的不是产品对比,而是要先理解一条后端数据链:请求进来后,哪些数据应该优先查缓存,查不到时怎样回源数据库,写入时怎样避免数据库和缓存之间出现长期不一致。

所以这章最好先带着"数据库负责真相、缓存负责热点"这个认识去读。后面的索引、事务、穿透、击穿、雪崩、本质上都在服务这条主线。


1. MySQL 索引为什么能加速查询?

标准回答

因为索引能减少全表扫描,通过更高效的数据结构(如 B+ 树)快速定位目标记录或缩小扫描范围。

更深入的理解

索引的本质不是"让查询 magically 变快",而是:

  • 提前按某种规则组织数据访问路径
  • 让查询少扫很多不必要的数据页

如果没有索引,很多查询只能:

  • 从头到尾一行一行看
  • 或扫描大量无关页

而有索引后,数据库可以:

  • 快速定位起点
  • 顺着有序结构继续扫范围
  • 减少磁盘 / buffer pool 的无效访问

面试高分点

索引的价值不是只在"查得快",还在于它能减少 IO 范围和无效扫描;但它并不是免费午餐,会增加写入维护成本和空间成本。


2. 为什么 MySQL 常用 B+ 树而不是红黑树或哈希?

标准回答

B+ 树适合磁盘/页式存储,层高低、磁盘 IO 次数少,叶子节点有序,适合范围查询。

为什么不是红黑树?

红黑树是二叉结构,树高通常更高。对于数据库这种页式存储来说:

  • 层高更高 → 访问路径更长
  • 磁盘/页 IO 次数更不友好

数据库更关心"少做几次页访问",这正是多叉 B+ 树的优势。

为什么不是哈希?

哈希适合:

  • 等值查询

但它不擅长:

  • 范围查询
  • 排序
  • 最左匹配后的连续扫描

而数据库索引特别常见的需求恰恰是:

  • ><between
  • order by
  • 范围过滤

为什么是 B+ 树,不是 B 树?

B+ 树通常:

  • 非叶子节点只存键,不存整行数据
  • 单页能容纳更多索引项
  • 树更矮
  • 叶子节点天然有序,范围扫描更方便

一句总结

MySQL 选 B+ 树,不是因为它"理论上最厉害",而是因为它最符合数据库页式存储、范围查询和排序扫描的需求。


3. 聚簇索引和非聚簇索引区别?

标准回答

在 InnoDB 中:

  • 聚簇索引叶子节点直接存整行数据
  • 二级索引叶子节点存主键值,需要再根据主键去聚簇索引拿整行

更深入地理解

这意味着:

  • 表数据本身就是按主键组织的
  • 主键访问通常路径最短
  • 二级索引查全字段时,可能需要两跳

对设计有什么影响?

  • 主键过大,会让很多二级索引也变胖,因为二级索引叶子里存的是主键
  • 主键设计不合理,会影响数据组织和插入局部性

面试高分点

InnoDB 里"表就是索引的一部分",这也是为什么主键设计会影响整张表的物理组织方式。


4. 什么是回表?

标准回答

通过二级索引定位到主键后,再去聚簇索引中查整行记录,这个过程叫回表。

为什么回表会慢?

因为它多了一次定位过程: 1. 先查二级索引 2. 再拿主键去聚簇索引取完整记录

如果命中很多行,回表成本会被放大。

什么时候容易被追问?

当面试官问:

  • 为什么某 SQL 走了索引还是慢?
  • explain 看起来用了 key,怎么性能还差?

很可能就是在等你提"回表成本"。


5. 什么是覆盖索引?

标准回答

查询所需字段都能从索引中直接拿到,不需要回表,这叫覆盖索引。

为什么覆盖索引经常很有价值?

因为它减少了一次访问主表/聚簇索引的过程。

特别是在:

  • 命中行较多
  • 查询非常频繁
  • 热路径 SQL

时,覆盖索引收益通常很明显。

但不要走极端

不是所有查询都应该为了覆盖索引去疯狂加字段,否则会带来:

  • 索引膨胀
  • 写入放大
  • 维护成本上升

一句总结

覆盖索引是典型的"空间换读取性能"手段,但不该被滥用。


6. 事务的 ACID 是什么?

标准回答

  • Atomicity:原子性
  • Consistency:一致性
  • Isolation:隔离性
  • Durability:持久性

不要只背缩写

面试更看重你能不能把它们落到数据库语境里:

原子性

事务中的操作要么都成功,要么都失败回滚。

一致性

事务执行前后,数据库从一个合法状态转到另一个合法状态。

隔离性

多个事务并发执行时,不应该彼此随意看见中间脏状态。

持久性

事务一旦提交,结果就应在系统故障后尽量能恢复。

高分点

一致性不是数据库单方面"自动保你业务永远正确",它依赖应用约束、事务控制、索引和数据规则共同保证。


7. 事务隔离级别有哪些?

四种隔离级别

  • Read Uncommitted
  • Read Committed
  • Repeatable Read
  • Serializable

常见问题对应

  • 脏读
  • 不可重复读
  • 幻读

不要机械背表格

更关键的是理解隔离级别是在权衡:

  • 并发性能
  • 锁冲突
  • 一致性强度

隔离越强,通常:

  • 并发越受限
  • 实现成本越高
  • 吞吐可能越低

一句总结

事务隔离级别不是"越高越好",而是并发性能和一致性需求之间的权衡。


8. MySQL 的幻读怎么理解?

标准回答

同一事务中前后两次范围查询,第二次看到了第一次不存在的新行,这种现象称为幻读。

为什么叫"幻"读?

因为不是原有某一行变了,而是:

  • 范围里突然"冒出"了新的记录

这和不可重复读的区别在于:

  • 不可重复读更像"同一行值变了"
  • 幻读更像"结果集成员变了"

InnoDB 常见回答

InnoDB 在可重复读级别下会结合:

  • MVCC
  • Next-Key Lock

来降低或避免很多幻读场景。


9. 什么是 MVCC?

标准回答

MVCC(多版本并发控制)通过保存数据多个版本,使读操作无需加锁即可读取适当版本,提高并发性能。

它解决了什么问题?

如果所有读都和写互相强阻塞:

  • 并发会很差
  • 热点表会很卡

MVCC 的核心思想是:

  • 读不一定非要读"当前最新版"
  • 可以读一个对当前事务可见的历史版本

这样就能让很多读操作不必和写操作直接冲突。

面试高分点

MVCC 的本质是"用多版本换读写并发",不是"数据库 magically 不加锁了"。写冲突、当前读等场景照样有锁问题。


10. Redis 为什么快?

标准回答

  • 基于内存
  • 数据结构高效
  • 单线程事件模型避免大量锁竞争(传统核心模型)
  • IO 多路复用

更完整的理解

Redis 快不只是"因为在内存里",还因为它在工程上刻意追求:

  • 数据路径短
  • 命令模型简单
  • 单线程避免复杂锁竞争
  • 常用结构针对典型场景优化

但也别神化

Redis 快并不意味着:

  • 所有命令都永远快
  • 大 key / 大范围操作没问题
  • 单线程就没有阻塞风险

如果操作本身很重,主线程照样可能被拖慢。


11. Redis 为什么用单线程还这么快?

标准回答

因为 Redis 的瓶颈很多时候不在 CPU,而在内存访问和网络 IO;单线程模型避免了锁竞争和上下文切换,配合高效事件循环,吞吐依然很高。

更深入一点

Redis 选择单线程核心执行模型,本质是在做一个工程取舍:

  • 放弃复杂共享内存并发
  • 换取实现简单、延迟稳定、锁开销低

这不代表 Redis "只有一个线程"

实际系统中还可能有:

  • IO 线程
  • 后台持久化线程
  • 异步释放资源线程

但命令执行核心路径长期以来强调的是:

  • 尽量串行
  • 尽量减少锁竞争

一句总结

Redis 单线程快,不是违反常识,而是因为它把典型瓶颈避开了,同时把执行路径做得很短。


12. Redis 常见数据结构有哪些?

  • String
  • List
  • Hash
  • Set
  • ZSet
  • Bitmap
  • HyperLogLog
  • Stream

面试不要只背名字

更重要的是知道它们分别适合:

  • String:缓存对象、计数器、分布式锁基础
  • Hash:对象字段聚合存储
  • Set:去重、共同好友、标签集合
  • ZSet:排行榜、延时任务、按分值排序
  • Stream:消息流、消费组

高分点

Redis 面试不是考你会不会背类型,而是看你能不能把"数据结构选择"和"业务场景"连起来。


13. 缓存穿透、击穿、雪崩分别是什么?

缓存穿透

查询不存在的数据,请求穿过缓存直打数据库。

解决思路

  • 布隆过滤器
  • 缓存空值
  • 非法请求拦截

缓存击穿

热点 key 失效瞬间,大量请求同时打到数据库。

解决思路

  • 互斥锁 / singleflight
  • 逻辑过期
  • 热点永不过期 + 后台刷新

缓存雪崩

大量 key 同时失效,数据库压力骤增。

解决思路

  • 过期时间加随机偏移
  • 多级缓存
  • 限流、降级
  • 预热热点数据

一句总结

这三类问题本质上都是"缓存没兜住流量",只是失守的原因不同:穿透是查不存在,击穿是热点瞬时失效,雪崩是大面积同时失效。


14. Redis 持久化方式有哪些?

RDB

定期生成快照。

优点

  • 恢复快
  • 文件紧凑
  • 对读多写少场景友好

缺点

  • 但可能丢最近一次快照之后的数据

AOF

记录写命令日志。

优点

  • 数据更完整
  • 更适合降低丢数据窗口

缺点

  • 但文件更大、恢复速度可能更慢

选择取决于

  • 恢复速度要求
  • 数据完整性
  • 运维成本

一句总结

Redis 持久化不是"开不开"的简单问题,而是在性能、恢复速度和数据丢失窗口之间做权衡。


15. MySQL 和 Redis 各适合做什么?

MySQL 擅长

  • 强一致核心数据
  • 复杂查询
  • 事务保障
  • 关系建模

Redis 擅长

  • 低延迟缓存
  • 计数器、排行榜、分布式协调
  • 热点数据快速读写

面试高分点

不要说成"Redis 能替代 MySQL"。更准确的说法是:

两者通常不是替代关系,而是分工协作:MySQL 保底层事实数据,Redis 做热路径加速。


复习自查

  1. MySQL 索引为什么能加速查询?
  2. 为什么是 B+ 树,不是红黑树/哈希?
  3. 什么是聚簇索引、回表、覆盖索引?
  4. ACID 和隔离级别怎么理解?
  5. 幻读和 MVCC 是什么关系?
  6. Redis 为什么快?
  7. 单线程为什么还能高吞吐?
  8. 缓存穿透、击穿、雪崩怎么区分与治理?
  9. Redis 持久化怎么选?
  10. MySQL 和 Redis 如何配合?

小结

数据库和缓存题的关键,不是背名词,而是理解它们分别在解决什么问题。MySQL 通过索引、事务、MVCC 提供结构化数据的查询能力和一致性保障,核心矛盾是查询效率与写入维护成本;Redis 通过内存模型和单线程事件循环把延迟压到极低,核心矛盾是持久化与性能、容量与成本。


进阶建议

如果你想把这一章练到不怕追问,至少做到:

  • 能说清 B+ 树为什么适合数据库
  • 能解释回表和覆盖索引的性能意义
  • 能讲清事务隔离级别的权衡
  • 能说清 Redis 快在什么地方,也慢在什么地方
  • 能把穿透、击穿、雪崩和治理方法真正区分开
  • 能讲清 MySQL 和 Redis 的协作边界

做到这里,这一章就不再只是八股,而会开始像真正的后端基础能力。