1. 面试官问联合索引,到底在考什么
先问个扎心的问题:你背了那么多MySQL八股文,为什么一到了联合索引这块,面试官总能把你问到卡壳?
我做了几年后端面试官,也被人面过,总结下来联合索引这个知识点之所以高频,不是因为它本身多难,而是因为它是一个完美的分水岭。它能同时考察你对三件事的理解程度:B+树的数据结构到底长什么样、MySQL的索引存储机制是否真的懂、以及你有没有在真实业务中踩过索引设计的坑。
背过八股的人能说出“最左前缀原则”这六个字,但一问到“为什么会有最左前缀原则”,就支支吾吾了。而真正有经验的候选人,会从联合索引在B+树里的排列顺序说起,讲到范围查询为什么会导致后面的索引列失效,再举例说明实际业务里如何设计联合索引来避免回表。这一套下来,面试官基本就能判断出你到底是真懂还是背的。
一句话先给你吃个定心丸:联合索引不是多个单列索引的简单叠加,它是将多个列按照指定顺序拼成一个索引键。这个“顺序”二字,就是整个联合索引的灵魂。
理解了这句话,你就理解了一大半。下面我从原理到实战给你拆透。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 联合索引的底层存储逻辑,以及最左前缀原则为什么存在
2.1 B+树里的“组合键”到底是怎么存的
要理解联合索引,必须先回到InnoDB的B+树索引结构上来。我们知道,InnoDB的主键索引(聚集索引)的叶子节点存储的是整行数据,而二级索引(普通索引)的叶子节点存储的是索引列的值加上主键值。
那联合索引呢?联合索引在B+树里是怎么排的?
假设我们有一个表结构如下:
sql复制CREATE TABLE `orders` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL,
`order_no` varchar(32) NOT NULL,
`status` tinyint NOT NULL,
`create_time` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_user_status_time` (`user_id`, `status`, `create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
我们创建了一个联合索引 (user_id, status, create_time)。这个索引在B+树中的存储,不是把三个列的值分别存到三个地方,而是把三个列的值拼接在一起,作为一个整体来比较大小和排序。
具体的排序规则是:先按第一列(user_id)排序,user_id相同的情况下再按第二列(status)排序,前两列都相同的,再按第三列(create_time)排序。
你可以把它想象成字典里的单词排序:先按首字母排,首字母相同按第二个字母排,以此类推。这就是最左前缀原则在底层数据结构上的根源——B+树的键值本身就是这样按从左到右的顺序构造出来的。
理解了这一点,最左前缀原则就非常自然了:联合索引的B+树结构决定了它只能从最左边的列开始匹配查找条件。如果你跳过第一列直接按第二列查,B+树根本不知道该往左子树还是右子树走,因为这个树的第一排序维度根本没有用上。
2.2 最左前缀原则的完整表述,比你想象的要宽
很多文章把最左前缀原则简单概括成“查询条件必须包含联合索引的第一列”,这个说法太粗糙了,也是很多人踩坑的根源。
完整的最左前缀原则实际上包括三种匹配方式:
- 最左前缀匹配:查询条件必须从索引的第一列开始连续匹配。例如
(user_id, status)或(user_id, status, create_time)都能用到这个联合索引,但(status, create_time)就用不上。 - 最左前缀模糊匹配:
WHERE user_id = 1 AND status IN (0, 1)也能用索引,IN列表本质上等价于多个等值条件的OR组合。 - 范围查询的右边界:
WHERE user_id = 1 AND status > 0 AND create_time > '2024-01-01',这里user_id和status的查询条件能用上索引,但create_time的查询条件就用不上索引了,原因在于status是一个范围查询,它在B+树中只能确定一个区间,这个区间内的数据不再是按create_time有序排列的。
第三种情况是面试中最高频的考点之一,后面我单独展开讲。
2.3 用一条SQL的索引匹配过程来理解
我们用上面的表来具体走一遍。假设执行这条SQL:
sql复制SELECT * FROM orders WHERE user_id = 100 AND status = 1 AND create_time > '2024-06-01';
这个查询条件匹配了联合索引的全部三个列,它在B+树中的查找过程是这样的:
- 根据
user_id = 100,在B+树中定位到所有user_id = 100的索引记录区间。 - 在这个区间内,索引记录已经是按
status排序好的,于是根据status = 1进一步定位。 - 在剩下的记录中,索引记录又是按
create_time排序好的,于是根据create_time > '2024-06-01'定位到具体的叶子节点位置。
这个过程每一层都用上了索引的有序性,所以这个查询能够高效地利用联合索引。
但如果你是这么查的:
sql复制SELECT * FROM orders WHERE status = 1 AND create_time > '2024-06-01';
查询条件跳过了第一列 user_id,B+树就炸了——它不知道该从哪棵子树开始找。这时候MySQL只能选择全索引扫描甚至全表扫描,联合索引完全派不上用场。
3. 范围查询引发的最右失效,以及排序场景中的隐藏杀手
3.1 为什么范围查询会让后面的索引列失效
先说结论:联合索引中,一旦某列使用了范围查询,它右边的所有列都无法用于索引定位,只能用于回表后的数据过滤。
这个现象在面试中经常以如下形式出现:
sql复制-- 假设联合索引为 (a, b, c)
SELECT * FROM t WHERE a = 1 AND b > 2 AND c = 3;
请问这个SQL能用上索引的哪些列?
答案是:a 和 b 能用上索引,但 c 用不上。
为什么?还是回到B+树的结构。当 a = 1 AND b > 2 定位到一段区间之后,这段区间内的记录是按照 a, b, c 的顺序排序的,也就是先按 a 排,a 相同按 b 排,b 相同按 c 排。现在 a 被锁定为常量1,区间内按 b 排序没问题,但 b 是一个范围(大于2),这意味着 b 相同的记录在物理上可能是分散的,而 c 的排序是在 b 相同的前提下才成立的。
更直白的说法:因为 b 是范围取值,所以这个区间内 b 取了多个不同的值,而不同的 b 值之间,c 是无序的。既然 c 在索引里是无序的,索引自然无法帮助 c 进行快速查找。
3.2 ORDER BY与联合索引的关系
这是另一个面试高频点,很多人完全没意识到。联合索引的有序性不仅服务于WHERE条件,还能服务于ORDER BY排序,从而避免 filesort(文件排序)。
举个实际例子。假设联合索引是 (user_id, create_time):
sql复制SELECT * FROM orders WHERE user_id = 100 ORDER BY create_time DESC LIMIT 10;
这条SQL的执行计划中,MySQL可以直接按联合索引倒序扫描,拿到 user_id = 100 的记录已经是按 create_time 排好序的,不需要额外的排序操作,效率极高。
但如果写成:
sql复制SELECT * FROM orders WHERE user_id = 100 ORDER BY status DESC LIMIT 10;
联合索引是 (user_id, create_time),而排序用的是 status 列,索引完全帮不上忙,MySQL只能先把 user_id = 100 的数据查出来,然后单独做一次排序,也就是出现了 filesort。数据量一大,这条SQL就会变成慢查询。
这里有一个平时很容易忽略的细节:ORDER BY和WHERE的列必须按照联合索引的最左前缀顺序来,但范围条件会破坏这种顺序。
比如联合索引是 (a, b, c),执行:
sql复制SELECT * FROM t WHERE a = 1 ORDER BY c;
因为 a 被锁定为常量,c 在 a = 1 的区间内是有序的,所以这个排序也能用到索引,不需要filesort。这个场景很多文章没提到,但面试中偶尔会出现,如果你能主动说出来,会是明显的加分项。
4. 覆盖索引与索引下推:联合索引的两大隐形红利
4.1 覆盖索引:让查询彻底摆脱回表
联合索引的底层叶子节点存储的是“索引列的值 + 主键值”,这就给了我们一个极大的优化空间:如果查询所需的字段全部在索引列和主键里,那么查询就完全不需要回表,直接从索引叶子节点上拿到数据。这就是覆盖索引。
举一个非常经典的场景。假设我们的表有大量字段,并且经常需要按 user_id 查询 order_no:
sql复制SELECT order_no FROM orders WHERE user_id = 100;
如果我们只建了一个 idx_user_id (user_id) 的单列索引,那么这条SQL的执行过程是:先通过 idx_user_id 找到 user_id = 100 对应的主键 id,然后拿着这些主键id去主键索引里回表,取出整行数据,再返回 order_no 字段。这个过程对于每一行匹配的记录都要回表一次,数据量大时成本很高。
但如果我们建的是 idx_user_id_order_no (user_id, order_no) 联合索引,查询需要 order_no,而索引里已经有 user_id 和 order_no 了,MySQL直接遍历索引就能拿到所有结果,完全不需要回表。这就是覆盖索引的威力。
面试里考察覆盖索引的典型问题是:解释一下“索引覆盖”和“回表”的区别,以及如何利用联合索引实现覆盖索引。回答得好的候选人会说:通过将查询字段全部包含在联合索引中,使InnoDB可以直接在二级索引上完成数据读取,从而避免回表带来的随机I/O开销,这在高频查询场景中性能提升非常显著。
4.2 索引下推:MySQL 5.6带来的隐形优化
覆盖索引关注的是查询阶段,索引下推关注的是使用索引进行数据过滤的阶段。
还是用例子说明。假设联合索引是 (user_id, status, create_time),执行:
sql复制SELECT * FROM orders WHERE user_id = 100 AND status = 1;
在没有索引下推(ICP,Index Condition Pushdown)的旧版本MySQL中,查询流程是:
- 通过索引定位到
user_id = 100的叶子节点。 - 对每一条记录根据主键回表,取出完整行。
- 在服务层判断
status = 1,过滤掉不匹配的行。
有ICP之后,流程变成了:
- 通过索引定位到
user_id = 100的叶子节点。 - 直接在索引层面判断
status = 1,过滤掉不匹配的索引记录。 - 只对过滤后的记录回表。
两者的差异在于回表次数。ICP把索引列的条件判断下推到存储引擎层,大幅减少了回表次数。
你可能不知道,ICP的这个优化对于联合索引至关重要,因为很多时候我们无法通过索引把所有条件都精确定位,但索引下推可以帮忙在索引层面过滤掉一部分数据,减少回表压力。这也是为什么你建了联合索引,status 在范围条件右侧无法用于定位,但依然能通过ICP减少回表的原因。
面试中如果被问到“联合索引的status列明明用不到索引查找了,索引还有什么用”,你可以回答:虽然无法用于定位,但可以通过索引下推在存储引擎层提前过滤,减少回表次数,这同样是联合索引带来的收益。
5. 联合索引的失效场景集合:从最左前缀到隐式转换
5.1 最常考的六种失效场景
结合我实际看过的慢SQL,以及面试中经常拿来考候选人的例子,联合索引失效的场景主要集中在这六类:
场景一:查询条件没有从最左列开始
sql复制SELECT * FROM orders WHERE status = 1 AND create_time > '2024-01-01';
联合索引是 (user_id, status, create_time),跳过 user_id 直接查 status。索引完全无法使用。这是最典型、也是最基础的一种失效。
场景二:中间列使用了范围查询
sql复制SELECT * FROM orders WHERE user_id = 100 AND status > 0 AND create_time > '2024-01-01';
status 是范围查询,导致右边的 create_time 无法用于索引定位。虽然 create_time 的条件也是联合索引的第三列,但整个条件中只有 user_id 和 status 能利用索引,create_time 实际是作为回表后的过滤条件使用的。这是面试中最容易出现分歧的考点。
场景三:对索引列使用了函数或表达式
sql复制SELECT * FROM orders WHERE DATE(create_time) = '2024-06-01';
即使 create_time 是联合索引列,一旦套了函数,MySQL无法使用索引。这也是为什么设计表结构时,我们一般不建议存 create_time 这种需要频繁按日期过滤的字段然后总是套DATE函数的做法——更好的方案是直接查询区间。
场景四:索引列发生了隐式类型转换
sql复制SELECT * FROM orders WHERE user_id = '100';
如果 user_id 是bigint类型,但你传了字符串,MySQL需要对 user_id 做类型转换,这个转换可能让索引失效。MySQL对字符串和数字的比较规则比较复杂,但为了保险起见,查询参数类型尽量和字段类型保持一致。
场景五:LIKE模糊查询使用左侧通配
sql复制SELECT * FROM orders WHERE order_no LIKE '%123%';
这个SQL会用上联合索引吗?要看联合索引里有没有 order_no 列。假设有,但 % 在前面导致无法利用B+树前缀匹配的有序性,索引会退化为全索引扫描。
场景六:使用OR连接非索引列
sql复制SELECT * FROM orders WHERE user_id = 100 OR status = 1;
即使 user_id 和 status 都在联合索引里,OR会将两个条件的结果集做并集,MySQL可能无法直接使用联合索引来高效求解,常常会退化为全表扫描。这一点很多人会忽略,面试中也经常被拿来考察。
5.2 一个真实的慢SQL排查案例
我在实际工作中遇到过这样一个案例。一个订单查询接口,线上偶尔出现慢查询告警,SQL大概长这样:
sql复制SELECT * FROM orders
WHERE user_id = 100
AND status IN (0, 1)
AND create_time BETWEEN '2024-01-01' AND '2024-06-01'
ORDER BY create_time DESC
LIMIT 20;
联合索引是 (user_id, status, create_time),看起来一切都匹配上了,但执行计划里出现了Using filesort。
排查了半天,原因是:status IN (0, 1) 表面上是一个等值条件列表,但MySQL在优化时将其拆成了两个范围条件(status = 0 OR status = 1),而范围条件右侧的 create_time 无法用于排序,所以虽然索引能用于过滤,但ORDER BY还是需要单独排序。
这个问题的优化方案是调整联合索引顺序,改成 (user_id, create_time, status):
sql复制ALTER TABLE orders ADD KEY idx_user_time_status (user_id, create_time, status);
这样调整之后,user_id = 100 是等值条件,create_time BETWEEN ... 是范围条件,且 create_time 在索引中是第二列,ORDER BY create_time DESC 可以完全利用索引的有序性,不需要额外排序。status 虽然被移到了索引的第三列,但它只是过滤条件,而且能被索引下推处理,影响不大。
这个案例特别典型,它同时考察了:联合索引列的顺序设计、范围查询对右侧列的影响、ORDER BY对索引有序性的依赖,还有IN条件在优化器中的处理逻辑。如果你能把这个案例讲清楚,面试官绝对会对你刮目相看。
6. 如何根据实际业务设计联合索引
6.1 设计联合索引的通用原则
纸上谈兵这么久,我们落到真正的工作场景。联合索引设计其实有一个比较通用的思路,我根据自己的实践总结成以下几条原则:
原则一:优先覆盖等值查询条件
把 WHERE 条件中等值比较的列放在联合索引的最前面。比如 user_id = 100 这种条件,作为索引第一列能给B+树提供最精确的定位。
原则二:把区分度高的列放在前面
区分度,简单理解就是一列数据中不同值的比例。比如 user_id 的区分度往往很高(不同用户很多),而 status 可能只有 0、1、2 三个值,区分度很低。把区分度高的列放在前面,可以更早地缩小范围,减少索引扫描的数量。
但要注意:区分度优先不是绝对的,等值条件和范围条件的优先级更高。如果一个列区分度极高但是范围查询,另一个列区分度稍低但是等值查询,通常还是把等值查询列放前面,因为在B+树中等值定位的效率远高于范围定位。
原则三:考虑范围查询对后续列的影响
如果某个查询条件是范围查询(>、<、BETWEEN),那么它右侧的索引列基本无法用于索引定位。因此设计索引时,要么把范围查询的列放在联合索引的末尾,要么接受它右侧列失效的问题。
原则四:最大化覆盖索引的收益
如果某条高频SQL查询的字段不多,尽量把这些字段全部包含在索引里,实现覆盖索引,避免回表。当然,索引列越多,索引存储空间越大,写入性能损耗也越大,这是一个权衡。
6.2 一个实际业务场景的联合索引设计全过程
以一个电商系统的订单列表页为例,常见的查询场景是:
sql复制-- 场景1:用户查看自己的订单列表
SELECT * FROM orders WHERE user_id = ? ORDER BY create_time DESC LIMIT 20;
-- 场景2:按用户和订单状态筛选
SELECT * FROM orders WHERE user_id = ? AND status = ? ORDER BY create_time DESC LIMIT 20;
-- 场景3:运营后台按状态和创建时间范围查询
SELECT * FROM orders WHERE status = ? AND create_time BETWEEN ? AND ? ORDER BY create_time DESC;
这三个场景我分别给你分析:
场景1的最优索引是 (user_id, create_time),因为 user_id 是等值条件,create_time 用于排序,完美匹配。
场景2的最优索引是 (user_id, status, create_time)。等值条件 user_id 和 status 放在前面,create_time 在最后用于排序。
场景3比较特殊,它没有 user_id 的等值条件,如果还用场景2的索引,最左前缀匹配就会失效。对于这种低频的运营后台查询,我个人的处理方式是:单独建一个 (status, create_time) 的索引,虽然这个索引的查询量可能不大,但运营后台的查询往往非常吃索引,值得单独优化。
但是请注意:不是所有表都需要为一个查询场景单独建索引。索引越多,写入越慢,存储越大,维护成本越高。这时候需要做好平衡。通常我们优先覆盖线上发往数据库的高频查询,运营后台这类低频查询可以适当做全表扫描或者另建一个轻量索引。
6.3 联合索引与单列索引:哪个更值得建
面试中经常被反问:既然联合索引这么强大,为什么不把所有列都建成联合索引?
这里要分清一个概念:联合索引能覆盖多个列的查询,但它毕竟是一个索引,而不是多个索引。如果你建了三个单列索引 (a)、(b)、(c),那么查询条件里单独用 a、单独用 b、单独用 c 都可以走各自独立的索引。但如果你建了一个联合索引 (a, b, c),它只能覆盖以 a 开头的查询组合,比如 a、a+b、a+b+c,而不能覆盖单独用 b 或单独用 c 的查询。
MySQL 8.0之前还有一个限制:单表最多能建 16 个索引,如果每个列都建索引,很快就会被耗尽。而联合索引的兴起实际上替代了大量单列索引,它既节省了索引数量,又能在单个索引内覆盖多个查询场景。
实际业务中,我的建议是:
- 高频查询中哪一个等值条件最通用,就把它作为联合索引的第一列。
- 如果有多个等值条件,按区分度从高到低排列。
- 排序字段放在等值条件之后,范围条件尽量放最后。
- 不要为了追求覆盖索引而把不必要的列塞进索引,索引不是免费的。
7. 面试实战:遇到联合索引题,怎么答才条理清晰
面试终归是面试,光懂原理不够,还得有表达框架。我这里给你一个可以直接套用的答题思路,按照这个结构回答,面试官基本挑不出毛病。
7.1 第一步:先讲清楚联合索引是什么
“联合索引是指对一张表的多个列创建一个索引,索引数据在B+树中按索引列从左到右的顺序进行排序。因此,只有当查询条件满足最左前缀匹配时,才能有效利用联合索引。”
这是一个标准开场,但光有开场还不够,你一定要接着往下讲。
7.2 第二步:用底层结构解释最左前缀原则
不要只说结论,一定要解释为什么:“因为联合索引在B+树中是一个复合键,它先按第一个列排序,再按第二个列排序,以此类推。B+树的查找过程依赖于键值的有序性,如果跳过第一列直接用后面的列做查找条件,就无法利用这个复合键的排序结构。”
这一步能直接筛掉一片背八股的人,你只要讲出来,面试官心里就会给你加分。
7.3 第三步:主动举例说明失效场景
不要等面试官问,主动说:“比如联合索引 (a, b, c),WHERE a = 1 AND b > 0 AND c = 2 中 a 和 b 能用到索引,c 无法用于索引定位,因为 b 是范围查询,c 在 b 的某个范围内是无序的。另外,WHERE b = 1 这种跳过了 a 的查询也无法使用联合索引。”
主动展示你对边界情况的理解,比被动回答更有说服力。
7.4 第四步:延伸覆盖索引、索引下推等进阶概念
“在设计联合索引时,我还会关注覆盖索引的收益。如果高频查询所需的列都在索引内,可以直接走覆盖索引避免回表。另外,索引下推也能在存储引擎层面提前过滤部分数据,减少回表次数。”
这四个步骤走完,你的回答已经把“最左前缀原则、B+树原理、失效场景、优化思路”全部覆盖了,条理清晰、层层递进,面试官很难不给高分。
8. 日常工作里的联合索引调优笔记
有段时间我在排查一个订单系统的慢查询,每天深夜固定有几条SQL超时,定位之后发现全是联合索引设计不当导致的。我把当时踩过的坑,还有最终总结的经验都整理在下面,希望能帮你少走弯路。
8.1 用 EXPLAIN 验证你的判断,不要靠猜
很多开发者在改完索引后根本不看执行计划,这是大忌。每次修改索引或者改写SQL之后,一定要用 EXPLAIN 验证一下:
sql复制EXPLAIN SELECT * FROM orders WHERE user_id = 100 AND status = 1 ORDER BY create_time DESC LIMIT 20;
重点关注 key 字段(实际使用的索引)、rows(扫描的行数)、Extra(是否出现 Using filesort、Using index、Using index condition 等)。如果出现了 Using filesort,说明排序没有用上索引;如果出现了 Using index,说明命中了覆盖索引。
通过 EXPLAIN 反馈不断调整索引列的顺序,这个循环是索引调优最核心的工作方式。
8.2 检查联合索引使用情况的性能字典
MySQL 8.0提供了 sys.schema_index_statistics 视图,可以查看索引的使用情况:
sql复制SELECT * FROM sys.schema_index_statistics WHERE table_schema = 'your_db';
通过这个视图可以看到哪些索引从未被使用过。对于长期未使用的索引,可以评估是否需要删除,以降低写入开销和存储成本。
不过要注意,这个视图统计的是自服务器启动以来的累积数据,如果服务器刚重启,数据参考价值有限。我通常会在业务运行一周后再来查看。
8.3 警惕“冗余索引”陷阱
联合索引 (a, b, c) 创建之后,单独的索引 (a) 和 (a, b) 实际上就变得冗余了。MySQL不会自动帮你删除这些冗余索引,需要自己定期审查。
冗余索引的坏处不只是浪费存储空间,更重要的是每次写入都要维护多余的索引结构,降低写入性能。
举个例子,你既有 idx_a (a),又有 idx_a_b (a, b),那么写入一条记录时,InnoDB需要同时更新两个索引,而 idx_a 提供的查询能力 idx_a_b 完全能够覆盖。这种情况下,idx_a 就应该被删除。
8.4 从慢查询日志反推索引缺陷
慢查询日志是发现索引问题的金矿。我经常做的操作是:
- 开启慢查询日志,设置
long_query_time=1。 - 定期导出慢查询日志,按执行次数和耗时排序。
- 对排名靠前的SQL逐条EXPLAIN,分析索引命中情况。
- 汇总后统一调整联合索引的设计。
这套流程看起来简单,但真正坚持做的人很少。很多团队只有线上告警了才去查SQL,平时完全没有主动审查的意识。等到业务量上来再想优化索引,数据体量已经让ALTER TABLE的成本变得很高——加索引时需要重建索引结构,在大表上非常耗时,而且会锁表。
8.5 大表加索引的实操注意点
说到ALTER TABLE,这里必须单独提一下。在生产环境的大表上添加联合索引,不是一个简单的 ALTER TABLE ADD INDEX 就完事的问题。几个实际经验:
- 使用
pt-online-schema-change或gh-ost这类工具实现在线DDL,避免阻塞线上读写。 - 操作窗口尽量选择业务低峰期。
- 加索引完成后,用 EXPLAIN 重新验证几条核心SQL,确认执行计划符合预期。
- 如果索引需要回填大量数据,磁盘空间要提前评估——索引和表本身差不多大时,需要预留至少一倍的空间。
这些细节平时不一定能用到,但真要碰到一次,能帮你少踩很多坑。
最后想说的话
联合索引这个东西,属于典型的“看起来容易,用起来容易错”的知识点。它不像分布式事务那么难,也不像缓存一致性那么绕,但它覆盖了MySQL索引机制里最核心的底层逻辑。面试官喜欢问联合索引,本质上是在考察你有没有真正理解B+树索引的排序原理,而不仅仅是背了几个失效场景。
我个人的建议是:每次设计索引时,不要急着写SQL,先在脑子里把B+树的排序规则过一遍。联合索引列怎么排,查询条件怎么匹配,哪些列能被用于定位,哪些列只是用于过滤,哪些操作会发生回表。想清楚了再建索引,基本能避免90%以上的索引低效问题。
如果你正在准备面试,把上面这些内容整理成自己的语言,找几个案例自己动手建个表、用EXPLAIN跑一遍,比单纯背书要有效得多。联合索引的原理就那么几条,但理解和实践之间的差距,只有亲手踩过坑才能填平。
