跳到主要内容
约 7 分钟4 张卡

联合索引与覆盖索引

联合索引把多列按确定顺序组织。覆盖索引表示一个索引包含当前查询所需的列。本文讲解 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 二级索引通过聚簇键定位行,存储组织见 聚簇索引。

使用二级索引查询不在索引中的列时,执行过程分两步:

  1. 从二级索引找到符合条件的主键。
  2. 根据主键访问聚簇索引,读取完整行。

第二步称为回表。若二级索引命中很多记录,随机或离散回表会成为主要成本。

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 行之前的扫描量。

12. 参考资料​