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+ 树的优势。
为什么不是哈希?¶
哈希适合:
- 等值查询
但它不擅长:
- 范围查询
- 排序
- 最左匹配后的连续扫描
而数据库索引特别常见的需求恰恰是:
>、<、betweenorder 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 做热路径加速。
复习自查¶
- MySQL 索引为什么能加速查询?
- 为什么是 B+ 树,不是红黑树/哈希?
- 什么是聚簇索引、回表、覆盖索引?
- ACID 和隔离级别怎么理解?
- 幻读和 MVCC 是什么关系?
- Redis 为什么快?
- 单线程为什么还能高吞吐?
- 缓存穿透、击穿、雪崩怎么区分与治理?
- Redis 持久化怎么选?
- MySQL 和 Redis 如何配合?
小结¶
数据库和缓存题的关键,不是背名词,而是理解它们分别在解决什么问题。MySQL 通过索引、事务、MVCC 提供结构化数据的查询能力和一致性保障,核心矛盾是查询效率与写入维护成本;Redis 通过内存模型和单线程事件循环把延迟压到极低,核心矛盾是持久化与性能、容量与成本。
进阶建议¶
如果你想把这一章练到不怕追问,至少做到:
- 能说清 B+ 树为什么适合数据库
- 能解释回表和覆盖索引的性能意义
- 能讲清事务隔离级别的权衡
- 能说清 Redis 快在什么地方,也慢在什么地方
- 能把穿透、击穿、雪崩和治理方法真正区分开
- 能讲清 MySQL 和 Redis 的协作边界
做到这里,这一章就不再只是八股,而会开始像真正的后端基础能力。