news 2026/8/1 1:28:17

MySQL: 最左前缀原则 索引下推(ICP)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL: 最左前缀原则 索引下推(ICP)

一、最左前缀原则

第一步:联合索引在 B+ 树里到底存了什么?

假设你有一张表:

CREATE TABLE tb_user ( id INT PRIMARY KEY, a INT, b INT, c INT, INDEX idx_abc (a, b, c) -- 联合索引 );

你建了一个联合索引(a, b, c)

MySQL 不会建三棵独立的树,而是只建一棵 B+ 树

但这棵树的叶子节点存的键值不是单独的abc,而是把三列的值拼在一起,当成一个整体键值

索引键值格式:(a, b, c) 叶子节点实际存储的是: (1, 2, 3) (1, 2, 5) (1, 3, 1) (2, 1, 4) (2, 2, 6) (3, 1, 2) ...

排序规则是:先按 a 排,a 相同再按 b 排,b 相同再按 c 排。

这和字典序排序一模一样:

  • 先比较第一个字母

  • 第一个相同,比较第二个

  • 第二个相同,比较第三个


第二步:B+ 树的有序性决定了查找方式

B+ 树的核心特性是:节点内的所有键值是有序的,查找时必须利用这个有序性二分查找顺序扫描。

当你执行:

SELECT * FROM tb_user WHERE a = 1;

MySQL 去idx_abc这棵 B+ 树里找(1, ?, ?)

因为所有键值是先按a排序的,所以a = 1的记录一定连续地排在一起。B+ 树能快速定位到第一个(1, ...)的位置,然后顺着链表往后读,直到a不等于 1 为止。

这个过程能走索引,因为查询条件用到了排序的第一维。


第三步:为什么WHERE b = 2走不了索引?

现在看这条 SQL:

SELECT * FROM tb_user WHERE b = 2;

MySQL 拿着b = 2idx_abc这棵 B+ 树里找。

问题出现了:

在这棵树上,(a, b, c)是按a为第一优先级排序的。b的值是"散"在整个树里的

(1, 2, 3) -- b=2 (1, 2, 5) -- b=2 (1, 3, 1) -- b=3 (2, 1, 4) -- b=1 (2, 2, 6) -- b=2 (3, 1, 2) -- b=1

b = 2的记录出现在(1, 2, 3)(1, 2, 5)(2, 2, 6)这几个位置,它们在 B+ 树的叶子链表上不是连续的

B+ 树没有办法直接跳到"所有b = 2的位置",因为它只能按完整的(a, b, c)键值排序查找。要找到所有b = 2的记录,只能遍历整棵树,逐个检查每个节点的b是不是 2。

遍历整棵树 = 全表扫描(索引扫描版),代价和全表扫描一样高,所以优化器会放弃这个索引,直接走全表扫描。


四:为什么WHERE a = 1 AND c = 3只能用到 a?

SELECT * FROM tb_user WHERE a = 1 AND c = 3;

这个查询能用到索引,但只用到了a这一列c用不上。

原因:

  1. 先通过a = 1定位到 B+ 树上a = 1的连续区间:

    (1, 2, 3) (1, 2, 5) (1, 3, 1)
  2. 在这个区间内,b的值是2, 2, 3,是有序的。但c的值是3, 5, 1因为b不同,所以c在这个区间内不是有序的

  3. 现在你要找c = 3,但ca = 1这个范围内是散落的(3, 5, 1),无法二分查找,只能在a = 1的所有记录里逐个检查c

所以索引只帮你在第一步过滤了a = 1,第二步的c = 3还是要回表后逐行判断。


第五步:WHERE a = 1 AND b = 2 AND c = 3为什么能全走索引?

SELECT * FROM tb_user WHERE a = 1 AND b = 2 AND c = 3;

B+ 树里的键值是(a, b, c),排序顺序是:

(1, 2, 3) (1, 2, 5) (1, 3, 1) (2, 1, 4) ...
  1. 先找a = 1:定位到(1, ...)开头的连续区间

  2. 在这个区间内,b是有序的,再找b = 2:定位到(1, 2, ...)的子区间

  3. 在这个子区间内,c是有序的,再找c = 3:精确命中(1, 2, 3)

每一层都利用了 B+ 树的有序性,每一层都能二分查找,所以三列都用上了索引。


第六步:范围查询为什么断尾?

SELECT * FROM tb_user WHERE a = 1 AND b > 2 AND c = 3;

这个查询能用索引,但只用到abc用不上

原因:

  1. a = 1:定位到a = 1的区间

  2. b > 2a = 1的区间内,b是有序的,可以找到第一个b > 2的位置,然后顺序往后读

    此时读取到的键值可能是:

    (1, 3, 1) -- b=3, c=1 (1, 3, 5) -- b=3, c=5 (1, 4, 2) -- b=4, c=2
  3. 现在你要找c = 3,但在这个b > 2的范围内,c的值是1, 5, 2不是有序的

    因为b已经是一个范围(> 2),b的值在变化(3, 3, 4...),导致c的值无法保证有序。

一旦某一列用了范围查询,它右边的列就无法再走索引的有序性了。


第七步:顺序无关——优化器会自动调整

WHERE b = 2 AND a = 1 AND c = 3

这个 SQL 虽然写的顺序是b, a, c,但优化器会自动重排为a, b, c,然后走索引。

注意:这是"等值条件"的顺序重排,不是"列的使用顺序"如果你写的是WHERE b = 2 AND c = 3,优化器不会凭空给你补一个a,依然走不了索引。


总结:最左前缀原则的本质

规则原因
必须从最左列开始B+ 树按(a, b, c)整体排序,最左列是第一排序键
中间不能断断了左边,右边列在树中不连续,无法二分
范围查询断尾范围条件导致后续列在局部区间内无序
顺序可重排优化器会重排等值条件的顺序,但不会补缺失的列

一句话记忆:

联合索引(a, b, c)就是一棵按a → b → c优先级排序的 B+ 树。查询条件必须能按这个优先级一层层定位,才能利用索引的有序性。跳过了a,树就不知道从哪开始找;跳过了bca的范围内就是乱的。


二、索引下推(ICP)

MySQL 5.6后,在二级索引遍历时就过滤条件,减少回表次数。

第一步:没有 ICP 时,MySQL 的查询流程是什么?

假设你有一张表:

CREATE TABLE tb_user ( id INT PRIMARY KEY, name VARCHAR(50), age INT, INDEX idx_name_age (name, age) -- 联合索引 );

执行这条 SQL:

SELECT * FROM tb_user WHERE name LIKE '张%' AND age = 20;

根据最左前缀原则,name LIKE '张%'可以用到idx_name_age索引,但age = 20是断尾的(因为name是范围条件),所以age本身无法利用 B+ 树的有序性来快速定位。


没有 ICP 时的执行流程:

  1. 存储引擎idx_name_age索引树,找到所有name LIKE '张%'的索引记录

  2. 对每一条找到的索引记录,不管age是多少,都拿着主键id去回表

  3. 回表拿到完整的行数据后,交给MySQL Server 层

  4. Server 层再检查age = 20,把不符合条件的过滤掉

弊端暴露:

如果name LIKE '张%'匹配了 1000 条记录,但这 1000 条里只有 10 条的age = 20,那么:

  • 发生了1000 次回表

  • 其中990 次回表是白做的——回表后发现age != 20,被 Server 层丢弃

回表需要查主键索引树,是磁盘 IO 操作。990 次无效回表 = 990 次无效磁盘 IO。


这就是没有 ICP 的核心问题:Server 层和存储引擎层之间职责划分太死板。

存储引擎只负责"用索引找到记录",找到就回表,把完整行交给 Server;Server 层负责"过滤条件"。

但存储引擎在遍历索引的时候,明明已经看到了age的值(因为idx_name_age的索引键是(name, age)),它却不判断,非要等回表后再让 Server 层判断。


第二步:ICP 的设计思路——把过滤条件下推

MySQL 5.6 引入 ICP(Index Condition Pushdown),设计思路是:

在存储引擎遍历二级索引的过程中,就把能在索引层面判断的条件提前过滤掉只有满足条件的记录才回表。

为什么能这样做?

因为idx_name_age这棵索引树的叶子节点存储的是(name, age, id)。对于每一个索引条目,存储引擎在读取它的时候,已经同时拿到了nameage的值

既然age的值就在索引条目里,为什么非要回表后再判断?直接在索引层判断age = 20,不满足的条目直接丢弃,不回表。


第三步:有 ICP 时的执行流程对比

同样的 SQL:

SELECT * FROM tb_user WHERE name LIKE '张%' AND age = 20;

有 ICP 时的执行流程:

  1. 存储引擎去idx_name_age索引树,找到第一条name LIKE '张%'的索引记录

  2. ICP 生效存储引擎检查这条索引记录里的age字段

    • 如果age != 20直接丢弃,不回表

    • 如果age = 20拿着主键id回表,查完整行数据返回给 Server 层

  3. 顺着索引链表继续找下一条name LIKE '张%'的记录,重复步骤 2


结果对比:

阶段没有 ICP有 ICP
name LIKE '张%'匹配 1000 条1000 次回表只回表age = 20的那 10 条
age != 20的 990 条回表后交给 Server 层丢弃在索引层直接丢弃,零回表
磁盘 IO1000 次回表 IO10 次回表 IO

第四步:在代码和 EXPLAIN 中怎么看 ICP?

EXPLAIN 中的标志

EXPLAIN SELECT * FROM tb_user WHERE name LIKE '张%' AND age = 20;

如果 ICP 生效,在Extra列会看到:

Using index condition

注意区分:

Extra 值含义
Using index覆盖索引,不需要回表
Using index condition使用了 ICP,需要回表,但回表前在索引层做了过滤
Using where没有 ICP,回表后在 Server 层过滤

关闭 ICP 做对比测试

-- 关闭 ICP(默认是开启的) SET optimizer_switch = 'index_condition_pushdown=off'; EXPLAIN SELECT * FROM tb_user WHERE name LIKE '张%' AND age = 20; -- Extra 显示:Using where(表示回表后 Server 层过滤) -- 开启 ICP SET optimizer_switch = 'index_condition_pushdown=on'; EXPLAIN SELECT * FROM tb_user WHERE name LIKE '张%' AND age = 20; -- Extra 显示:Using index condition

第五步:ICP 的生效条件

ICP 不是万能的,它有以下限制:

1. 只对二级索引生效

主键索引(聚簇索引)的叶子节点本身就是完整数据,不存在"回表"这个概念,所以不需要 ICP。

2. 条件必须能在索引层判断

-- 能下推:age 在 idx_name_age 索引里 WHERE name LIKE '张%' AND age = 20 -- 不能下推:address 不在 idx_name_age 索引里 WHERE name LIKE '张%' AND address = '北京'

address不在索引中,存储引擎在遍历idx_name_age时看不到address的值,所以address = '北京'无法下推,只能回表后由 Server 层判断。

3. 不能用于存储函数

WHERE name LIKE '张%' AND YEAR(created_at) = 2024

如果created_at在索引中,但条件里包函数YEAR(),ICP 通常不会下推(因为存储引擎不一定能直接计算函数结果)。


总结逻辑链

阶段问题/弊端解决方案
没有 ICP存储引擎只负责索引定位,所有匹配记录都回表,Server 层再过滤职责划分不合理,回表次数过多
ICP 设计索引条目里明明有age的值,却非要回表后再判断把过滤条件下推到存储引擎层
有 ICP 后存储引擎遍历索引时,先检查索引中的列条件,不满足直接丢弃大幅减少无效回表,降低磁盘 IO
限制只对二级索引生效,条件列必须在索引中主键索引不需要,非索引列无法下推
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/1 1:27:49

腾讯云CLB实现HTTPS卸载:架构优化与实战配置指南

1. 项目背景与核心价值:为什么需要CLB处理HTTPS?在今天的互联网架构里,HTTPS早已不是“可选项”,而是保障数据传输安全、提升用户信任度的“必选项”。无论是电商、金融还是企业官网,一个绿色的安全锁图标是用户访问的…

作者头像 李华
网站建设 2026/8/1 1:26:34

如何快速彻底重置AnyDesk ID:Windows系统终极清理指南

如何快速彻底重置AnyDesk ID:Windows系统终极清理指南 【免费下载链接】generate-a-new-anydesk-id Generate a new AnyDesk ID 项目地址: https://gitcode.com/gh_mirrors/ge/generate-a-new-anydesk-id 你是否曾因AnyDesk ID安全问题而担忧远程连接的安全性…

作者头像 李华
网站建设 2026/8/1 1:26:33

本地部署Claude CLI全攻略:模型路由、用量监控与踩坑排错

文章目录前言1. 第一步:先把地基打牢1.1 版本选对,少踩一半坑1.2 可选花活:NVM版本管理器1.3 可选花活:Git2. 主力选手:Claude Code CLI2.1 安装就一行命令2.2 API Key和网络那些糟心事2.3 备选选手:OpenCo…

作者头像 李华
网站建设 2026/8/1 1:25:33

Just do it (请做个小改变吧)

作者提出了一个行动理论:当你决定要得到某个事物,并且在物理上、情感上或是精神上开始行动后,这些行动就为你的生活开拓了新的道路。即使是很小的一步(物理上、情感上)。解释一下:现在想象你站在自我世界的正中央&…

作者头像 李华
网站建设 2026/8/1 1:22:18

世毫九理论(SH9)对话本体论形式化证明全维度研究报告

世毫九理论(SH9)对话本体论形式化证明全维度研究报告 作者:方见华 单位:世毫九实验室 核心摘要 本报告基于世毫九实验室(Shardy Lab)2026年公开的系列定型研究成果,针对该理论支撑“关系先于实体…

作者头像 李华
网站建设 2026/8/1 1:21:26

Python智能客服系统开发:从NLP到部署实践

1. 项目概述"基于Python的人工智能智能客服系统"是一个结合自然语言处理(NLP)和机器学习技术的自动化对话解决方案。这类系统正在彻底改变传统客服行业的运作模式,根据Gartner的预测,到2025年,超过80%的企业客户服务交互将由AI处理…

作者头像 李华