面试频率:★★★★☆
工作频率:★★★★☆
⚡ 30 秒速记
ICP:
Index Condition Pushdown
中文:
索引条件下推 / 索引下推
一句话:
在使用二级索引查询时,把原本需要回表后才能进行的部分条件判断,下推到存储引擎的索引层,利用索引中已有的字段提前过滤数据,从而减少不必要的回表。
核心:
没有ICP:
二级索引
↓
找到候选记录
↓
回表
↓
拿完整数据
↓
判断其他条件
有ICP:
二级索引
↓
找到候选记录
↓
利用索引字段提前过滤
↓
符合条件
↓
回表
所以:
ICP核心作用
=
减少回表
EXPLAIN:
Using index condition
↓
使用了ICP
注意:
Using index
≠
Using index condition
一、什么是 ICP?
ICP 全称:
Index Condition Pushdown
中文:
索引条件下推
它是一种:
查询优化机制
核心思想:
能在索引层判断的条件,就尽量先判断,过滤掉不符合条件的数据以后,再对剩余记录进行回表。
目的:
减少回表
↓
减少数据页访问
↓
提高查询效率
二、先复习什么是回表
假设:
CREATE INDEX idx_name_age
ON user(name, age);
这是:
二级联合索引
叶子节点可以简单理解为:
name + age + 主键id
例如:
张三 18 → id=1
张三 20 → id=2
张四 30 → id=3
张五 20 → id=4
执行:
SELECT *
FROM user
WHERE name = '张三';
因为:
SELECT *
需要完整行数据。
而二级索引只有:
name
age
主键id
可能还缺少:
address
phone
email
...
所以:
二级索引
↓
得到主键id
↓
查询聚簇索引
↓
获取完整行
这个过程就是:
回表
三、没有 ICP 会怎么样?
假设联合索引:
INDEX(name, age)
查询:
SELECT *
FROM user
WHERE name LIKE '张%'
AND age = 20;
索引数据:
张三 18 → id=1
张三 20 → id=2
张四 30 → id=3
张五 20 → id=4
张六 25 → id=5
name LIKE '张%':
可以确定一个索引扫描范围
于是找到:
张三18
张三20
张四30
张五20
张六25
如果没有 ICP,可以粗略理解为:
根据name找到候选索引记录
↓
得到主键
↓
回表
↓
读取完整行
↓
再判断:
age = 20?
例如:
张三18
↓
回表
↓
age != 20
↓
丢掉
张三20
↓
回表
↓
age = 20
↓
返回
张四30
↓
回表
↓
age != 20
↓
丢掉
问题:
很多数据明明age不符合
↓
却已经回表了
产生:
大量不必要的回表
四、有 ICP 会怎么样?
还是:
SELECT *
FROM user
WHERE name LIKE '张%'
AND age = 20;
索引:
(name, age)
因为:
age
本身就在:
二级索引
里面。
所以 MySQL 可以:
先在索引层判断age
流程:
根据name找到候选索引记录
↓
直接读取索引里的age
↓
判断age = 20?
↙ ↘
不是 是
↓ ↓
丢掉 回表
↓
完整数据
例如:
张三18
↓
索引层判断
↓
18 != 20
↓
直接丢掉
↓
不回表
张三20
↓
索引层判断
↓
20 = 20
↓
回表
↓
完整数据
张四30
↓
索引层判断
↓
30 != 20
↓
直接丢掉
↓
不回表
这样:
不符合age条件的数据
↓
根本不用回表
五、ICP 最大的作用
假设:
name LIKE '张%'
找到了:
10000条候选记录
其中:
age = 20
只有:
100条
没有 ICP
可以粗略理解:
10000条候选记录
↓
大量回表
↓
拿完整数据
↓
判断age
↓
最终100条符合
有 ICP
10000条候选索引记录
↓
索引层判断age
↓
过滤掉9900条
↓
剩100条
↓
只让符合条件的记录回表
所以:
ICP
↓
减少不必要的回表
这就是最核心的意义。
六、ICP 和联合索引有什么关系?
假设:
INDEX(name, age)
索引内部:
先按照name排序
↓
name相同再按照age排序
执行:
WHERE name LIKE '张%'
AND age = 20;
这里:
name
可以用于:
确定索引扫描范围
而:
age
虽然不一定能够继续缩小 B+ Tree 的扫描范围,但:
age就在索引里面
所以:
仍然可以拿来过滤
这就是:
ICP
七、为什么范围条件之后的字段不一定完全没用?
这是理解 ICP 非常重要的一点。
很多人学最左匹配以后会背:
范围查询之后
↓
后面的索引字段全部失效
这个说法太绝对。
例如:
INDEX(name, age)
查询:
WHERE name LIKE '张%'
AND age = 20;
可以理解:
name
↓
用于确定索引扫描范围
而:
age
↓
可能不能像连续等值匹配那样进一步缩小扫描范围
但是:
age还在索引里面
所以:
可以用于索引层过滤
从而:
减少回表
因此:
不能继续缩小索引扫描范围
≠
这个字段完全没用了
八、ICP 和最左匹配的区别
最左匹配主要解决:
哪些索引字段可以帮助确定 B+ Tree 的查找范围?
ICP 主要解决:
已经找到候选索引记录以后,能不能利用索引中的其他条件提前过滤,从而减少回表?
所以:
最左匹配
↓
确定查哪里
ICP
↓
候选数据里面
先过滤哪些
九、用一个例子理解
索引:
(name, age)
数据:
张三18
张三20
张四25
张五20
张六30
李四20
王五20
查询:
SELECT *
FROM user
WHERE name LIKE '张%'
AND age = 20;
第一步
根据:
name LIKE '张%'
找到:
张三18
张三20
张四25
张五20
张六30
这一步:
利用索引确定扫描范围
第二步
因为:
age
也在索引:
(name,age)
里面。
所以索引层直接判断:
18 ❌
20 ✅
25 ❌
20 ✅
30 ❌
剩下:
张三20
张五20
第三步
只对:
张三20
张五20
进行:
回表
获取:
完整行数据
十、为什么 ICP 能提升性能?
主要减少:
回表次数
回表意味着:
二级索引
↓
得到主键
↓
再次访问聚簇索引
↓
获取完整数据
如果:
候选记录很多
大量回表:
成本可能很高
ICP:
索引层提前过滤
↓
减少候选数据
↓
减少回表
↓
减少数据页访问
所以性能可能更好。
十一、EXPLAIN 怎么看 ICP?
执行:
EXPLAIN
SELECT *
FROM user
WHERE name LIKE '张%'
AND age = 20;
如果:
Extra
出现:
Using index condition
通常表示:
使用了ICP
也就是:
Index Condition Pushdown
十二、Using index 是什么?
你前面已经学过:
Using index
通常表示:
覆盖索引
例如:
INDEX(name, age)
执行:
SELECT name, age
FROM user
WHERE name = '张三';
需要:
name
age
索引:
name
age
全部都有。
所以:
直接从索引获取数据
不需要:
回表
可能看到:
Using index
十三、Using index condition 是什么?
Using index condition
表示:
ICP
流程:
索引找到候选记录
↓
索引层提前过滤
↓
符合条件
↓
再回表
所以:
Using index condition
并不代表:
完全不用回表
而是:
尽量先过滤,减少需要回表的数据。
十四、Using index 和 Using index condition 区别
这个面试很容易问。
Using index
覆盖索引
↓
查询需要的数据
索引里面全部都有
↓
直接返回
↓
通常不用回表
Using index condition
索引下推 ICP
↓
利用索引字段
提前过滤
↓
符合条件的数据
↓
再回表
所以:
Using index
↓
重点:
避免回表
而:
Using index condition
↓
重点:
减少回表
这句话非常好记:
覆盖索引:避免回表。
索引下推:减少回表。
十五、覆盖索引和 ICP 哪个更好?
例如:
INDEX(name, age)
查询:
SELECT name, age
FROM user
WHERE name LIKE '张%'
AND age = 20;
需要的数据:
name
age
索引里面:
全部都有
那么:
根本不需要获取完整行
可以直接:
从索引返回
这种:
覆盖索引
通常比:
先过滤
↓
再回表
更加直接。
所以:
能覆盖
↓
直接覆盖
ICP 更重要的场景通常是:
最终还是需要回表
↓
但是可以先过滤
↓
减少回表次数
十六、什么条件才能在索引层判断?
核心:
这个条件需要的数据
↓
必须能从当前索引记录中获得
例如索引:
(name, age)
可以在索引中直接获得:
name
age
主键
如果条件:
age = 20
索引里面有:
age
所以:
有机会在索引层判断
但是如果:
address = '杭州'
而索引:
(name, age)
里面没有:
address
那么:
索引层没有address
就不能靠这个索引直接完成:
address条件判断
通常还是需要:
回表
↓
拿address
↓
再判断
十七、ICP 主要用于二级索引
ICP 的价值主要体现在:
二级索引
↓
需要回表
↓
提前过滤
↓
减少回表
因为:
聚簇索引叶子节点
↓
本身就是完整行数据
而二级索引:
叶子节点不是完整行
所以:
减少回表
才特别有意义。
十八、完整流程对比
没有 ICP
联合二级索引
↓
找到10000条候选记录
↓
得到10000个主键
↓
大量回表
↓
拿完整数据
↓
判断age
↓
剩100条
有 ICP
联合二级索引
↓
找到10000条候选索引记录
↓
索引层判断age
↓
剩100条
↓
100条回表
↓
拿完整数据
核心区别:
没有ICP:
先回表
再过滤
有ICP:
先过滤
再回表
十九、和 B+ Tree 串起来
现在把前面的知识串起来:
联合索引
(name, age)
↓
B+ Tree
↓
根据name确定扫描范围
↓
扫描叶子节点
↓
索引里面还有age
↓
ICP判断age
↓
过滤不符合的数据
↓
得到主键
↓
回表
↓
聚簇索引
↓
完整行数据
所以:
B+ Tree
↓
联合索引
↓
最左匹配
↓
ICP
↓
减少回表
二十、和 EXPLAIN 串起来
查询:
EXPLAIN
SELECT *
FROM user
WHERE name LIKE '张%'
AND age = 20;
分析:
type
↓
看访问方式
key
↓
看使用哪个索引
rows
↓
看预计扫描多少
Extra
↓
看有没有:
Using index condition
如果:
Using index condition
说明:
使用ICP
二十一、一张图记住 ICP
二级索引
↓
找到候选索引记录
↓
索引中还有过滤条件?
↙ ↘
有 没有
↓ ↓
索引层先判断 需要其他处理
↓
符合条件?
↙ ↘
不符合 符合
↓ ↓
丢掉 回表
↓
聚簇索引
↓
完整数据
二十二、最重要的三个概念区别
回表
二级索引
↓
主键
↓
聚簇索引
↓
完整行
覆盖索引
查询需要的数据
↓
二级索引全部都有
↓
直接返回
↓
避免回表
ICP
最终还需要回表
↓
但是索引里有部分过滤条件
↓
索引层先过滤
↓
符合的才回表
↓
减少回表
一句话:
回表
=
去主键索引拿完整数据
覆盖索引
=
不用回表
ICP
=
少回表
二十三、完整知识链
现在 MySQL 索引这部分可以串成:
MySQL索引
↓
B+ Tree
↓
聚簇索引 / 二级索引
↓
二级索引查询
↓
需要完整数据
↓
回表
优化方式:
查询字段索引里全有
↓
覆盖索引
↓
避免回表
如果不能覆盖:
最终还是要回表
↓
索引中存在其他过滤字段
↓
ICP
↓
索引层提前过滤
↓
减少回表
所以:
覆盖索引
↓
最好:
直接不回表
ICP
↓
不能完全避免回表时:
尽量少回表
🎤 面试回答
问:
什么是索引下推 ICP?
答:
ICP 全称 Index Condition Pushdown,也叫索引条件下推。
在使用二级索引查询时,如果部分 WHERE 条件涉及的字段已经存在于当前索引中,MySQL 可以把这些条件下推到存储引擎的索引层进行判断。
这样可以先过滤掉不符合条件的索引记录,再对符合条件的数据进行回表,从而减少不必要的回表和数据页访问,提高查询效率。
在 EXPLAIN 中,如果 Extra 出现
Using index condition,通常表示使用了 ICP。
🎯 面试追问
Q1:ICP 全称是什么?
答:
Index Condition Pushdown
中文:
索引条件下推
Q2:ICP 主要解决什么问题?
答:
减少不必要的回表
Q3:ICP 为什么可以减少回表?
答:
因为:
索引里面已经存在部分WHERE条件需要的字段
↓
可以在索引层先判断
↓
不符合直接丢掉
↓
符合的才回表
Q4:没有 ICP 和有 ICP 有什么区别?
答:
没有ICP:
先回表
↓
再过滤
有ICP:
先在索引层过滤
↓
再回表
Q5:EXPLAIN 怎么看 ICP?
答:
看:
Extra
如果出现:
Using index condition
通常表示:
使用了ICP
Q6:Using index 和 Using index condition 一样吗?
答:
不一样
Using index
↓
通常表示覆盖索引
↓
避免回表
Using index condition
↓
ICP
↓
减少回表
Q7:ICP 是完全不回表吗?
答:
不是
ICP:
减少回表
覆盖索引:
可以避免回表
Q8:为什么 ICP 经常和联合索引一起出现?
答:
因为联合索引:
包含多个字段
有些字段:
用于确定索引扫描范围
其他索引字段:
可能还可以用于提前过滤
从而:
减少回表
Q9:不满足继续缩小索引范围的字段是不是完全没用?
答:
不一定
虽然它可能:
不能继续缩小B+ Tree扫描范围
但是如果字段:
存在于当前索引
可能:
通过ICP参与索引层过滤
Q10:索引里没有的字段能直接用于 ICP 过滤吗?
例如:
索引:
(name, age)
条件:
address = '杭州'
索引里面没有:
address
所以不能直接通过这个索引记录判断:
address条件
通常需要:
回表
获取完整数据后再处理。
⚠️ 易错点
1. ICP 不是覆盖索引
覆盖索引
=
避免回表
ICP
=
减少回表
不要混淆。
2. Using index condition 不是“不回表”
错误:
Using index condition
↓
不用回表
正确:
Using index condition
↓
索引层提前过滤
↓
减少回表
3. 后面的联合索引字段不一定完全没用
例如:
INDEX(name, age)
查询:
WHERE name LIKE '张%'
AND age = 20;
age 即使不能像连续等值匹配一样继续缩小扫描范围:
仍可能参与ICP过滤
所以不要死背:
范围查询以后
↓
后面所有字段彻底失效
4. ICP 的核心不是“让 SQL 走索引”
ICP 的核心是:
已经在使用索引查询候选记录
↓
尽量利用索引中的条件
↓
提前过滤
↓
减少回表
❓ 自测
-
ICP 全称是什么?
-
ICP 中文叫什么?
-
ICP 主要解决什么问题?
-
什么是回表?
-
没有 ICP 时通常什么时候判断其他过滤条件?
-
有 ICP 时过滤发生在哪里?
-
为什么 ICP 可以减少回表?
-
ICP 能完全避免回表吗?
-
什么机制可以避免回表?
-
覆盖索引和 ICP 有什么区别?
-
Using index通常表示什么? -
Using index condition表示什么? -
Using index和Using index condition一样吗? -
联合索引为什么适合使用 ICP?
-
INDEX(name,age)中 age 不能继续缩小扫描范围时,是不是一定完全没用了? -
什么情况下 age 还可能参与过滤?
-
索引中不存在的字段能直接通过当前索引记录进行 ICP 判断吗?
-
为什么减少回表可以提高性能?
-
ICP 和最左匹配分别解决什么问题?
-
EXPLAIN 怎么判断是否使用了 ICP?
-
“覆盖索引避免回表,ICP 减少回表”这句话是什么意思?
-
INDEX(name,age)查询name LIKE '张%' AND age=20时,name 和 age 分别可能起什么作用?