📖 本篇简介:真实面试不是一问一答,而是"追问链"——你答了一层,面试官顺着往下钻三层,直到你答不上来。本篇精选 20 条最高频的追问链,每条按"题干 → 初答 → 追问1 → 追问2 → 追问3"链式拆解,帮你提前把每一层都准备好。前置知识:全系列前 39 章;本系列示例基于 MySQL 8.0。
目录
- 1 怎么应对追问链
- 题目 1:索引追问链
- 题目 2:事务追问链
- 题目 3:锁追问链
- 题目 4:主从复制追问链
- 题目 5:分库分表追问链
- 题目 6:慢SQL追问链
- 题目 7:读写分离追问链
- 题目 8:分布式ID追问链
- 题目 9:扣库存追问链
- 题目 10:缓存一致性追问链
- 题目 11:大事务追问链
- 题目 12:Join追问链
- 题目 13:自增主键追问链
- 题目 14:redo log追问链
- 题目 15:count追问链
- 题目 16:索引失效追问链
- 题目 17:死锁追问链
- 题目 18:在线DDL追问链
- 题目 19:连接池追问链
- 题目 20:扩容迁移追问链
- 速查速记
- 常见坑与注意事项
- 复习清单
- 练习题
1 怎么应对追问链
面试官追问本质是验证你是真懂还是背了。应对三原则:
1. 第一层给结论,别绕;
2. 被追问就往下讲机制,讲不清就诚实说"这块我理解到 X 层,再深没实践过";
3. 别硬撑——不知道比瞎编强。
| 层次 | 面试官意图 | 你的答法 |
|---|---|---|
| 第1层 | 会不会 | 结论 + 关键词 |
| 第2层 | 懂不懂机制 | 原理 + 流程 |
| 第3层 | 有没有实践 | 场景 + 权衡 |
下面 20 条追问链覆盖了面试最高频的"三连问"主题。建议复习方式:先遮住答案自己说一遍,再对照补漏;尤其注意每条链的第 3 层——那是区分"背过"和"做过"的分水岭。答到第 2 层合格,答到第 3 层加分。
| 信号 | 说明 |
|---|---|
| 面试官开始追问 | 你第一层答到点了,他在往下挖 |
| 连续两问"为什么" | 在验证你是真懂还是背 |
| 问"线上怎么处理" | 考你的工程实践,别只讲原理 |
| 你答不上 | 大方说"这块没深入实践",别硬编 |
题目 1:索引追问链
考点
- 索引结构;聚簇/二级;页分裂。
参考答案
题干:MySQL 为什么用 B+ 树做索引?
初答:B+ 树矮胖(3~4 层存千万行)、叶子双向链表适合范围查询、按页读写 IO 少。
追问
- 追问1:聚簇索引和二级索引有什么区别?答:聚簇索引叶子存整行,二级索引叶子存主键,查二级要回表。
- 追问2:回表什么代价?怎么避免?答:多一次 B+ 树查找+随机 IO;覆盖索引把查询列包进索引免回表。
- 追问3:那主键为什么推荐自增?答:自增顺序插尾部不页分裂;UUID 随机插入频繁页分裂、碎片。
题目 2:事务追问链
考点
- ACID;隔离级别;MVCC。
参考答案
题干:事务的 ACID 怎么实现?
初答:原子性靠 undo、持久性靠 redo、隔离性靠锁+MVCC、一致性是目标。
追问
- 追问1:RR 怎么实现可重复读?答:第一次 SELECT 生成 ReadView,之后复用,沿 undo 版本链找可见版本。
- 追问2:那幻读怎么防?答:快照读 MVCC 防;当前读(for update)靠临键锁锁间隙。
- 追问3:RC 和 RR 在实现上差在哪?答:ReadView 生成时机——RC 每次 SELECT 都新建,RR 只第一次建。
题目 3:锁追问链
考点
- 行锁;间隙锁;死锁。
参考答案
题干:MySQL 行锁什么时候加?
初答:InnoDB 行锁加在索引记录上,当前读/更新时加,UPDATE 没命中索引会退化为锁全表。
追问
- 追问1:除了记录锁还有什么锁?答:间隙锁(锁间隙防插入)、临键锁(记录+间隙)。
- 追问2:间隙锁什么时候触发?答:RR 隔离级别下范围查询/当前读;RC 没有间隙锁。
- 追问3:怎么排查死锁?答:
SHOW ENGINE INNODB STATUS看死锁日志,找两个事务互相等的锁。
-- 锁等待与死锁排查命令,本示例基于 MySQL 8.0
SHOW ENGINE INNODB STATUS; -- 重点看 LATEST DETECTED DEADLOCK 段
SELECT * FROM information_schema.INNODB_TRX; -- 当前所有事务,找最老事务
SELECT * FROM performance_schema.data_lock_waits; -- 锁等待关系:谁在等谁
题目 4:主从复制追问链
考点
- 复制原理;延迟;脑裂。
参考答案
题干:主从复制原理是什么?
初答:主库写 binlog,从库 IO 线程拉到 relay log,SQL 线程重放。
追问
- 追问1:为什么会主从延迟?答:单线程重放、大事务、从库性能差;8.0 并行复制缓解。
- 追问2:延迟了业务读到旧数据怎么办?答:写后读走主库,或半同步复制。
- 追问3:主从切换怎么防脑裂?答:同一时间只允许一个写, fencing/仲裁,老主降级为从。
题目 5:分库分表追问链
考点
- 为什么拆;怎么拆;扩容。
参考答案
题干:什么时候要分库分表?
初答:单表千万级、单库 CPU/IO 到瓶颈,纵向升配不划算。
追问
- 追问1:分片键怎么选?答:高离散、高频查询列,如 user_id;避免低基数列和时间片。
- 追问2:分了之后跨库查询怎么办?答:业务规避,复杂检索走 ES,避免跨库 Join 和深分页。
- 追问3:后续要从 8 库扩到 16 库怎么做?答:双写迁移 + binlog 追平 + 校验 + 灰度切流。
题目 6:慢SQL追问链
考点
- 定位;EXPLAIN;改写。
参考答案
题干:线上一条 SQL 很慢怎么排查?
初答:开慢日志,pt-query-digest 找 Top,EXPLAIN 看 type/key/rows。
追问
- 追问1:EXPLAIN 里 type=ALL 说明什么?答:全表扫描,where 条件没用到索引。
- 追问2:加了索引为什么还全表扫?答:函数包裹列、隐式转换、OR 无索引列、优化器算过账放弃。
- 追问3:深分页
LIMIT 100000,20慢怎么优化?答:游标分页或延迟关联,先取主键再回表。
-- 慢SQL定位:EXPLAIN 重点字段,本示例基于 MySQL 8.0
EXPLAIN SELECT * FROM t_order WHERE user_id = 1001 ORDER BY created_at DESC LIMIT 20;
-- type=ALL 全表扫(差)
-- key=NULL 没走索引
-- rows 越大扫的越多
-- Extra=Using filesort 额外排序,要警惕
题目 7:读写分离追问链
考点
- 主从延迟;一致性读。
参考答案
题干:读写分离怎么保证一致性读?
初答:写后读强制走主库,关键读(支付/余额)走主。
追问
- 追问1:哪些读必须走主?答:写后立刻读、资金强一致读。
- 追问2:全走主库主库扛得住?答:扛不住,只对必须强一致的读走主,其余走从+缓存。
- 追问3:从库延迟大了怎么自动切?答:监控 Seconds_Behind_Master,超阈值把读切回主。
题目 8:分布式ID追问链
考点
- 为什么;方案;时钟。
参考答案
题干:分库分表后全局唯一 ID 怎么做?
初答:号段模式或雪花算法,趋势递增。
追问
- 追问1:为什么不用 UUID?答:UUID 随机导致 B+ 树页分裂,写入性能差。
- 追问2:雪花算法有什么问题?答:时钟回拨。
- 追问3:时钟回拨怎么办?答:记录上次时间戳,回拨超阈值报错或等待,号段兜底。
题目 9:扣库存追问链
考点
- 超卖;原子;热点。
参考答案
题干:秒杀怎么防超卖?
初答:一条 SQL 原子扣减 WHERE stock>0,别先查后扣。
追问
- 追问1:为什么先查后扣会超卖?答:两个事务都读到有货再各自扣,写覆盖写。
- 追问2:热点商品一行扣太慢怎么办?答:库存分桶打散行锁,或前置 Redis 预扣。
- 追问3:Redis 预扣和 DB 不一致怎么办?答:下单成功异步扣 DB,失败回补 Redis,定时对账。
-- 原子扣库存:一条SQL完成判断+扣减,本示例基于 MySQL 8.0
UPDATE t_stock
SET stock_count = stock_count - 1, update_time = NOW()
WHERE sku_id = 10086 AND stock_count > 0; -- 受影响0行即售罄,杜绝超卖
题目 10:缓存一致性追问链
考点
- 先更库还是删缓存;删除失败。
参考答案
题干:缓存和数据库双写怎么保证一致?
初答:先更新数据库,再删除缓存(Cache-Aside)。
追问
- 追问1:为什么删缓存而不是更新缓存?答:更新可能并发覆盖,删除更简单安全。
- 追问2:删缓存失败怎么办?答:失败进重试队列,或订阅 binlog 异步删。
- 追问3:还有不一致窗口吗?答:有,加延迟双删 + TTL 兜底,强一致场景直接读库。
题目 11:大事务追问链
考点
- 危害;拆分;消息表。
参考答案
题干:大事务有什么危害?
初答:undo 膨胀、锁持有久、主从延迟、回滚慢。
追问
- 追问1:怎么拆?答:拆多个小事务,非核心操作异步化,批量按批提交。
- 追问2:拆了跨服务数据怎么一致?答:本地消息表,同事务写业务表+消息表,异步投递。
- 追问3:消费方重复消费怎么办?答:按业务唯一键幂等。
题目 12:Join追问链
考点
- 驱动表;N+1;嵌套循环。
参考答案
题干:慢 JOIN 怎么优化?
初答:小表驱动大表,被驱动表 Join 列要有索引。
追问
- 追问1:为什么小表驱动?答:被驱动表每次都查索引,小表驱动 = 查的次数少。
- 追问2:什么是 N+1 查询?答:先查 N 条主表,再循环 N 次查关联,应用层组装。
- 追问3:N+1 怎么解决?答:改成一条 JOIN,或批量
WHERE id IN (...)一次取。
题目 13:自增主键追问链
考点
- 为什么;页分裂;UUID。
参考答案
题干:为什么推荐自增主键?
初答:顺序插入尾部不页分裂,BIGINT 短二级索引省空间。
追问
- 追问1:页分裂怎么影响性能?答:插入要拆页搬数据,写入抖动、磁盘碎片。
- 追问2:UUID 做主键到底差在哪?答:随机插入位置导致频繁页分裂 + 二级索引存更长主键。
- 追问3:分库后自增还能用吗?答:不能全局唯一,要换趋势递增的分布式 ID。
题目 14:redo log追问链
考点
- WAL;checkpoint;两阶段。
参考答案
题干:redo log 是干嘛的?
初答:WAL,先写日志再刷数据页,crash 后重放保证已提交不丢。
追问
- 追问1:和 binlog 什么关系?答:两阶段提交,redo prepare → 写 binlog → redo commit。
- 追问2:为什么要两阶段?答:保证 redo 和 binlog 一致,否则主从数据脱节。
- 追问3:checkpoint 干嘛?答:把脏页刷盘推进 redo 可复用点,缩短 crash 恢复时间。
-- 两阶段提交时序,本示例基于 MySQL 8.0
1. 写 redo log 置 prepare
2. 写 binlog
3. redo log 置 commit
-- 崩溃恢复:prepare 态看 binlog 是否完整,完整则提交,否则回滚
题目 15:count追问链
考点
- InnoDB count 原理;优化。
参考答案
题干:count(*) 为什么慢?
初答:InnoDB 要数可见行(MVCC),不能像 MyISAM 直接读行数。
追问
- 追问1:为什么 InnoDB 不能存行数?答:多事务下每个会话可见的行不同,不能存一个固定值。
- 追问2:要精确总数怎么办?答:维护计数表,增删时同步更新;大数放 Redis。
- 追问3:只要大概数呢?答:
SHOW TABLE STATUS估算,或走最小索引。
题目 16:索引失效追问链
考点
- 函数包裹;隐式转换;左模糊。
参考答案
题干:索引为什么会失效?
初答:函数包裹列、隐式类型转换、左模糊、OR 无索引列、跳过前导列。
追问
- 追问1:隐式转换怎么导致失效?答:列是 VARCHAR 用数字比,转换作用在列上,索引列值被改无法定位。
- 追问2:
LIKE '%x%'怎么办?答:走覆盖索引或全文索引,否则全表扫。 - 追问3:怎么系统排查失效?答:EXPLAIN 看 type=ALL、key=NULL,对照 WHERE 写法逐条查。
题目 17:死锁追问链
考点
- 场景;排查;预防。
参考答案
题干:死锁怎么产生的?
初答:两个事务互相持有对方要的锁,循环等待。
追问
- 追问1:常见死锁场景?答:两个事务以不同顺序更新多行(A 先锁1再锁2,B 先锁2再锁1)。
- 追问2:怎么排查?答:
SHOW ENGINE INNODB STATUS看 LATEST DETECTED DEADLOCK。 - 追问3:怎么预防?答:固定访问顺序、短事务、降低锁粒度、合理索引避免锁全表。
-- 典型死锁:两事务以相反顺序更新同一两行,本示例基于 MySQL 8.0
-- 事务A: UPDATE acc SET bal=bal-1 WHERE id=1; UPDATE acc SET bal=bal-1 WHERE id=2;
-- 事务B: UPDATE acc SET bal=bal-1 WHERE id=2; UPDATE acc SET bal=bal-1 WHERE id=1;
-- A等B持有的2,B等A持有的1,循环等待 => 死锁
题目 18:在线DDL追问链
考点
- INPLACE;pt-osc;锁。
参考答案
题干:大表加索引怎么不锁表?
初答:ALGORITHM=INPLACE, LOCK=NONE,大表用 gh-ost/pt-osc。
追问
- 追问1:INPLACE 完全不锁写?答:写不锁,但元数据锁窗口仍在,别在高峰。
- 追问2:pt-osc 原理?答:建影子表 + 触发器同步增量 + 分批拷贝 + 原子换表。
- 追问3:8.0 有什么改进?答:原子 DDL,崩溃自动回滚不留残表。
题目 19:连接池追问链
考点
- 大小;为什么不是越大。
参考答案
题干:数据库连接池设多大合适?
初答:别贪大,参考"核数*2+磁盘数",通常几十到几百。
追问
- 追问1:为什么不是越大越好?答:连接多了 CPU 上下文切换、锁竞争反而重。
- 追问2:连接数打满一般什么原因?答:慢 SQL/长事务占着连接不放,不是真并发高。
- 追问3:怎么缓解?答:kill 长事务、缩短事务、连接池合理等待超时。
题目 20:扩容迁移追问链
考点
- 双写;校验;切流。
参考答案
题干:分库分表扩容怎么不停机迁移?
初答:双写 + 存量搬迁 + binlog 追平 + 校验 + 灰度切流。
追问
- 追问1:双写期间一边失败怎么办?答:以一边为准,异步补偿对账,不让两分支不一致。
- 追问2:怎么确认数据搬对了?答:count + checksum 抽样/全量比对。
- 追问3:怎么切最稳?答:先切读流量灰度,观察无误再切写,留回滚开关。
速查速记
| 追问链 | 三层核心 |
|---|---|
| 索引 | B+树 → 回表 → 自增主键 |
| 事务 | ACID → ReadView → 临键锁 |
| 锁 | 行锁 → 间隙锁 → 死锁排查 |
| 主从 | 复制 → 延迟 → 脑裂 |
| 分库分表 | 分片键 → 跨库 → 扩容 |
| 扣库存 | 原子 → 热点分桶 → Redis |
| 缓存 | 先更库 → 删缓存失败 → 延迟双删 |
| 扩容 | 双写 → 校验 → 灰度切流 |
常见坑与注意事项
- 坑 1:只会第一层不会第二层——背了结论但讲不清机制,一问"为什么"就露馅;
- 坑 2:被追问时硬编——不懂就说"这块我理解到 X,再深没实践过",别瞎编;
- 坑 3:把并列问题答成二选一——如"缓存更新还是删除"要讲清为什么删而不是更;
- 坑 4:忽略权衡——每个方案都有代价,答了方案要带一句"代价是……";
- 坑 5:答架构题不给兜底——只说主流程不说失败/对账/回滚,工程上不完整;
- 注意:追问链为公开高频面试套路,答案为通用原理与工程实践。
复习清单
| 追问链 | 第一层 | 第三层(拉开差距) |
|---|---|---|
| 索引 | B+树好 | 自增主键为什么 |
| 事务 | ACID | ReadView 时机 |
| 锁 | 行锁 | 死锁怎么查 |
| 主从 | 三线程 | 脑裂怎么防 |
| 分库分表 | 分片键 | 扩容怎么迁 |
| 慢SQL | EXPLAIN | 深分页优化 |
| 缓存 | 先更库 | 删除失败兜底 |
| count | 为什么慢 | 计数表维护 |
| 死锁 | 循环等待 | 怎么预防 |
| 连接池 | 别贪大 | 打满根因 |
练习题
练 1:面试官问"为什么用 B+ 树",你怎么往下接住他的三连追问?
<details>
<summary>参考答案</summary>
第一层:B+ 树矮胖、叶子链表、IO 少。追问1 聚簇 vs 二级:叶子存整行 vs 存主键,二级要回表。追问2 回表代价:多一次随机 IO,覆盖索引免回表。追问3 主键设计:所以推荐自增(顺序插尾部不页分裂),UUID 随机会页分裂。
</details>
练 2:面试官问"缓存和数据库一致性",接着追问"删缓存失败怎么办""还有窗口吗",你怎么答?
<details>
<summary>参考答案</summary>
初答:先更库再删缓存。追问1 删失败:失败进重试队列,或订阅 binlog(Canal)异步删。追问2 仍有窗口(读旧库回填旧缓存):加延迟双删 + TTL 兜底;强一致场景直接读库。
</details>
下一篇预告:第 41 章 安装与配置踩坑
文章回复
0 条公开回复