目录
[1.1、传统MySQL 架构](#1.1、传统MySQL 架构)
[2.3、ICP 工作原理](#2.3、ICP 工作原理)
[3、验证 ICP 效果](#3、验证 ICP 效果)
[3.2、关闭 ICP](#3.2、关闭 ICP)
[3.3、开启 ICP(默认)](#3.3、开启 ICP(默认))
[4. ICP 适用条件与限制](#4. ICP 适用条件与限制)
[4.4、索引最大化 ICP 效果](#4.4、索引最大化 ICP 效果)
引子:一个慢查询引发的思考
假设你负责一个电商系统,用户反馈"订单查询很慢"。你发现以下 SQL 执行时间长达 2 秒:
sql
-- 查询用户 123 的"已支付"订单
SELECT * FROM orders
WHERE user_id = 123
AND status LIKE 'paid%';
表结构如下:
sql
CREATE TABLE orders (
id INT PRIMARY KEY,
user_id INT NOT NULL,
status VARCHAR(20) NOT NULL, -- 值如 'paid_success', 'paid_failed'
amount DECIMAL(10,2),
create_time DATETIME,
KEY idx_user_status (user_id, status) -- 复合索引
);
问题来了:
- 已有
(user_id,status)索引,为什么还慢? - 如何优化?
如下所示:

💡 本文将带你一步步揭开谜底------答案就在 MySQL 的"索引下推"机制中。
1、架构设计背景
1.1、传统MySQL 架构
MySQL 由 Server 层 和 存储引擎层(如 InnoDB)组成:
-
Server 层:解析 SQL、优化器、执行器
-
存储引擎层:数据存储、索引管理
如下所示:

1.2、执行流程
对于上述查询,MySQL 5.6 之前的执行流程:

关键问题:
- InnoDB 返回了 1000 条无用数据(其中 200 条 status 不符合)
- Server 层做了无谓的过滤
- 回表次数 = 800 次(但本可更少)
2、索引下推(ICP)
2.1、官方定义
把 WHERE 条件中"能用索引过滤的部分",直接交给存储引擎处理,而不是全部返回给 Server 层再过滤。
如下所示:

2.2、执行流程
如下所示:

效果:
- InnoDB 只返回 800 条有效数据
- Server 层无需额外过滤
- 回表次数不变,但减少了无用数据传输
2.3、ICP 工作原理
1、什么条件下能下推?
ICP 能下推的条件必须满足:
-
使用二级索引(非主键索引)
-
WHERE 条件包含索引列
-
条件类型支持:
-
等值查询(
=) -
范围查询(
>,<, between) -
like 前缀匹配('%xxx%')
-
2、为什么只对"非前导列"生效?
在复合索引 (A, B, C) 中:
-
前导列 A :用于索引定位(如 A
=1) -
非前导列 B/C :用于 ICP 过滤(如 B
>10)
索引
(user_id,status)
user_id
=123 → 索引定位(必须)status like
'paid%'→ ICP 过滤(优化点)
2.4、与覆盖索引的区别
如下所示:

两者可同时生效:如果查询同时满足覆盖索引 + ICP,性能最佳!
3、验证 ICP 效果
3.1、准备测试环境
如下所示:
sql
-- 创建测试表
CREATE TABLE employees (
id INT PRIMARY KEY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
department VARCHAR(50) NOT NULL,
salary INT NOT NULL,
KEY idx_name_dept (first_name, last_name, department)
);
-- 插入 10 万条测试数据(略)
3.2、关闭 ICP
SQL如下所示:
sql
-- 关闭 ICP
SET optimizer_switch='index_condition_pushdown=off';
-- 执行查询
EXPLAIN SELECT * FROM employees
WHERE first_name = 'John'
AND last_name LIKE 'S%'
AND department = 'IT';
执行计划输出:
sql
+----+-------------+------------+------------+------+-----------------+-----------------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+-----------------+-----------------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | employees | NULL | ref | idx_name_dept | idx_name_dept | 152 | const | 1000 | 10.00 | Using where |
+----+-------------+------------+------------+------+-----------------+-----------------+---------+-------+------+----------+-------------+
关键 :Extra =Using where → 无 ICP
3.3、开启 ICP(默认)
SQL如下所示:
sql
-- 开启 ICP(默认)
SET optimizer_switch='index_condition_pushdown=on';
EXPLAIN SELECT * FROM employees
WHERE first_name = 'John'
AND last_name LIKE 'S%'
AND department = 'IT';
执行计划输出:
sql
+----+-------------+------------+------------+------+-----------------+-----------------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+-----------------+-----------------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | employees | NULL | ref | idx_name_dept | idx_name_dept | 152 | const | 1000 | 10.00 | Using index condition |
+----+-------------+------------+------------+------+-----------------+-----------------+---------+-------+------+----------+-----------------------+
✅ 关键 :Extra =Using index condition → ICP 已生效!
3.4、性能对比
如下所示:

性能提升 3 倍!
4. ICP 适用条件与限制
4.1、支持的场景
| 条件类型 | 示例 | 是否支持 |
|---|---|---|
| 等值查询 | status='paid' |
✅ |
| 范围查询 | salary > 5000 | ✅ |
| like 前缀 | last_name like 'Smith%' | ✅ |
| 复合索引 | (A, B) 中 B 列过滤 |
✅ |
4.2、不支持的场景
| 场景 | 原因 | 替代方案 |
|---|---|---|
| 函数操作 | where year(create_time) = 2023 | 建函数索引(MySQL 8.0+) |
| 后缀 like | last_name like '%Smith' | 全文索引 |
| 子查询 | 复杂条件无法下推 | 重写为 JOIN |
| MyISAM 表 | 仅 InnoDB 支持 | 迁移到 InnoDB |
4.3、注意事项
- ICP 仅对二级索引生效(主键索引无意义)
- MySQL 5.6+ 默认开启
- Web 环境中需注意字符集匹配(避免隐式转换导致索引失效)
4.4、索引最大化 ICP 效果
1.索引列顺序原则
- 高选择性列放前面(如 user_id 比 status 选择性高)
- 等值查询列放前,范围查询列放后
sql
-- 好:等值列 user_id 在前
KEY idx_good (user_id, status, create_time)
-- 差:范围列 create_time 在前(导致 status 无法用 ICP)
KEY idx_bad (create_time, user_id, status)
2.避免索引失效
sql
-- 错误:函数导致索引失效
WHERE DATE(create_time) = '2023-01-01'
-- 正确:范围查询
WHERE create_time >= '2023-01-01' AND create_time < '2023-01-02'
3.结合覆盖索引
sql
-- 如果只需 user_id 和 status
SELECT user_id, status FROM orders WHERE user_id = 123 AND status LIKE 'paid%';
-- 创建覆盖索引
KEY idx_cover (user_id, status);
-- 效果:无需回表 + ICP 过滤 → 性能极致
5、常见误区与避坑指南
误区 1:"ICP 能减少回表次数"
-
真相 :ICP 不减少回表次数 ,而是减少回表前的数据量
-
回表次数 = 最终结果行数,ICP 无法改变
误区 2:"所有 LIKE 都能用 ICP"
-
真相 :只有 前缀匹配('%xxx%')可用
-
后缀/通配匹配('%xxx%', '%xxx%')会导致索引失效
误区 3:"ICP 在 MyISAM 也有效"
-
真相 :仅 InnoDB 支持 ICP
-
MyISAM 用户需升级存储引擎 Using index condition
总结
| 问题 | 答案 |
|---|---|
| ICP 是什么? | 在存储引擎层过滤索引数据 |
| 为什么重要? | 减少无用数据传输,提升查询性能 |
| 如何验证? | explain**的 Extra 列 =**Using index condition |
| 何时生效? | 复合索引 + 非前导列过滤(等值/范围/LIKE 前缀) |
行动建议
- 检查慢查询:用 explain 分析是否启用 ICP
- 优化索引设计:高选择性列放前,避免函数操作
- 结合覆盖索引:进一步减少回表
- 保持默认开启:除非特殊场景,否则不要关闭 ICP
"索引下推不是银弹,但它是你 SQL 性能优化工具箱中不可或缺的一把利器。"
参考文章: