先问一个问题:2026 年 Java 后端跳槽,哪些 MySQL 知识点是绕不过去的?
答案几乎固定:B+树、联合索引、BufferPool。不管是千万级数据量的慢 SQL 排查,还是面试官顺着索引追问的“为什么这么快”“为什么失效”,本质都在这三块里。这次我们就把这三块串成一个完整体系,从原理到手撕题,从 EXPLAIN 输出到 BufferPool 调优,一次讲透。
这不是概念背诵,而是真正能应对“你线上有一张千万级表,查询很慢,怎么排查”这类场景化面试题的知识闭环。读完之后,你可以直接拿这套思路去拆解慢 SQL,也能在面试现场把索引问题回答得有条理。
1. MySQL 索引面试考点速览
先给一份适用于 2026 Java 后端面试的 MySQL 索引考点清单。面试官问索引相关问题时,考点其实非常集中:
| 考点 | 核心问题 | 面试深度 |
|---|---|---|
| B+树结构 | 为什么 InnoDB 选择 B+树而不是 B树、红黑树、哈希表 | 原理层 |
| 聚簇索引 | 数据和索引如何存储,主键为什么重要 | 原理层 |
| 二级索引 | 非主键索引存储了什么,回表代价如何计算 | 原理层 |
| 覆盖索引 | 什么样的查询可以避免回表 | 优化层 |
| 联合索引 | 最左前缀原则怎么理解,如何设计字段顺序 | 优化层 |
| 索引失效 | 哪些写法会导致索引失效,如何用 EXPLAIN 验证 | 实战层 |
| 索引下推 | 联合索引条件下推如何减少回表 | 原理+实战 |
| BufferPool | 索引查询为什么快,数据页缓存和淘汰机制如何工作 | 原理+调优 |
这套知识点不是孤立的。B+树负责解释“为什么能快”,联合索引负责“如何设计得更快”,BufferPool 负责“快的基础设施是什么”。面试官从“慢查询”切入,最终一定会落到这三者上。
2. B+树:千万级数据为什么还能跑得这么快
2.1 面试题:InnoDB 为什么选择 B+树
这是 MySQL 原理题的鼻祖,几乎所有面试官都会从这个问题开始。如果你只回答“因为 B+树矮”是不够的,需要拆成四个层面:
第一,磁盘 IO 次数少。B+树是矮树,千万级数据通常只有 3 到 4 层。从根节点到叶子节点,一次查询只需要 3 到 4 次磁盘 IO。而 InnoDB 的数据页默认 16KB,每个非叶子节点可以存储大量索引项,树的宽度非常大,高度自然被压低。
第二,叶子节点形成有序链表。B+树的所有叶子节点按顺序连接,范围查询和排序非常高效。比如查询id BETWEEN 100 AND 200,只需要定位到起始叶子节点,然后沿着链表向后扫描即可,不需要在中序遍历中反复回溯。
第三,非叶子节点不存数据,只存索引。这意味着每个数据页可以放上千个键值,相同层高的树能容纳的数据量远大于 B树。同样的数据量,B+树比 B树更矮,磁盘 IO 更少。
第四,查询性能稳定。B+树的查询必须走到叶子节点才能拿到数据,这一点保证了所有查询的时间复杂度都接近树高。而 B树可能在非叶子节点就拿到数据,看起来更高效,但也造成每次查询时间不确定,对数据库这种需要稳定 QPS 的场景并不友好。
2.2 手撕题:三层 B+树到底能存多少数据
面试官喜欢追问“你说 B+树矮,那到底能存多少数据”。这是个计算题,要现场算。
InnoDB 非叶子节点的一个索引项大约占 8 到 12 字节(主键 bigint 8 字节 + 页指针 6 字节,算上页内其他结构按 12 字节估算)。一个 16KB 的非叶子节点可以存放:
16 * 1024 / 12 ≈ 1365 个索引项如果第一层根节点有 1365 个索引项,第二层就有 1365 个非叶子节点,第三层就有 1365 * 1365 个叶子节点,每个叶子节点可以存放约 15 到 16 条记录(按每行 1KB 估算):
1365 * 1365 * 15 ≈ 2795 万条两层非叶子节点 + 一层叶子节点,三层 B+树就足够支撑千万级数据量。这就是“千万级”这个说法的来源。当然实际情况取决于表结构、行大小和主键长度,但这个数量级足够说明问题。
2.3 聚簇索引与二级索引
InnoDB 的表本身就是按 B+树组织的,这个 B+树就被称为聚簇索引。聚簇索引的叶子节点直接存储整行数据。所以在 InnoDB 中,主键就是数据的物理组织方式,没有主键的 InnoDB 表会尝试使用唯一索引,再不行会生成隐藏主键。
二级索引则是另外一棵 B+树,叶子节点存储的是索引列值 + 主键值。查询时先用二级索引找到主键,再用主键去聚簇索引查完整行数据,这个过程称为回表。
这里有一个面试官非常爱追问的点:为什么二级索引叶子节点不直接存数据行地址?因为数据页在 B+树中是会移动、分裂、合并的。如果二级索引直接存行地址,一旦页分裂或重排,所有二级索引都要更新,代价不可接受。存主键值则相对稳定。
2.4 覆盖索引与回表代价
回表不是必须的。如果查询需要的字段已经全部包含在二级索引中,就不需要回表,这叫做覆盖索引。
-- 假设有联合索引 (username, age) -- 这个查询只需要 username 和 age SELECT username, age FROM user WHERE username = 'zhangsan';上面的查询在二级索引中就能拿到全部需要的数据,不需要回到聚簇索引。这种优化在千万级表上收益巨大,因为回表一次就是一次随机磁盘 IO,大量回表会变成明显的性能瓶颈。
面试回答时可以补充:不要无脑 SELECT *。SELECT *会把二级索引覆盖查询直接变成回表查询。这也是日常 CRUD 优化中最容易做的一点。
3. 联合索引:最左前缀原则的底层逻辑
3.1 联合索引的树结构是什么样
联合索引的考点集中在最左前缀。只背口诀“最左前缀”是不够的,要理解联合索引在 B+树中的排序方式。
假设有一个联合索引(a, b, c),InnoDB 会先按a排序,a相同的记录再按b排序,b也相同的再按c排序。在叶子节点上,数据首先以a为主序,b和c只是在这个前提下有序。
这就导致一个关键结论:
- 查询条件中有
a,索引可以定位。 - 查询条件中只有
b,由于整体数据是按a排序的,b并没有形成一个全局有序序列,索引无法用于快速定位。 - 查询条件中有
a和c,但没有b,此时只有a能用于索引定位,c无法参与最左匹配。
3.2 手撕题:联合索引 (a, b, c) 哪些查询能走索引
这是一个出现频率极高的面试题,建议直接背下这张表:
| 查询条件 | 能否走索引 | 说明 |
|---|---|---|
WHERE a = 1 | 能 | 完全符合最左前缀 |
WHERE a = 1 AND b = 2 | 能 | 使用 a 和 b |
WHERE a = 1 AND b = 2 AND c = 3 | 能 | 全值匹配 |
WHERE a = 1 AND c = 3 | 部分 | a 用于定位,c 无法使用索引过滤 |
WHERE b = 2 | 不能 | 缺少最左列 a |
WHERE c = 3 | 不能 | 缺少最左列 |
WHERE a = 1 ORDER BY b | 能 | 索引同时支持排序 |
WHERE a = 1 ORDER BY c | 不能 | b 没有参与,c 无法用索引排序 |
注意,WHERE a = 1 AND c = 3这条,MySQL 经过优化后可能会走索引,但只有a是真正参与索引定位的,c是在回表后或者索引下推阶段才能处理。判断是否高效,不能只看是否用到了索引,还要看用了多少列。
3.3 联合索引的字段顺序设计
联合索引字段顺序是真正的实战问题。几个核心原则:
选择性高的字段放在最前面。选择性是指某个字段的不同值数量占总行数的比例。性别字段只有两个值,选择性极低;手机号基本完全唯一,选择性极高。放在前面的字段选择性越高,索引定位越精确。
需要考虑排序和分组字段。如果查询中常用ORDER BY col,那么把col放进联合索引,并且尽量让它的排序方向一致,可以避免文件排序。
不要把经常更新或过长的字段放前面。联合索引本身是 B+树,字段更新会导致索引页重排,更新频繁的字段放在前面,写放大会更明显。而 Varchar 超长字段参与索引会增加索引体积,降低单个数据页能容纳的索引项数量。
3.4 索引下推:面试官最爱追问的优化细节
索引下推是 MySQL 5.6 引入的优化,全称 Index Condition Pushdown。它解决的核心问题是:当联合索引只能部分匹配查询条件时,把剩余条件的判断下推到存储引擎层,减少回表次数。
举个例子:
-- 联合索引 (age, name) SELECT * FROM user WHERE age > 20 AND name LIKE '张%';如果不使用索引下推,MySQL 会先用age > 20查出所有匹配的主键,然后逐条回表,在服务层判断name LIKE '张%'。如果age > 20命中了 10000 条记录,就要回表 10000 次。
使用索引下推后,存储引擎在读取二级索引时,已经可以在索引内部判断name是否以张开头,只对符合条件的记录回表,回表次数急剧减少。
在EXPLAIN输出中,如果出现Using index condition,就代表索引下推生效了。5.6 之后默认开启,代码里通常不需要额外配置。
4. 索引失效:千万级表最常见的技术债
4.1 八种典型索引失效场景
面试和实际排查中,以下情况出现概率最高:
| 场景 | 示例 | 原因 |
|---|---|---|
| 隐式类型转换 | WHERE phone = 13800138000,phone 是 varchar | 字符串转数字导致索引列发生函数运算 |
| 对索引列使用函数 | WHERE DATE(create_time) = '2026-01-01' | 索引列被函数包裹,B+树无法按原值查找 |
| LIKE 前缀模糊 | WHERE name LIKE '%张' | 最左匹配失效,从中间开始无法定位 |
| OR 连接非索引列 | WHERE id = 1 OR status = 0,status 无索引 | 需要全表扫描判断所有行的 status |
| 联合索引不满足最左前缀 | WHERE b = 1,索引为 (a, b, c) | 缺少最左列 a |
| 范围查询后的列 | WHERE a > 1 AND b = 2,索引为 (a, b) | a 是范围条件,b 无法继续用索引定位 |
| 使用不等于、NOT IN、IS NOT NULL | WHERE status != 0 | 无法转化为等值和范围匹配 |
| 优化器认为全表扫描更快 | 小表或数据量极小时 | 索引回表代价比全表扫描高 |
4.2 隐式类型转换的实战案例
这是最常见的线上问题之一。假设user表有 1000 万数据,phone字段是 varchar 类型,并且建了唯一索引。
-- 索引失效版本 SELECT * FROM user WHERE phone = 13800138000; -- 索引生效版本 SELECT * FROM user WHERE phone = '13800138000';第一句 SQL 中,MySQL 会把phone字段隐式转换为数字,这意味着索引列上发生了函数运算,B+树无法按原始顺序快速查找,索引失效。排查时可以用EXPLAIN看type字段,如果是ALL或index,就说明没有走范围或等值索引。
4.3 时间字段的函数操作
很多慢 SQL 出在时间查询上。比如统计某天的用户增量:
-- 索引失效版本 SELECT * FROM user WHERE DATE(create_time) = '2026-06-01'; -- 索引生效版本 SELECT * FROM user WHERE create_time >= '2026-06-01 00:00:00' AND create_time < '2026-06-02 00:00:00';DATE(create_time)会让 create_time 字段参与函数运算,索引失效。正确的写法是把范围计算放在常量一侧,让 create_time 保持原始列值,范围查询可以走索引。
4.4 范围查询后索引失效的精确理解
“范围查询后的列失效”是最容易被误解的一条。很多人以为WHERE a > 1 AND b = 2完全不能走索引,实际上a > 1是能走索引的,只是b无法参与索引定位。这里要看 EXPLAIN 的key_len来判断索引具体使用了多少列。
-- 索引 (a, b) EXPLAIN SELECT * FROM table WHERE a > 1 AND b = 2;如果key_len只显示 a 字段的长度,说明 b 条件只在回表后用于过滤,索引使用的列数没有到联合索引的全部长度。这比“索引完全失效”要轻微一些,但仍然需要优化。
4.5 隐式字符集问题
关于索引失效,还有一个容易被忽略的场景:表 join 时两个字段类型相同,但字符集不同。比如一个表是 utf8mb4,另一张表是 utf8,关联查询时 MySQL 可能对其中一个字段做转换,导致索引失效。这类问题在 EXPLAIN 中不容易直接看出来,需要检查建表语句中的CHARSET。2026 年的新项目建议全部统一使用 utf8mb4。
5. BufferPool:支撑 MySQL 高效查询的底层机制
这一部分是面试中“从索引讲到 MySQL 架构”的关键转折点。索引解决了查询路径变短的问题,但最终真正让数据访问变快的,是内存中的缓存机制。
5.1 BufferPool 到底是什么
BufferPool 是 InnoDB 在内存中维护的一块缓存区域,默认大小通常是物理内存的 75% 左右。数据页、索引页、变更缓冲的页面都缓存在这里。当执行查询时,InnoDB 会先去 BufferPool 中找对应的数据页,如果找到就直接使用,避免磁盘 IO;如果没找到,才从磁盘加载到 BufferPool。
面试官问到“为什么 MySQL 第一次查询慢,第二次快”,答案就在 BufferPool。第一次查询是冷数据加载,需要磁盘 IO;第二次数据页已经在内存中,直接内存访问,耗时可能差一个数量级。
5.2 BufferPool 与索引查询的关系
B+树查询过程中,每一层节点的读取都需要访问数据页。如果这些节点正好在 BufferPool 中,整个查询过程可以完全在内存中完成。这也是为什么“千万级数据量查询仍然很快”成立的原因之一——数据量大不代表每次查询要读全量数据,通过索引定位后,真正读取的数据页很少,而且大概率已经缓存。
生产环境观察时可以关注一个指标:Innodb_buffer_pool_read_requests和Innodb_buffer_pool_reads。前者是逻辑读,后者是物理读。如果逻辑读除以物理读的比例很高,说明 BufferPool 命中率很好。线上一般要求命中率在 95% 以上。
-- 查看 BufferPool 命中率相关状态 SHOW GLOBAL STATUS LIKE '%Innodb_buffer_pool_read%';5.3 BufferPool 的内存淘汰机制
BufferPool 不是无限大的,需要淘汰旧页面,InnoDB 使用的是一种改进的 LRU 算法。
传统 LRU 有一个问题:一次全表扫描会把整个 BufferPool 里的热缓存全部挤出去,这些热缓存可能是真正高频访问的数据页。为了避免这种情况,InnoDB 把 LRU 链表分成了 New 和 Old 两个区域,默认比例为 37% 的 Old 区。数据页首次加载进入 Old 区,只有在指定时间内再次被访问,才会进入 New 区。全表扫描虽然会读入大量磁盘页,但这些页大概率不会被快速二次访问,最终在 Old 区就被淘汰,对 New 区的热数据影响较小。
5.4 Change Buffer:二级索引写入的秘密武器
面试问到“为什么 InnoDB 插入慢但也不是那么慢”时,可以引出 Change Buffer。
对于二级索引页,如果 BufferPool 中不存在目标页,InnoDB 并不是每次都在磁盘上直接更新,而是先把变更记录在 Change Buffer 中,等后续读取该页时再合并,或者在后台线程刷新时合并。这样能减少随机磁盘 IO,提升写入性能。
不过 Change Buffer 也有代价:如果缓存了大量未合并的变更,读取某个索引页时需要进行更多合并操作。对高频写场景,要注意监控 Change Buffer 的大小。
5.5 BufferPool 参数调优面经
面试或生产环境几个必问的参数:
| 参数 | 作用 | 建议 |
|---|---|---|
innodb_buffer_pool_size | 定义 BufferPool 大小 | 生产环境通常设为物理内存的 60% 到 80%,具体要看业务 |
innodb_buffer_pool_instances | 拆分 BufferPool 实例 | 减少并发访问锁竞争,内存较大时建议设置 |
innodb_change_buffer_max_size | 限制 Change Buffer 占用 BufferPool 的比例 | 写多读少可以适当调大,默认 25% |
innodb_old_blocks_time | 控制数据页在 Old 区停留时间 | 避免全表扫描污染热缓存,默认 1000 毫秒 |
需要提醒的是,这些参数都没有绝对正确的值,调优必须结合自己的监控数据。先确认 BufferPool 命中率和慢 SQL 特征,再决定调整方向。
6. 千万级慢 SQL 排查实战:从定位到优化
面试场景中的“手撕题”,往往不会只问原理,而是给出一个实际场景。这里整理一套完整排查思路。
6.1 开启慢查询日志
线下复现时,先打开慢查询日志,确认哪些 SQL 真正慢。
# MySQL 5.7+ 或 8.0 SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1; SHOW VARIABLES LIKE 'slow_query_log_file';把long_query_time设置为 1,表示超过 1 秒的 SQL 会被记录下来。如果线上不方便直接修改全局变量,可以在配置文件my.cnf中配置并重启 MySQL。
6.2 用 EXPLAIN 定位问题
拿到一条慢 SQL,优先跑 EXPLAIN:
EXPLAIN SELECT * FROM orders WHERE user_id = 10001 AND status = 1 ORDER BY create_time DESC LIMIT 20;重点看几个字段:
type:如果出现ALL,说明全表扫描,必须优化;如果是ref或range,说明索引使用还算正常;eq_ref和const是理想情况。key:实际使用的索引名。key_len:索引使用的字节数,联合索引中能判断哪些列参与了匹配。rows:预估扫描行数,这个数字如果远超预期,就要检查索引设计。Extra:如果出现Using filesort,说明排序没有用到索引;Using temporary说明使用了临时表,通常需要优化。
6.3 一个完整的慢 SQL 优化案例
假设orders表有 2000 万条订单记录,高频查询是:
SELECT order_id, user_id, amount, status FROM orders WHERE user_id = 12345 AND status = 1 ORDER BY create_time DESC LIMIT 10;最直接的方案是建联合索引:
ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, create_time);这个索引的字段顺序设计思路是:user_id等值查询、status等值查询、create_time排序。等值条件字段放前面,排序字段放最后,查询和排序都能走索引,避免回表排序。
再进一步,如果业务只需要order_id、user_id、amount、status四个字段,而联合索引中没有amount,那么查询会回表。可以把索引升级为覆盖索引:
ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, create_time, amount) DROP INDEX idx_user_status_time ...要注意,amount放在最后主要用于覆盖索引,不参与定位和排序。具体加哪些字段要看业务是否高频覆盖,不能盲目把所有查询字段都塞进索引,索引过大也会增加增删改的开销。
6.4 分页深翻页优化
千万级表还有一类典型慢查询:深分页。
SELECT * FROM orders ORDER BY create_time DESC LIMIT 500000, 20;MySQL 需要先扫描到第 50 万行,再取 20 行,扫描过程非常耗时。常见优化方案有两种。
第一种是延迟关联:
SELECT o.* FROM orders o INNER JOIN ( SELECT id FROM orders ORDER BY create_time DESC LIMIT 500000, 20 ) t ON o.id = t.id;内层只查主键或索引列,通过索引快速定位到需要的主键集合,再与原表做关联查询取完整数据,避免全行扫描。
第二种是范围查询代替偏移查询,适合有连续主键或时间序列的场景:
SELECT * FROM orders WHERE create_time < '2026-05-01 00:00:00' ORDER BY create_time DESC LIMIT 20;这个方案更适合业务上可以用上一次拿到的时间点作为游标的场景。对于大多数 ToC 业务,延迟关联是更通用的解法。
7. 索引设计的最佳实践,直接可以写进简历
7.1 每个表都应该有主键,且尽量短
InnoDB 是聚簇索引表,主键就是数据组织方式。如果使用随机 UUID 作为主键,新数据插入时会随机落在 B+树的不同位置,导致页分裂和碎片增多。建议使用自增主键或有序雪花 ID。同时主键越短越好,因为二级索引叶子节点存储主键,主键越长,所有二级索引的体积就越大,BufferPool 命中率也会受影响。
7.2 联合索引建议控制在 3 到 5 列
联合索引不是列数越多越好。每增加一列,索引体积扩大,更新代价上升。一条 SQL 能在联合索引中覆盖 3 到 5 个字段,已经覆盖了绝大多数业务场景。多余字段更合适的方式是冗余到表中或者使用覆盖查询之外的手段,而不是无限扩大索引。
7.3 冗余索引要定期清理
有联合索引(a, b)时,单独的索引(a)就是冗余索引。因为 MySQL 完全可以用(a, b)来支持WHERE a = ?的查询。冗余索引增加了写入开销和存储成本,通过sys.schema_unused_indexes可以检查从未使用过的索引。
-- 查看从未使用过的索引 SELECT * FROM sys.schema_unused_indexes;7.4 线上大表加索引要冷静
对千万级数据量的表执行ALTER TABLE ADD INDEX,会锁住 DDL 写入。即使 MySQL 8.0 支持在线 DDL,也不能完全避免负载影响。建议使用gh-ost这类在线变更工具,或者在业务低峰期执行,并且分批运行ANALYZE TABLE来更新统计信息。
7.5 区分等值查询和范围查询的字段
索引设计可以围绕查询类型来定:
- 等值查询字段,放在联合索引前面。
- 范围查询字段,放在联合索引中间或后面,但要意识到范围查询之后的字段无法继续用于索引定位。
- 排序字段尽量利用索引顺序,避免
Using filesort。
8. 常见面试追问与标准化回答框架
这部分直接给出面试现场可用的回答结构。
8.1 追问:为什么覆盖索引比回表查询快?
回答框架:覆盖索引的叶子节点已经包含查询所需字段,查询过程中不需要回到聚簇索引取整行数据,省掉了随机磁盘 IO。在千万级表中,回表通常意味着随机 IO,随机 IO 的数量级远大于顺序 IO。所以覆盖索引的优化是真实有效的。
8.2 追问:联合索引中字段顺序怎么确定?
回答框架:先看等值查询条件,等值字段放前面;再看范围查询字段,范围字段放中间;最后看排序字段,排序字段尽量放在联合索引中,且方向和查询要求一致。如果能形成覆盖索引,就把需要查询的普通字段追加在最后,但要注意不要盲目扩大索引体积。
8.3 追问:发现一条 SQL 没走索引,你怎么排查?
回答框架:先用 EXPLAIN 看type、key、rows、Extra。如果type=ALL,优先检查查询条件里有没有OR、函数、隐式转换、模糊前缀。如果是联合索引,检查是否满足最左前缀原则。如果 SQL 写法没问题,检查统计信息是否过期,执行ANALYZE TABLE更新统计信息后再看执行计划。最后考虑优化器评估:小表可能全表扫描更快,这时候不需要强行加索引。
8.4 追问:BufferPool 太大有什么问题?
回答框架:BufferPool 过大可能导致 OOM,MySQL 的常驻内存管理会被操作系统判定为内存压力过大;过小则缓存命中率低,磁盘 IO 频繁。实际部署要预留出操作系统和其他进程的内存,同时监控日志错误。InnoDB 官方经验是 75% 左右,但生产环境要按实际内存分配。
8.5 追问:LRU 为什么分 New 和 Old 两段?
回答框架:防止全表扫描或一次性大查询污染热缓存。数据页先进入 Old 区域,生命周期较短,如果没有被再次访问就淘汰;频繁访问的数据才晋升到 New 区域。这种设计保证了 BufferPool 中保留的高频热数据不会被冷数据全部挤出。
9. 千万级 MySQL 索引优化清单
| 优化方向 | 做法 | 收益 |
|---|---|---|
| 表结构 | 使用自增主键或有序 ID,避免随机 UUID | 减少页分裂,降低二级索引体积 |
| 索引设计 | 联合索引等值字段在前,范围字段在中,排序字段靠后 | 提高索引利用率,减少回表 |
| 查询写法 | 避免索引列函数运算、隐式转换、前置模糊 | 防止索引失效 |
| 覆盖索引 | 高频查询只查索引能覆盖的字段,避免SELECT * | 省去回表随机 IO |
| 分页 | 深分页改用延迟关联或游标 | 避免大偏移量扫描 |
| 参数 | 合理设置 BufferPool 大小 | 提高缓存命中率 |
| 监控 | 开启慢查询日志,定期分析sys.schema_unused_indexes | 做到问题早发现 |
这套清单不是面经里停留于纸面。实际项目中,先看慢查询日志,再对慢 SQL 做 EXPLAIN,最后调整索引设计和查询写法,是最高效的路径。
回到开头的问题:2026 年 Java 后端面试,MySQL 索引为什么还是核心?因为数据量只会越来越大,慢查询问题不会消失。B+树决定了索引能力的天花板,联合索引决定了 SQL 设计的好坏,BufferPool 决定了底层性能的底座。能把这三点串成一套实战思路,无论是跳槽面试还是线上排查,都能直接派上用场。
建议先把文章中的几个手撕题答案自己默写下,再用 EXPLAIN 实际跑一遍自己熟悉的表,效果比只看不练好得多。