详解索引下推

索引下推(Index Condition Pushdown,ICP)

MySQL 5.6 版本引入,InnoDB / MyISAM 都支持,默认开启。

概括:

在索引遍历过程中,把 WHERE 里能用到索引字段的条件,直接在索引层过滤,减少回表次数。

一、没有 ICP 的时候怎么做

假设联合索引:idx_name_age (name, age) 表结构:

sql 复制代码
CREATE TABLE user(
    id INT PRIMARY KEY,
    name VARCHAR(20),
    age INT,
    phone VARCHAR(11),
    INDEX idx_name_age(name, age)
);

查询 SQL:

sql 复制代码
SELECT * FROM user WHERE name='张三' AND age>18;

无 ICP(5.6 之前)执行流程

联合索引叶子节点保存 (name, age, 主键id)

  1. 根据 name='张三' 在索引树找到所有匹配name='张三'的索引记录;
  2. 不判断 age 条件,拿着这些记录里的主键 id,全部回表,读取完整行数据;
  3. 回到服务层,再过滤 age>18;
  4. 返回结果。

问题:很多name='张三'但是age<=18的数据,白白做了回表,IO 开销大。

开启 ICP 之后

  1. 根据 name='张三' 在索引树找到匹配索引记录;
  2. 在索引层直接判断索引字段条件 age>18;
  3. 只有索引记录同时满足 name 和 age,才拿主键 id 去回表;不满足直接丢弃;
  4. 回表读取完整行,返回。

✅ 核心优化点:在索引页就过滤掉一部分数据,减少回表次数。

二、ICP 适用条件

  1. 只能用于二级索引,主键索引不生效(主键索引本身就存完整数据,不存在回表)
  2. WHERE 条件里,部分字段在索引中,部分不在索引中
  3. 条件是范围查询之后的索引列(联合索引最左匹配,范围后面的字段无法用于索引查找,但可以用 ICP 过滤)
  4. 不能是聚合函数、子查询;
  5. SELECT 只查索引字段(覆盖索引)时,不需要回表,ICP 就没意义。

❗ 关键区分:

  • 索引查找(Index seek) :用来定位索引起始位置,只能用最左前缀,遇到范围> < between就停止;
  • 索引下推 ICP :范围后面的索引字段,不能用来定位起点,但可以用来过滤。

三、经典场景示例

场景 1:联合索引 + 范围查询(ICP 最典型场景)

索引:idx_a_b_c(a, b, c) SQL:

sql 复制代码
SELECT * FROM t WHERE a=10 AND b>20 AND c=30;

联合索引最左匹配规则: a=10 等值,b>20 范围,b 之后的 c 无法参与索引查找。

  1. a=10:等值匹配 → 定位到所有 a=10 的这一段索引区间 ✅
  2. b>20:范围查询 在 a=10 这一组里,找到第一个 b>20 的位置,然后顺序向后扫描一段连续索引 。 ⚠️ 重点:经过 b>20 这个范围之后,这一段扫描出来的数据里,b 不再是一个固定值,b 是 21、22、23...... 不断变化。

    原本只有当 b 固定不变的时候,c 才是有序的;现在 b 五花八门,c 在这段范围里不再全局有序!

联合索引里,c 的有序性,仅建立在前面所有字段全部相等的前提下。

  • a 固定,b 固定 → c 有序
  • a 固定,b 是一个范围(多个不同 b)→ c 无序

索引想要快速定位 c=30,必须 c 是有序的,才能二分查找。 现在 c 乱序了,无法利用索引快速过滤 c,只能:

通过索引找到 a=10 AND b>20 的所有行,回表取出完整数据,再在内存里过滤 c=30

对比演示,更好理解

索引 idx(a,b,c)

  1. where a=10 and b=20 and c=30 a 等值,b 等值,c 等值 → 三列全部走索引定位 ✅
  2. where a=10 and b>20 and c=30 a 等值,b 范围 → b 之后 c无法索引定位,只能扫描后过滤 c ❌
  3. where a=10 and b=20 and c>30 a 等值,b 等值,c 范围 → c 是第一个范围,c 后面无字段,没问题 ✅

优化方案

如果业务经常 a=xx AND b>xx AND c=xx,

调换索引顺序: 把等值条件放范围前面:idx_a_c_b(a,c,b) 条件:a=10 AND c=30 AND b>20

  • a 等值,c 等值,b 范围 等值列全部放在范围列之前,就可以充分利用索引。

没有 ICP: 找到所有 a=10, b>20 的索引记录 → 全部回表 → 在 MySQL 服务层过滤 c=30。

开启 ICP: 找到 a=10, b>20 的索引记录 → 在索引层直接判断 c=30,不满足直接丢掉,只把满足 c=30 的记录回表。

这里 c 在范围 b> 后面,不能用于索引定位,但是可以 ICP 过滤。

看执行计划 explain,Extra 字段会出现 Using index condition,代表 ICP 生效。

sql 复制代码
Extra: Using index condition

区分:

Using index:覆盖索引,不需要回表;

Using index condition:索引下推 ICP; 两个可以同时出现。

场景 2:ICP 不生效的场景

① 条件字段不在索引里

索引 idx_a_b(a,b)

sql 复制代码
SELECT * FROM t WHERE a=1 AND b>2 AND d=5; -- d不在索引

d=5 无法下推,只能回表后过滤;只有 a、b 可以在索引层处理。

② 覆盖索引,不需要回表
复制代码
SELECT a,b,c FROM t WHERE a=10 AND b>20 AND c=30;

查询字段全部在索引里,不需要回表。此时 ICP 没有收益,Extra 不会出现 Using index condition。

③ 主键索引

主键索引叶子节点保存整行数据,本身没有回表操作,ICP 无效。

④ 子查询、函数操作
复制代码
SELECT * FROM t WHERE a=10 AND b>20 AND DATE(c)='2026-01-01';

DATE(c) 函数,字段发生隐式转换,ICP 无法下推。

四、ICP 的局限性

  1. ICP 只是过滤,不能减少索引扫描行数(explain 里的 rows 是预估扫描索引行数,ICP 不会减少这个 rows,只会减少回表次数)
  2. ICP 只能过滤索引上存在的字段,非索引字段条件必须回表后过滤
  3. 对于 InnoDB,ICP 只支持二级索引;
  4. ICP 无法优化排序、分组。

五、开关控制

复制代码
-- 查看是否开启ICP
show variables like 'optimizer_switch';

-- 关闭ICP(测试对比用)
set optimizer_switch = 'index_condition_pushdown=off';
-- 开启ICP
set optimizer_switch = 'index_condition_pushdown=on';

六、一句话总结面试版

索引下推 ICP 是 MySQL 5.6 引入的优化,针对二级索引。

当联合索引遇到范围查询后,后面的索引字段不能用来定位索引起点,但可以在索引页提前过滤数据,减少回表次数,降低随机 IO;

执行计划 Extra 显示Using index condition代表生效。ICP 不改变索引扫描行数,只减少回表。

七、面试常问对比

  1. ICP 和覆盖索引的区别?

ICP:减少回表次数;覆盖索引:完全不需要回表。二者可以同时存在。

  1. ICP 会不会减少 explain 里的 rows?

不会,rows 是预估索引扫描行数,ICP 只是扫描到索引记录时提前过滤,不减少索引扫描数量。

相关推荐
Arcai10241 小时前
PostgreSQL数据备份和恢复完全指南
数据库·postgresql·ffmpeg·备份一体机·备份软件·smartstor·智备科技
闲云自留地3 小时前
主库倒下 30 秒自动扶正:MySQL MHA 高可用实战
数据库·mysql
许彰午12 小时前
01-Oracle数据库简介与版本选择
数据库·oracle
菜鸟~noob23312 小时前
【电子战】第三季预告—波武器与电子系统强力毁伤
开发语言·数据库·matlab·电子战
Seraphina3614 小时前
SQL注入(二)盲注
网络·数据库·经验分享·笔记·sql
盟接之桥15 小时前
AI助手:你的7×24小时智能管家——让异常预警无处不在
大数据·网络·数据库·人工智能·制造·ai编程
waoooqwe15 小时前
问卷样本真实吗
大数据·数据库·算法·数据分析
可乐ea16 小时前
Agent 评测换判据:用终态数据库状态判定任务是否完成
数据库·ui·oracle·mcp工具·agent评测·终态断言·智能体可靠性
l1t16 小时前
DeepSeek总结的一个不含数据的 DuckDB 数据库
服务器·数据库·duckdb