联合索引与覆盖索引
联合索引把多列按确定顺序组织。覆盖索引表示一个索引包含当前查询所需的列。本文讲解 MySQL 8.0 InnoDB 的范围定位、回表、ICP 和排序推演。
1. 面试核心
先记住这几条,再读正文- 联合键顺序。 联合索引
(a,b,c)按 a、b、c 逐级排序,a=1 AND b>10可以构造连续扫描范围。卡 · 联合索引的键顺序 - 范围后的索引列。
a=1 AND b>10 AND c=5中的 c 仍能参与 ICP 过滤或覆盖取列。卡 · 范围定位、下推过滤与覆盖 - 覆盖与回表。 二级索引查询缺失列时通过聚簇键回表,覆盖查询从索引取得所需列。卡 · 二级索引与回表
- 索引排序。
(tenant_id,status,created_at,id)的前两列固定后,可按后两列顺序满足ORDER BY created_at DESC,id DESC。卡 · 排序、LIMIT 与列顺序 - 覆盖列的代价。 增加覆盖列会扩大索引,提高写入、存储与缓存成本。
2. 联合索引的键顺序
假设表上有索引 (tenant_id, status, created_at)。索引项先按 tenant_id 排序。tenant 相同时,索引项再按 status 排序。前两列都相同时,created_at 才是有序的。
因此,以下条件可以直接利用连续索引范围:
WHERE tenant_id = 10
AND status = 'PAID'
AND created_at >= '2026-08-01'
如果只给出 status,不同 tenant 的同一状态会分散在整棵索引中。传统最左前缀因此无法直接定位单个连续区间。这就是联合索引的左前缀特性。
3. 等值、范围与排序列
等值列固定前缀,范围列界定扫描区间。排序列需要与剩余键顺序匹配。完整设计流程见 索引设计。
例如 (tenant_id, created_at, status) 能按 tenant 和时间扫描。条件为 created_at > '2026-08-01' 时,后面的 status 用于扫描中的过滤。若主要查询按 tenant、status 等值过滤并按时间翻页,(tenant_id, status, created_at) 更合适。
跳跃扫描、索引条件下推和索引合并各有适用条件。实际采用的路径以执行计划为准。
4. 二级索引与回表
InnoDB 二级索引通过聚簇键定位行,存储组织见 聚簇索引。
使用二级索引查询不在索引中的列时,执行过程分两步:
- 从二级索引找到符合条件的主键。
- 根据主键访问聚簇索引,读取完整行。
第二步称为回表。若二级索引命中很多记录,随机或离散回表会成为主要成本。
5. 覆盖索引
若同一个索引包含查询所需的过滤列和返回列,它就覆盖这次查询:
SELECT created_at
FROM orders
WHERE tenant_id = 10 AND status = 'PAID';
索引 (tenant_id, status, created_at) 已包含查询所需数据。因此,查询具备从索引直接取列的条件。这对应 EXPLAIN Extra 中的 Using index。
MVCC 可见性检查仍有访问聚簇索引的路径。二级索引记录有删除标记时,InnoDB 会访问聚簇记录。若修改该页的事务对当前视图而言较新,也会如此。必要时,InnoDB 通过 undo 重建可见版本。
覆盖是索引与具体查询之间的关系。把所有返回列都塞进索引,会增加空间、缓存压力和写放大。应优先覆盖高频且性能敏感的窄查询。
6. 主键宽度与索引成本
二级索引携带聚簇键。主键越宽,所有二级索引的空间与缓存开销越大。机制与选择条件见 聚簇索引。
7. 索引访问的执行证据
索引访问的收益要结合扫描量、回表和排序判断。证据字段见 数据库索引,实测审查流程见索引设计。
8. 范围定位、下推过滤与覆盖
下面是独立的表结构示意。是否选中某条索引,仍取决于数据量和统计信息:
CREATE TABLE index_demo (
id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL,
status VARCHAR(16) NOT NULL,
created_at DATETIME NOT NULL,
payload VARCHAR(200),
KEY idx_tenant_time_status (tenant_id, created_at, status)
) ENGINE=InnoDB;
EXPLAIN SELECT payload FROM index_demo
WHERE tenant_id = 10 AND created_at > '2026-08-01'
AND status = 'PAID';
tenant_id 等值加 created_at 范围可以定位一段连续索引区间。status 不再进一步缩小该区间,但它仍在索引中。启用且满足 ICP 条件时,引擎可先用它过滤索引项。引擎再为剩余记录读取 payload,从而减少回表。Using index condition 就属于这种机制,它在回表前完成索引列过滤。
若将返回列改成 id, created_at, status,查询就具备覆盖条件。过滤与返回需要的列都已在索引项中。Using index 与 Using index condition 分别回答“数据是否足够”和“过滤在哪里做”。
前缀索引如 name(10) 只保存前 10 个字符;查询完整 name 时仍需要取得剩余内容。
9. 排序、LIMIT 与列顺序
假设主要查询是某租户已支付订单的最新列表:
SELECT id, created_at
FROM orders
WHERE tenant_id = 10 AND status = 'PAID'
ORDER BY created_at DESC, id DESC
LIMIT 20;
显式索引 (tenant_id, status, created_at, id) 的前两列被等值固定后,后两列可用于排序。同向 DESC 可以通过反向扫描满足。显式写出 id 便于分析稳定排序。实际设计也要考虑 InnoDB 已携带的主键,避免无意义的冗余定义。
若查询去掉 status 条件,索引先按不同状态分组。各状态内部分别按时间排列。若执行计划沿该路径取得所有状态的结果,就需要额外排序。也可针对该查询另设 (tenant_id, created_at, id)。方向混合的排序还要核对索引各列升降序。
LIMIT 20 限制返回行数。若扫描时大量记录被状态条件过滤,就要读取更多索引项。这样才能找到 20 条符合条件的记录。大 OFFSET 也需要跨过前面的记录。查询成本应以实际扫描量验证。
10. 常见问题
10.1 字符串前缀匹配
对可直接比较的字符串索引列,LIKE 'abc%' 可以形成前缀范围。后续列的使用取决于生成的访问区间。LIKE '%abc' 缺少固定前缀,需要扫描并过滤。
10.2 联合索引与单列访问
联合索引支持与其有序前缀匹配的范围定位。它也支持全索引扫描、覆盖和符合条件的跳跃扫描。某列频繁独立查询时,应检查现有路径的扫描范围。若扫描过宽,可以评估单列索引或另一组联合索引。最终要结合读写成本和真实查询频率取舍。
11 · 面试题卡片
11.1 什么是覆盖索引和回表,如何减少回表成本
出现公司:字节跳动
考察重点
- 理解InnoDB 聚簇索引与二级索引的叶子内容。
- 结合具体查询判断索引是否覆盖。
- 理解回表次数、索引宽度和写入成本的权衡。
参考回答
覆盖索引包含查询所需的列;二级索引缺少结果列时,要通过聚簇键读取完整行,这个过程称为回表。
通过调整联合索引列顺序,或加入少量高频返回列,可以减少扫描和取列回表。覆盖查询仍可能因 MVCC 可见性检查访问聚簇记录,例如二级索引记录有删除标记或页面被较新事务修改时。
索引越宽,存储、缓存和写入成本越高,因此要结合查询频率与实际执行计划判断收益。
11.2 联合索引 (a, b, c) 遇到 a=1、b>10、c=5,哪些列还起作用?
出现公司:暂无已核验的公开来源(本站自测)
考察重点
- 理解复合键的有序区间。
- 理解范围边界与剩余列过滤。
相关内容:联合索引的键顺序、范围定位、下推过滤与覆盖。
参考回答
a 的等值与 b 的范围构造扫描区间,c 可用于 ICP 过滤或覆盖取列。
不同 b 分组中的 c=5 分散在区间内部。继续按 c 过滤,可以减少取行工作。还应确认实际计划;条件书写成 c、b、a 的顺序不会改变索引自身顺序。
11.3 Using index 和 Using index condition 有什么区别?
出现公司:暂无已核验的公开来源(本站自测)
考察重点
- 理解覆盖取列与 ICP。
- 理解回表与 MVCC 的边界。
相关内容:范围定位、下推过滤与覆盖、覆盖索引。
参考回答
Using index 表示从索引取得查询所需列。
Using index condition 表示引擎在取完整行之前先按索引列过滤。前者仍受 MVCC 可见性检查影响。后者减少进入回表阶段的记录数,从而降低成本。
11.4 同一个联合索引,为什么加上或去掉一个等值条件会改变排序成本?
出现公司:暂无已核验的公开来源(本站自测)
考察重点
- 理解固定前缀后的顺序。
- 理解LIMIT 与实际扫描量。
相关内容:排序、LIMIT 与列顺序。
参考回答
联合索引按列逐级分组,只有前导等值列固定后,后续列的顺序才能直接满足目标排序。
对 (tenant_id,status,created_at,id),tenant_id 和 status 都固定时,可直接按后两列取最近记录。去掉 status 条件后,各状态组内部有序,直接串接却不保证全局时间顺序。
用执行计划确认是否出现 filesort,再决定是否增加适合主要查询的索引;过滤条件和大 OFFSET 仍可能增加返回 LIMIT 行之前的扫描量。