索引下推ICP

索引下推(ICP)到底是什么?

你也许见过这样的 SQL 优化建议:"用 (name, age) 联合索引,查询 name LIKE '王%' AND age = 30 时,MySQL 5.6 之后的索引下推能减少回表。"

但你可能困惑:

  • 为什么没有索引下推时,age = 30 不能直接在索引里判断?
  • 索引下推到底改变了什么?

一、准备一个例子

user

id name age
1 王明 25
2 王红 30
3 王芳 28
4 李强 30
5 王伟 30
6 张丽 30

索引:(name, age) 联合索引。

查询:

sql 复制代码
SELECT * FROM user 
WHERE name LIKE '王%'   -- 前缀匹配,可以用索引
  AND age = 30;

目标:找出所有姓"王"且年龄为 30 的用户。


二、没有索引下推(MySQL 5.6 之前)

存储引擎 (InnoDB)只做一件事:根据 name LIKE '王%' 找到所有满足该条件的记录位置(主键 id)。

不关心 age = 30 这个条件 ------ 这是 Server 层的活儿。

流程如下:

  1. 存储引擎扫描联合索引,找到所有 name 以"王"开头的记录。

    索引内容(顺序按 name,name 相同时按 age):

    name age id
    王明 25 1
    王红 30 2
    王芳 28 3
    王伟 30 5
    李强 30 4
    张丽 30 6

    "王%"匹配到前 4 行:id = 1,2,3,5。

  2. 存储引擎用这 4 个 id 依次回表(去主键索引取完整行数据),将完整行返回给 Server 层。

  3. Server 层拿到 4 行数据后,再应用 age = 30 过滤,只保留 id=2 和 id=5 的两行。

结果:回表 4 次,实际只需要 2 行。浪费了 2 次随机 I/O。


三、有索引下推(MySQL 5.6+)

索引下推(Index Condition Pushdown, ICP) 改变分工:Server 层把 age = 30 这个条件也"下推"给存储引擎,让存储引擎在扫描索引时提前判断。

流程:

  1. 存储引擎扫描联合索引,找到 name LIKE '王%' 的记录。

    每读到一条索引记录(此时还没回表),立刻检查 age = 30 是否成立。

    • 读 (王明,25) → age=25 不满足 → 跳过,不回表。
    • 读 (王红,30) → 满足 → 记录 id=2,稍后回表。
    • 读 (王芳,28) → 不满足 → 跳过。
    • 读 (王伟,30) → 满足 → 记录 id=5。
  2. 只有满足 name LIKE '王%' AND age = 30 的 id(2 和 5)才去回表取完整行。

  3. Server 层直接拿到最终 2 行数据,无需再过滤(因为条件已经在索引里判断过了)。

结果:回表 2 次,减少了一半的随机 I/O。


四、你可能纠结的几个专业点

1. "为什么无 ICP 时,不能在索引里直接判断 age?"

因为老版本 MySQL 的存储引擎与 Server 层分工僵硬

  • 存储引擎只负责"根据索引查找条件找到记录位置并回表"。
  • Server 层负责所有 WHERE 条件的过滤。
  • 存储引擎不认识 age = 30(它没有被"下推")。

ICP 打破了这堵墙:Server 把部分条件传给存储引擎,让它在索引页内部提前过滤。

2. "不是说 LIKE '王%' 之后非等值匹配会停止,用不到 age 索引吗?那 ICP 怎么还能用?"

这里有两个不同概念:

  • 索引查找(ref / range)

    联合索引 (name, age) 中,name LIKE '王%' 只能用到 name 列来缩小扫描范围,age 列无法用于二分查找或范围跳跃,因为不同 name 下的 age 不是全局有序的。这就是"匹配停止"。

  • 索引下推(ICP)

    虽然不能利用 age 的有序性来加速查找,但扫描过程中 ,每一行索引记录本来就包含 age 字段。ICP 只是朴素地逐行判断 age = 30,不需要有序。它是在扫描完 name 范围后、回表前做的额外过滤。

打个比方:

  • 无 ICP 时:你拿着索引找到所有姓王的门牌号,挨个进屋(回表)问年龄。
  • 有 ICP 时:你在门口(索引里)先透过猫眼看一眼年龄,不是 30 的直接不敲门。

3. "为什么 b > 10 会导致 c 无法用于索引查找?"(进阶但常见)

这是一个很容易混淆的点。我们用一个更通用的例子来说明:

假设有联合索引 (a, b, c),查询:
SELECT * FROM t WHERE a = 1 AND b > 10 AND c = 5

索引的物理排序规则

  • 先按 a 排序 → 再按 b 排序 → 再按 c 排序。

数据示例(只显示索引列):

a b c
1 5 2
1 12 1
1 12 9
1 15 4
1 20 7
2 8 3

索引查找能做的是:

  • a=1:定位到第一个 a=1 的记录(1,5,2)。
  • b>10:因为 ba=1 范围内有序,可以跳过 b<=10 的记录,直接定位到第一个 b>10 的记录(1,12,1)。
    扫描起点就是 (1,12,1),然后沿着链表向后扫描,直到 a 不再是 1。

c=5 为什么不能用于查找?

因为 cb>10 这个范围内是无序 的。在扫描区间内,c 的取值是:1, 9, 4, 7 ...... 没有规律,所以无法用二分或跳步定位到 c=5 的位置。

换句话说,遇到第一个范围查询(b>10),该列之后的索引列(c)都无法用于缩小扫描范围 ,只能作为过滤条件

那 ICP 能做什么?

虽然 c 不能用于查找,但索引叶子节点中确实存了 c 的值。无 ICP 时,上面 4 行(b>10 的所有行)都会回表,然后 Server 发现 c=5 都不满足,白白回表。

有 ICP 时,存储引擎在扫描索引过程中,每读到一行 (a,b,c) 就立即判断 c=5,因为都不满足,所以一次回表都不做,直接返回空结果。

一句话区分

  • "无法用于查找" = 不能帮助快速跳过区间。
  • "ICP 可用" = 可以在索引内逐行过滤,减少回表。

4. "等值条件如 name='王' AND age=30 需要 ICP 吗?"

不需要。因为 name='王' 是等值,联合索引可以同时用上 name 和 age 两个列 来精确定位,直接就能定位到 (王,30) 这一条记录,几乎没有多余的扫描。

ICP 主要解决索引查找只能用前缀列 (如 LIKE<> 等非等值)时,后续列无法参与查找,但可以通过 ICP 减少回表。


五、一张图总结 ICP 前后流程

text 复制代码
【无 ICP】
索引扫描(name范围) → 得到 id 列表 [1,2,3,5] → 回表4次 → Server层过滤age=30 → 结果[2,5]

【有 ICP】
索引扫描(name范围) → 边扫描边判断age=30 → 得到 id [2,5] → 回表2次 → Server层直接返回

六、ICP 的适用场景(什么时候有效)

  • 联合索引,且查询条件只有索引前缀列能用于查找 (如 LIKE 'prefix%'col > value)。
  • 其余列(后缀列)的过滤条件比较严格,能过滤掉大部分行。
  • 回表代价高(比如表很大、非覆盖索引)。

不适用的情况:

  • 覆盖索引(不需要回表,ICP 没有收益)。
  • 分区表(部分版本限制)。
  • 条件中涉及子查询或存储函数。

七、例子

假设索引 (a, b, c),查询:
SELECT * FROM t WHERE a = 1 AND b > 10 AND c = 5

  1. 索引查找能用到哪几列?
  2. ICP 可以帮上什么忙?

  1. 索引查找能用 a = 1b > 10(因为 b > 10 是第一个范围条件,c 无法用于查找)。
  2. ICP 可以在索引扫描过程中,对满足 a=1 AND b>10 的每一行提前判断 c=5,只对通过的行回表。如果没有任何行 c=5,则回表次数降为 0。
相关推荐
小羊在睡觉8 小时前
Go与MySQL锁:索引失效陷阱
数据库·后端·mysql·golang
givemeacar9 小时前
MySQL数据库误删恢复_mysql 数据 误删
数据库·mysql·adb
Carino_U10 小时前
MySQL事务隔离机制&锁机制&MVCC详解
数据库·mysql
gechunlian8811 小时前
MySQL数据库的数据文件保存在哪?MySQL数据存在哪里
数据库·mysql
momin~11 小时前
MySQL-part1【初始数据库-数据库基础知识】超详细
数据库·mysql
Darkdreams12 小时前
MySQL四种备份表的方式
mysql·adb·oracle
渡我白衣12 小时前
【MySQL基础】(3):MySQL库与表的操作
android·数据库·人工智能·深度学习·神经网络·mysql·adb
dovens12 小时前
MYSQL批量UPDATE的两种方式
数据库·mysql
田超凡12 小时前
深入理解MySQL_8 Index 索引(上)
mysql·java-ee