ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MySQL索引日志与崩溃恢复面试答题笔记

MySQL索引日志与崩溃恢复面试答题笔记

简介:面向备战后端开发面试的求职者与初中级 MySQL 使用者的知识点梳理文档,围绕常见高频考点做系统归纳。内容覆盖关系型与非关系型数据库的差异、一条 SQL 从连接器到执行器的完整执行流程、索引的引入原因与哈希表/有序数组/搜索树三类底层结构,并延伸到聚簇索引与二级索引的区别、MyISAM 与 InnoDB 的 B+ 树实现差异、普通索引与唯一索引的取舍、覆盖索引与索引下推、索引失效的典型写法,以及 change buffer、redo log 与 binlog 等日志机制要点。压缩包内仅 1 个 docx 文件,约 40KB,按问答条目组织,便于面试前速览与逐题自测。已有 252 人学习,适合用作查漏补缺的复习清单。

1. 一道「索引加了为什么还是慢」就能筛掉一半人

MySQL 面试题有个特点:题目看着都是八股,多问两层就分人。面试官问「索引为什么用 B+ 树」,背过的人能答出「减少随机 IO」;紧接着一句「这张表主键用自增还是 UUID,一次查询回表几次」,答不上来的立刻露底。这场面试真正考的不是你记住了多少条结论,而是能不能把「一条 SQL 从连接器走到引擎层」这条链路讲通——索引怎么选、日志怎么保证不丢、崩溃之后拿什么恢复。拿分的办法很朴素:每个考点都按「结论 → 原因 → 代价」三层说,别只丢一句结论就停。这份笔记适合已经背过一轮、想把答案说出层次的人,也适合进场前把高频题快速过一遍的人。

2. 索引底层与回表:B+ 树、聚簇索引、覆盖索引的三层答法

索引是数据库部分出镜率最高的模块,一轮技术面里往往有三分之一的时间耗在这儿。面试官的提问路径基本固定:先问「为什么要索引」,再问「用什么数据结构」,然后问「主键索引和普通索引差在哪」,最后落到「怎么用覆盖索引和索引下推把回表省掉」。这四问是一条递进的链,前一句答得含糊,后面的追问就会变成压力测试。所以准备的时候不要按题目背,要按这条链把每一环的因果关系理顺——每一步都要能说出「换一种做法会多付出什么」。

2.1 三种数据结构对比:哈希、有序数组、搜索树的取舍

索引的底层结构不是只有一种答案,面试官问「三种常见的数据结构」时,他想听的是你对场景的判断力,而不是名词罗列。哈希表用键值对定位,等值查询一次就能命中,复杂度是常数级,但它没有任何顺序,范围查询和排序都用不上,一旦要where id > 100就只能全表扫。有序数组正好相反,等值和范围查询都很快,二分查找足够优雅,可一旦要插入或删除,后面所有元素都得挪位置,更新成本高到只适合静态存储引擎——数据写完就不再改的场景。

搜索树这一类里,真正被数据库引擎广泛采用的是 N 叉树。原因不玄学:磁盘 IO 是按块读的,一次读进来的数据越多,同样的树高能覆盖的记录就越多,树就压得越矮。二叉树每个节点只有两个分叉,几百万行数据能把树撑到二十多层,每下一层就是一次磁盘访问;而 N 叉树一个节点塞下几百上千个键,三四层就能装下十亿级的数据。这背后是同一笔账:把随机 IO 换成顺序 IO,把「多次读小块」换成「一次读大块」。

结构等值查询范围查询更新成本典型场景
哈希表常数级,很快不支持,需全表扫低键值缓存、等值命中
有序数组二分,很快很快高,需整体挪动静态数据、只读表
N 叉树(B+ 树)按树高,稳定支持,叶节点链表串联中,页内与页间分裂磁盘存储引擎的通用索引

这张表建议直接记结论:等值查询谁都快,真正的分水岭在范围查询和更新成本上。答题时把这两列说透,比堆一堆复杂度符号有用得多。

2.2 InnoDB 为什么选 B+ 树而不是 B 树或哈希

这道题的标准答法有两层考虑。第一层是功能:InnoDB 要支撑的查询里有大量范围条件和排序,哈希索引在这类语句上完全帮不上忙,只能退化成全表扫描,性能优势直接归零。第二层是代价:CPU 把磁盘数据加载进内存所花的时间,远大于在内存里做比较的时间,所以索引结构的设计目标从来不是「比较次数最少」,而是「磁盘访问次数最少」。

B 树和 B+ 树的差别就在这一层。B 树的非叶子节点也存数据行,意味着每次经过一个非叶子节点,都要顺带把一条记录读进来,而这个记录未必是你要找的;更要命的是,非叶子节点存了数据,能放的分叉数就少了,树会变高,随机 IO 反而增加。B+ 树把所有数据行都赶到叶子节点,非叶子节点只留键值,一个页能塞下更多的分叉,树更矮;同时所有叶子节点用指针串成链表,范围扫描只要找到起点,顺着链表往后走就行,把顺序遍历中的随机 IO 压到最低。

面试里常被拿来验证你是否真懂的一个数字:InnoDB 一个整数字段索引,单个节点的分叉数 N 大约在 1200 左右。树高为 4 时,能存的记录数是 1200 的三次方,也就是十几亿的量级。考虑到根节点几乎常驻内存,第二层也有很大概率被缓存,一张十亿行的表按整型字段查一个值,实际落盘的访问次数往往只有三次左右。把这个估算过程说清楚,比背「B+ 树更矮」这句话有说服力得多。

2.3 聚簇索引与二级索引:一次回表多花几次 IO

按叶子节点存什么来分,InnoDB 的索引只有两类。主键索引的叶子节点存的是整行数据,所以它本身就叫聚簇索引——数据即索引,索引即数据,两者是同一个文件。非主键索引的叶子节点存的是主键值,官方叫二级索引。这个设计带来一个必然后果:通过二级索引查到主键之后,还得拿主键再走一遍主键索引才能拿到整行,这一步就是回表。

回表的代价要能算出来。走二级索引是一次 B+ 树查找,回表又是一次 B+ 树查找,等于一次查询付了两份磁盘访问的钱。如果一条 SQL 命中十万行,那就是十万次回表,优化器很可能干脆放弃索引直接全表扫——这也是为什么「明明建了索引却不走」的现场 frequently 出现在区分度低的列上。答题时把「回表次数 × 单次树高」这个乘法摆出来,面试官就知道你不是在背书。

MyISAM 的区别也要顺带说一句,这几乎是必考的对比点。MyISAM 的 B+ 树叶子节点存的是数据记录的物理地址,索引文件和数据文件是分开的两个文件,查到地址之后再去数据文件里取行。InnoDB 是索引组织表,MyISAM 是堆组织表,这个定性判断能一句话把区别说清。

-- 主键索引:叶子节点存整行,查 id 不需要回表 EXPLAIN SELECT * FROM t_user WHERE id = 100; -- 二级索引 idx_name:叶子节点只存主键 id,查其它列必然回表 EXPLAIN SELECT * FROM t_user WHERE name = 'A同学';

上面两条语句看EXPLAIN输出的key列即可:第一条走PRIMARY,第二条走idx_name。真正的差别藏在Extra列里——如果第二条的查询列改成只有id和name,Extra会出现Using index,说明这次没有回表。参数说明就一句:EXPLAIN的key告诉你用了哪个索引,Extra告诉你是否回表、是否用上了索引下推,这两个列是索引类问题的通用证据。

2.4 真题演练:覆盖索引和索引下推的完整答法

题目一:什么是覆盖索引,为什么它能提升性能?

答题思路分三步走。先给定义:一条查询语句所需要用到的列,全部都能在某个索引里拿到,不需要回到主键索引取整行,这个索引就覆盖了这次查询。再说原因:省掉的是回表那一次 B+ 树查找,命中行数越多,省下来的 IO 越可观,所以它是性价比最高的优化手段之一。最后补一句代价:覆盖索引本质是让索引变宽,联合索引的列越多,索引文件越大,写入维护成本越高,不能为了覆盖而无限加列。三步说完,这道题就从「背定义」变成了「会权衡」。

题目二:索引下推解决了什么问题?

这题的关键是说出「5.6 之前是怎么做的」。在没有索引下推的年代,联合索引(name, age)遇到where name like 'A%' and age = 18,引擎只能先用name把索引里匹配的记录找出来,然后一条条回表取整行,回到 Server 层才判断age是否等于 18,不符合的扔掉。也就是说,age这个条件在索引里明明有,却完全没被用上,白白回表了。索引下推做的事很直接:在遍历索引的过程中就把索引里包含的字段判断掉,不满足条件的记录根本不回表。

-- 联合索引 idx_name_age (name, age) -- 5.6 之前:按 name 前缀匹配后逐条回表,回到 Server 层才过滤 age -- 5.6 之后:在索引遍历阶段就用 age 过滤,减少回表次数 EXPLAIN SELECT * FROM t_user WHERE name LIKE 'A%' AND age = 18;

执行这条EXPLAIN,Extra列出现Using index condition就说明索引下推生效了。要注意它和覆盖索引不是一回事:覆盖索引是「不回表」,索引下推是「少回表」,前者靠索引列覆盖查询列,后者靠把条件下推到引擎层。面试时把这两个概念混着说,是最常见的失分点之一。

3. 索引失效与慢查询:六类失效写法加一套排查顺序

索引失效这类题,面试官通常不给标准答案,而是丢给你一条 SQL 让你判断走不走索引。他考的是你对「索引里存的是什么」这件事的理解——索引存的永远是字段的原始值,任何让比较值偏离原始值的操作,都会让 B+ 树没法定位。把这条原则记住,六类失效场景就不用死记,可以现场推出来。

3.1 六类索引失效写法逐条对照

第一类是左模糊和全模糊,like '%xx'和like '%xx%'。原因很直白:B+ 树是按前缀有序排列的,%xx开头意味着你不知道从哪个索引值开始找,只能全表扫。右模糊like 'xx%'是可以走索引的,这一点必须能区分开。第二类是在索引列上做函数运算或表达式计算,比如where id + 1 = 5、where DATE(create_time) = '2024-01-01',索引里存的是create_time原值,不是DATE()之后的日期,自然用不上。这类问题的解法是把函数挪到等号右边,变成对常量的计算。

第三类是隐式类型转换。字段是字符串类型,查询时传了个数字,MySQL 会把字段值转成数字再比较,等同于给字段套了个函数,索引直接失效。这个坑在实际项目里极常见,手机号、订单号这类字段尤其容易踩。第四类是OR连接的条件里有一个列没建索引,优化器会判定走索引加回表还不如直接全表扫,于是整体放弃索引。第五类是联合索引不满足最左前缀,跳过了第一列直接查第二列。第六类是优化器基于统计信息主动放弃索引,比如区分度太低、或者预估回表行数超过全表扫描的成本。

失效写法失效原因改法
like '%xx'/like '%xx%'前缀不确定,无法定位索引起点改成右模糊,或用其它字段先缩小范围
where DATE(col) = ?索引存原值,函数结果不在索引里改成范围条件col >= ? and col < ?
字符串列传数字隐式转换等于套了函数参数类型与字段类型保持一致
or中有一列无索引整体退化为全表扫给另一列补索引,或拆成两条语句
联合索引跳过首列违反最左前缀调整索引列顺序或补足前导列
区分度太低被优化器放弃预估回表成本高于全表扫换更高区分度的列建索引

这张表里的六行,建议按「条件写法 → 原因 → 改法」的顺序口述一遍,能顺下来基本就过关了。

3.2 字符串字段建索引的四种方案取舍

这题是典型的「没有标准答案,只看你会不会权衡」。给一个长字符串字段建索引,有四条路。第一条是直接给完整字段建索引,查询最快、能覆盖、能范围扫,代价是占空间,字段越长索引文件越大。第二条是前缀索引,只取前 N 个字符建索引,空间省下来了,但每次查询要多扫几次、并且用不了覆盖索引,因为索引里没有完整值,必须回表核对。

第三条是倒序存储再建前缀索引,专门用来对付「前缀区分度不够」的场景,比如一批 URL 前缀全都一样、只有尾部不同,正着取前几位全是重复值,倒过来取反而能区分开。第四条是额外建一个 hash 字段存哈希值并建索引,查询性能最稳定,等值查询一次命中,代价是要多存一列、多算一次哈希,而且和倒序方案一样不支持范围扫描——哈希之后的大小顺序和原值毫无关系。

答题时的收尾句式可以固定:如果这个字段只做等值查询且很长,优先考虑 hash 列;如果还需要范围查询或排序,老老实实建完整索引;如果只是空间紧张且在尾部有区分度,前缀索引或倒序加前缀才是选项。把「要不要范围查询」当成决策的分水岭,答案就立住了。

3.3 真题演练:本来很快的 SQL 变慢,答题分四层

这是全篇最像真实工作场景的一道题。面试官问「原本执行很快的语句,突然慢得离谱,你怎么查」,他想听的是排查顺序,而不是「加索引」三个字。答题从大到小分四层。第一层,MySQL 本身被堵住了,系统或网络资源不够、连接数打满、磁盘 IO 到顶,这时候所有语句都慢,不是某一条的问题,先看监控排除整体因素。第二层,语句被锁住了,表锁、行锁、元数据锁都会让语句卡在等锁状态,用show processlist看状态是不是在等锁。第三层,索引没用对,该走索引的走了全表扫,用EXPLAIN看key和rows就能确认。第四层,索引走了,但回表次数太多,数据分布导致优化器选了个看起来合理实际很差的方案。

排查清楚之后,解决手段也是四种。一是用force index强制指定索引,先验证「换索引确实会快」这个判断,再决定后续动作——它适合临时定位问题,不适合长期写死在代码里。二是改写语句引导优化器,比如把order by b limit 1改成order by b, a limit 1,语义完全一致,但后者能让优化器意识到需要的那条记录在联合索引里唯一确定,从而选对索引。三是新建一个更合适的索引,或者把误用的索引删掉。四是确认这个索引本来就没必要存在,直接清理。

-- 怀疑优化器选错索引时,先用 force index 验证判断 SELECT * FROM t_order FORCE INDEX (idx_create_time) WHERE create_time > '2024-01-01' ORDER BY create_time LIMIT 1; -- 改写语句:让排序字段组合能唯一确定一条记录,引导优化器选索引 SELECT * FROM t_order ORDER BY create_time, id LIMIT 1;

两段代码的用途不一样。第一段里的FORCE INDEX (idx_create_time)是临时手段,执行计划被强制之后,对比EXPLAIN的rows变化,就能确认是不是选错索引导致的慢,确认完记得把这个 hint 撤掉。第二段是把排序键从单列扩成「单列 + 主键」,ORDER BY的语义在加了唯一键之后并没有变化,但优化器在估算时能识别出「顺着索引走一条就够」,从而避免排序或临时表。参数层面唯一需要注意的是LIMIT的数字会影响优化器对成本的估算,行数很大时两条语句的差异会更明显。

4. 日志与两阶段提交:redo log、binlog 的必答链路

日志这块是面试里最能拉开差距的部分,因为它同时考三件事:你知不知道有两种日志、知不知道它们各自属于哪一层、知不知道崩溃恢复时谁先谁后。绝大多数人卡在第三层,能说出「两阶段提交」四个字,但说不清 prepare 和 commit 之间为什么要夹一个 binlog。这一章的四个小节,就是按「各自是什么 → 谁能保证不丢 → 怎么配合 → 参数怎么调」这条链排的。

4.1 redo log 循环写与 crash-safe 能力

redo log 是 InnoDB 引擎层的物理日志,记录的是「在哪个数据页上做了什么修改」。它存在的理由是 WAL:更新语句如果每次都同步把数据页刷到磁盘,一次随机写要等很久,SQL 响应会慢到没法用;所以 InnoDB 先把修改写进内存页,同时把「改了什么」顺序写进 redo log,事务就可以返回了。真正把脏页刷回磁盘的动作,交给后台慢慢做。

它能保证不丢数据,关键在「循环写」这三个字。redo log 由固定数量的文件首尾相连组成,有两个指针:write pos是当前写到的位置,一边写一边后移,写到最后一个文件末尾就绕回开头;checkpoint是要擦除的位置,擦除之前必须保证对应的脏页已经刷到磁盘。两个指针之间是空着的可写区域,write pos追上checkpoint就表示日志满了,此时必须先推进checkpoint、把脏页刷盘,才能继续接受新的更新。

这个机制带来的能力叫 crash-safe:因为 redo log 里只保留「还没刷盘」的修改,已经落盘的部分会被循环覆盖掉,所以数据库重启之后,只要把 redo log 里的记录重放一遍,就能把内存里丢掉的数据补回来,而且不会重复应用已经落盘的修改。答题时一定要强调「只记录未刷盘的部分」,这是它和 binlog 最本质的分野。

4.2 binlog 三种格式与完整性的判断方式

binlog 是 Server 层的归档日志,和存储引擎无关,所有引擎写的变更都会记进来。它有两个用途:主从复制和数据恢复。恢复的做法是取某个时间范围内的 binlog 重放,把数据推到误操作之前的状态。

三种格式的取舍要能说清。statement记录的是原始 SQL 语句,日志量小、IO 省,但有些语句在主库和备库上执行结果可能不一致,比如带now()、随机数的语句,所以还必须额外记录执行时的上下文信息。row格式不记 SQL,只记哪一行被改成了什么,细节完整,存储过程、函数、触发器的复制都不会出问题,代价是日志量可能非常大,一条批量更新能写出成百上千行记录。mixed是两者的结合,普通语句用statement,遇到可能不一致的函数就自动切row,由 MySQL 按每条语句具体判断。

还有一个常被忽略的小题:MySQL 怎么判断一个事务的 binlog 是完整的?答案是看格式末尾的标志——statement格式的 binlog 最后会有COMMIT,row格式的最后会有一个XID event。这个细节之所以重要,是因为崩溃恢复时要靠它判断 binlog 有没有写全,写全了才能拿去恢复。

格式记录内容优点缺点
statement原始 SQL 语句日志量小,IO 省部分函数与上下文相关,主备可能不一致
row每行数据的变更前后值细节完整,复制可靠日志量大,批量更新时膨胀明显
mixed按语句自动选择兼顾体积与可靠性行为依赖具体语句,排查时需确认格式

答题时给一句收尾:现在生产环境多数直接用row,因为可靠性优先于日志体积,磁盘便宜但数据不一致的代价很贵。

4.3 两阶段提交与崩溃恢复的三种情况

先说为什么需要两阶段提交。redo log 和 binlog 是两个独立的日志,一个在引擎层、一个在 Server 层,如果各写各的,中间崩溃就会出现两种不一致:先写 redo log 再崩溃,binlog 里少了这次更新,拿备份恢复时数据对不上;先写 binlog 再崩溃,redo log 没写成功,事务实际没生效,但 binlog 已经被备库拿去重放了,同样对不上。两阶段提交就是把 redo log 的写入拆成 prepare 和 commit 两步,中间插入 binlog 的写入,让两个日志在逻辑上保持一致。

-- 一条更新语句在引擎层的日志顺序(伪代码示意,非可直接执行) -- 1. 引擎层把 id=666 这行的 a 改成 1,修改内存页 -- 2. 写 redo log,标记为 prepare 状态 -- 3. Server 层写 binlog -- 4. 写 redo log,标记为 commit 状态 UPDATE T SET a = 1 WHERE id = 666;

这四步的顺序不能乱。恢复时的判断规则也只有一条:如果 redo log 里的事务是 commit 状态,说明 binlog 一定也写成功了,直接恢复;如果 redo log 里是 prepare 状态,就要去查对应的 binlog 事务是否完整,完整就提交,不完整就回滚。这条规则是整个日志体系的题眼,能把它讲清楚,两阶段提交这一块基本不会失分。

崩溃后未刷盘数据的恢复,按三种情况推演最清晰:

场景redo log 状态binlog 状态恢复动作
一已 fsync 但未 commit未 fsync 到磁盘这部分数据丢失,事务回滚
二已 fsync 未 commit已 fsync 到磁盘先从 binlog 恢复 redo log,再由 redo log 恢复数据
三已 fsync 且已 commit已 fsync 到磁盘直接从 redo log 恢复

三种情况的差别就落在「谁先落盘」上,答题时按表格顺序说一遍,比模糊地说「两阶段提交保证一致性」要扎实得多。顺便提一句 change buffer:它缓存的是对非唯一二级索引页的更新,这些更新同样要经过 redo log 保护,否则崩溃之后 change buffer 里的内容就无从恢复。

4.4 innodb_flush_log_at_trx_commit 三个取值怎么答

redo log 的写入分两段:先写内存里的redo log buffer,再经由操作系统的缓冲区落到磁盘上的redo log file。这个「先写日志再写磁盘」的动作就是 WAL。中间的落盘时机由innodb_flush_log_at_trx_commit控制,三个取值对应三档可靠性。

取 0 是延迟写,事务提交时压根不碰 OS 缓冲区,靠后台每秒刷一次,性能最好,但 MySQL 进程一挂就可能丢一秒的数据。取 1 是实时写实时刷,每次提交都把日志写进 OS 缓冲区并立刻落盘,最安全,代价是每次提交都要等一次磁盘。取 2 是实时写延迟刷,提交时写进 OS 缓冲区,每秒再落盘,MySQL 进程挂了不丢,但整机断电可能丢一秒。

答题时不要只说「1 最安全」,要补一句取舍:金融类业务通常锁死为 1,日志类、可容忍少量丢失的业务可以放宽到 2 换吞吐。面试官听到「按业务容忍度选」,就知道你理解的是工程而不是参数表。

5. 易错点:这五处答偏了,前面背得再熟也白搭

有些题不是不会,是答的方向偏了。下面五处是高频翻车点,每一条都按「错在哪 → 为什么错 → 怎么答对」拆开,建议对着自己的答案逐条核对。

5.1 内存表能不能上生产:只答「快」就掉坑了

错在哪:被问到 MEMORY 引擎时,很多人只答「数据放内存里,查询快,适合临时表」,完全不提它的问题,面试官会认为你没考虑过生产环境。

为什么错:MEMORY 引擎有两个硬伤。一是锁粒度,它用的是表级锁,并发写入会互相阻塞,跟 InnoDB 的行锁不是一个量级。二是数据不持久,重启就清空——这一点在单机上只是「丢数据」,在主备架构上会变成事故:备库重启后内存表变空,主备同步线程可能直接停掉;如果是双主架构,主库的内存表数据还可能被备库的空表覆盖掉。

怎么答对:先说结论「不建议在生产环境用内存表」,再给两条理由(锁粒度、持久化),最后给替代方案——需要缓存就用外部缓存组件,需要临时结果集就用会话级临时表。临时表这块顺带能加分:它只对当前会话可见,可以和普通表重名,show tables不显示,会话退出自动删除,天然避免了并发重名问题,适合放复杂计算的中间结果。

5.2 主备同步谁说了算:位点方向说反了

错在哪:被问「主备怎么同步」,答成「主库主动把日志推给备库,从某个位置开始」,把建立关系和维持关系两个阶段混成一件事。

为什么错:主备关系刚建立时,是备库指定起点——备库说「我要从 binlog 文件 A 的位置 P 开始同步」,主库按这个位点往后发。关系建立之后,主动权才转到主库:主库有新的日志就主动发给备库。这两个阶段的发起方是反的。

怎么答对:分开说。「建立阶段由备库指定位点,运行阶段由主库推送」,一句话把两个阶段切开。切换到新主库时,客户端读写都指向新的主节点,原主变成备库,把更新同步过来即可。能把这个方向说清,说明你真读过流程而不是背了名词。

5.3 误删数据:恢复顺序反了等于二次事故

错在哪:答「用 binlog 重放恢复」就结束了,不说恢复到哪儿、不说前提条件。

为什么错:直接把 binlog 重放到线上库,等于在故障库上再执行一遍所有变更,一旦中间有别的写入,数据会彻底乱掉。而且 DML 和 DDL 的恢复路径完全不同:delete、update这类误操作可以靠解析 binlog 再反转(把 delete 反转成 insert、update 前后镜像对调),但这要求binlog_format=row且binlog_row_image=full,格式不对根本反转不了;truncate、drop这类 DDL 在 binlog 里只记语句不记镜像,只能靠全量备份加 binlog 重放,数据量大时恢复时间会很长。

怎么答对:按「预防 → 恢复」两层答。预防包括权限控制、操作规范、SQL 审计、延迟备库、定期全量与增量备份。恢复时先找到误操作前最近的时间点,把 binlog 重放到一个临时实例,在临时实例上确认数据无误,再导回线上。顺序不能反,这句是加分项。

5.4 大表查询不爆内存:只答「边读边发」不完整

错在哪:被问「几千万行的查询为什么不会把内存打爆」,答「因为 MySQL 是边读边发」,然后停住。

为什么错:边读边发只解释了 Server 层不保存完整结果集——取数据和发数据通过一个缓冲区来回倒,客户端读得慢只会堵住查询,不会撑爆内存。但引擎层还有一半答案没说到:InnoDB 的Buffer Pool有淘汰策略,用的是改进的 LRU,按 5:3 把链表分成 young 区和 old 区,全表扫描读进来的冷数据页只会进 old 区,不会把热数据挤出去,所以一次大范围扫描对缓存命中率的影响是可控的。

怎么答对:两层都说完。Server 层靠边读边发不攒结果集,引擎层靠改进 LRU 控制冷数据污染。再补一句现象:客户端接收慢会导致这条语句执行时间变长,看起来像「卡住」,其实是发送端被反压,这个现象能说明你是真理解而不是背的。

5.5 change buffer 与唯一索引:结论记住了理由没记住

错在哪:答「建议用普通索引而不是唯一索引」,理由是「唯一索引性能差」,说不清差在哪。

为什么错:唯一索引的“慢”只体现在写入路径上,而且是特定条件下。change buffer 的作用是:要更新的数据页不在内存里时,先把这个更新缓存起来,等下次读到这个页再合并,避免一次随机读磁盘。但唯一索引每次插入都要先判断「这个值是否已存在」,必须把页读进内存核对,用不上 change buffer,于是写多的场景就多了一次随机 IO。查询性能上,唯一索引和普通索引没有区别。

怎么答对:先说 change buffer 的机制,再说唯一索引为什么用不上它,最后给场景边界——写多读少(账单、日志类)优先普通索引;业务上必须靠数据库保证唯一性的,该建唯一索引还是得建,别为了性能牺牲约束。能主动说出「业务可以接受的话才优先普通索引」,这个限定词会让答案显得可信。

6. 押题与答题节奏:把三十多个问题压成四张答题卡

到进场前,题目不用再按条背了,按链路压成四张卡更管用。第一张是索引卡:为什么建索引 → 三种结构怎么选 → InnoDB 为什么选 B+ 树 → 聚簇与二级索引 → 回表 → 覆盖索引与索引下推 → 六类失效。这条链从头讲到尾不超过四分钟,中间任何一环被追问都能往回接。第二张是日志卡:redo log 是什么 → 为什么循环写 → crash-safe 从哪来 → binlog 三种格式 → 两阶段提交 → 崩溃恢复三种情况 → 刷盘参数。第三张是故障卡:SQL 变慢的四层排查、误删数据的恢复顺序、kill 不掉的原因(命令被堵、命令到位但没触发、触发了但执行需要时间)。第四张是架构卡:存储引擎选型、主备建立与切换、大表查询为什么不爆内存。

答题卡起手问题必须说到的一句常见追问
索引为什么用 B+ 树非叶子不存数据,树更矮,叶节点链表降随机 IO回表几次、覆盖索引代价
日志redo log 和 binlog 区别一个循环写只留未刷盘,一个追加写保存全量prepare 状态怎么判
故障SQL 突然变慢怎么查先排除整体资源与锁,再看执行计划force index 能不能长期用
架构主备怎么同步建立阶段备库定位点,运行阶段主库推送双主下内存表的风险

节奏上有两个血泪经验。一是别抢答。面试官问「索引为什么失效」,你不要一口气把六类全倒出来,先说「本质是索引存的是原始值,任何改变比较值的操作都会失效」,停一下看他往哪追,再展开具体场景——他往往只想听其中两类。二是遇到不会的,把推导过程说出来。比如问「这条 SQL 走不走索引」,你一时判断不了,就说「我先看这个列在索引里的位置,再看条件是不是等值」,把判断方法讲出来,比硬猜一个「走」或「不走」得分高。

还有个玄学但有效的技巧:回答任何性能类问题,末尾主动加一句代价。「覆盖索引能省回表,但索引会变宽,写入维护成本上升」「强制索引能救急,但数据分布一变就会翻车」。面试官听到你主动提代价,会默认你做过真活儿。

从那以后我每次准备数据库面试,都强制走一遍这四张卡:先不看答案,把每条链口头讲一遍,卡壳的地方标出来,只补那一段,不再从头刷题。讲不顺的地方,就是面试时会翻车的地方。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进