Skip to content

MySQL 的覆盖索引是什么? ​

  • 标签:后端、MySQL、数据库

🏛 业务架构师 ​

覆盖索引指查询所需要的列全部包含在索引里,不需要回表到聚簇索引取整行数据,EXPLAIN 里表现为 Extra: Using index。

它解决的是隐藏成本问题。以电商为例:商品列表页每屏只展示标题、价格、主图 URL,如果这三列加上过滤列建成联合索引 (category_id, status, title, price, pic_url),千万级商品表按类目翻页时,直接在二级索引上就拿到全部数据,省掉每一行一次回表的随机 IO——大表上回表往往是查询耗时的大头,覆盖索引经常能把几百毫秒的查询压到几毫秒。

业务落地我抓三点:

  • 克制使用:索引列不能无限堆,写放大和索引膨胀是代价。只为高频、高价值查询做覆盖索引——用慢查询统计和接口调用量来决策,而不是给每个查询都配一个"完美索引";
  • 与 select * 的规范联动:代码规范禁止列表查询 select *,一方面给覆盖索引创造条件,另一方面减少网络与内存开销;
  • 主键尽量短:二级索引叶子节点存主键值,主键越短,索引越小,覆盖索引的性价比越高。

覆盖索引是典型的"用设计换硬件"的手段,在数据库容量逼近、又不想急着分库分表的阶段,往往是最便宜的优化路径。

🏗 系统架构师 ​

覆盖索引指查询需要的所有列(select、where、order by 涉及的列)都能从联合索引里拿到,不需要回表查主键索引,EXPLAIN 里表现为 Extra = Using index。

为什么值钱:回表是随机 IO,一次主键查找约 3~4 次页访问;覆盖索引把"索引 → 回表"变成"纯索引扫描",二级索引又比聚簇索引瘦(一页能放更多条),大范围扫描时 IO 可能差一个数量级。比如 select id, name from user where name = 'x',建 (name) 索引即覆盖,因为二级索引叶子节点本身带主键 id。

我的实践:

  • 高频的列表页查询(比如订单列表取 20 条),我会把 select 的字段收窄,按查询反推联合索引做覆盖,避免每行一次回表;
  • 但不为覆盖索引盲目加列——索引变宽写入放大、缓存命中下降,通常覆盖"最热的 1~2 条 SQL"就够,其余靠 KV 缓存兜住。

💻 全栈开发专家 ​

查询需要的字段全部包含在索引里,不需要回表查聚簇索引,EXPLAIN 的 Extra 会显示 Using index。

sql
-- 索引 idx_name_phone(name, phone)
SELECT name, phone FROM user WHERE name = '张三';  -- 覆盖索引,免回表
SELECT age        FROM user WHERE name = '张三';   -- age 不在索引里,要回表

工程价值:回表是随机 IO,一次查询多回几万次表性能差一个量级。我在优化后台列表接口时的常用手法就是把 select 的列"塞进"联合索引做覆盖:

sql
ALTER TABLE orders ADD INDEX idx_user_status(user_id, status, create_time, amount);
-- 列表页查 user_id + status,返回 create_time/amount,全程走索引

注意点:索引列多了写放大和空间开销也大,覆盖索引更适合读多的查询场景(后台报表、热点接口);同时主键默认包含在二级索引叶子节点里,SELECT id 天然覆盖。