覆盖索引指查询所需所有字段均包含在索引中,无需回表查主键索引;它通过避免二次I/O提升性能,尤其利于宽表和大字段场景,但需合理设计联合索引顺序与列选择。
覆盖索引是什么,为什么它能跳过回表
覆盖索引不是新索引类型,而是查询恰好只用到索引中已有的列(包括 WHERE、ORDER BY、SELECT 所有字段),无需再回到主键索引(聚簇索引)查整行数据。MySQL/PostgreSQL 在执行计划里看到
就是命中了覆盖索引;如果出现
,说明 WHERE 条件也走索引下推,效率更高。
回表是性能杀手:InnoDB 主键索引叶子节点存整行数据,二级索引叶子节点只存主键值;查完二级索引还得拿主键去主键索引里再查一次,I/O 翻倍
覆盖索引把“查索引 + 查数据”压缩成“只查索引”,尤其对宽表、大字段(
、
)效果显著
不是所有 SELECT * 都能被覆盖——只要 SELECT 里出现索引没包含的列,立刻失效
怎么写覆盖索引:列顺序和冗余字段取舍
覆盖索引本质是为特定查询定制的联合索引,顺序必须匹配查询模式。例如查询
,推荐建:
注意不同数据库语法差异:
PostgreSQL 支持
子句,把
放进索引但不参与排序/查找,节省空间又满足覆盖
MySQL 没有
,只能全塞进索引列:
——但要注意,加在最后的
不参与范围查找,只用于覆盖;如果
是范围条件(如
),它后面的列就失效了
别把
当目标:索引列越多越占空间、越拖慢 INSERT/UPDATE,优先保 WHERE 条件列 + ORDER BY 列 + 最常查的 1–2 个返回列
EXPLAIN 看不见的坑:WHERE 条件隐式类型转换毁掉索引
明明建了
索引,
却显示
或
?常见原因:
是
,但查询写成
(字符串)→ MySQL 自动转类型,索引失效
是
或
,却用
(而实际值是 1)→ 同样触发隐式转换
开头带通配符:
无法用索引前缀,哪怕
是索引首列
验证方法很简单:
重点看
是否非空、
是否接近表总行数、有没有
或
覆盖索引不是银弹:什么时候它反而更慢
覆盖索引对读友好,但对写有代价,且某些场景收益极低:
表更新频繁(每秒百次以上 INSERT/UPDATE):索引越多,维护成本越高;每个索引都要写磁盘、占 Buffer Pool,可能挤占热点数据缓存
查询返回字段极少但条件列本身就很宽:比如
,给这个列建索引意义不大,更别说覆盖
使用了函数或表达式:
无法走
索引,自然也无法覆盖;得改成
联合索引中存在高基数列(如
)在前,低基数列(如
,只有 3 个值)在后:虽然能覆盖,但索引区分度差,B+ 树层级深,实际扫描行数未必少
真正要盯住的,是慢查询日志里
远大于
的那些语句——它们才最值得你动手建覆盖索引。
Using indexUsing where; Using indexTEXTBLOBSELECT user_id, status, created_at FROM orders WHERE shop_id = ? AND status IN (?, ?) ORDER BY created_at DESCCREATE INDEX idx_shop_status_created ON orders (shop_id, status, created_at) INCLUDE (user_id);INCLUDEuser_idINCLUDE(shop_id, status, created_at, user_id)user_idcreated_at> '2024-01-01'SELECT *(user_id, status)EXPLAINtype: ALLkey: NULLuser_idBIGINTWHERE user_id = '123'statusENUMTINYINTWHERE status = 'active'LIKEWHERE name LIKE '%abc'nameEXPLAIN FORMAT=TRADITIONAL SELECT ...;keyrowsUsing filesortUsing temporaryWHERE long_json_col LIKE '%foo%'WHERE YEAR(created_at) = 2024created_atcreated_at >= '2024-01-01' AND created_at < '2025-01-01'user_idstatusRows_examinedRows_sent