登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  MySQL

MySQL 索引条件下推怎样从执行计划确认

来源:17golang原创

时间:2026-10-26 01:04:44 188浏览 收藏

我最近排查一个 MySQL 查询时,执行计划明明显示用了复合索引,回表读取量却没有明显下降。后来把注意力从“有没有用索引”移到“条件在哪一层被判断”,才确认问题和索引条件下推(Index Condition Pushdown,简称 ICP)有关。判断它是否生效,最直接的证据是 EXPLAIN 结果里的 Extra=Using index condition;EXPLAIN ANALYZE 更适合观察实际执行的行数和耗时,不能把它当成这个标记的同义词。

官方地址:https://dev.mysql.com/doc/refman/8.4/en/index-condition-pushdown-optimization.html

先用 EXPLAIN 看 Extra 是否出现 Using index condition,再用 EXPLAIN ANALYZE 的 TREE 输出观察实际执行。如果没有这个标记,就按访问方式、二级索引、条件表达式、优化开关和统计信息逐层排查。

索引条件下推到底提前了哪一步

普通的索引访问可以粗略理解为:存储引擎先用索引找到候选条目,再根据条目定位基表中的完整记录,最后由服务器层判断剩余的 WHERE 条件。候选范围较大时,很多记录在回表之后才会被淘汰,额外的整行读取就产生了。

ICP 的改变点是:如果某一部分条件只依赖索引中已经有的列,存储引擎可以在拿到索引条目时先判断它。只有通过这次判断,才继续读取基表整行。因此它减少的是不必要的回表,不是把查询变成覆盖索引,也不是让所有过滤条件都在索引中完成。

MySQL 索引条件下推在二级索引过滤与基表回表之间的边界结构图
图1:结构说明图,展示索引条件下推如何在回表前过滤索引条目,不是运行截图。

官方文档给出的典型场景是复合索引 (zipcode, lastname, firstname):zipcode 可以帮助定位索引范围,lastname LIKE '%etrunia%' 虽然不能继续缩小索引扫描范围,却可以利用索引条目中的列在回表前判断;address LIKE '%Main Street%' 不在这个索引中,就仍然要等读到完整行后再处理。

先准备一个能看出差异的查询

为了避免被业务表里复杂条件干扰,可以用一个小表复现判断逻辑。下面的示例只负责准备结构和查询形状,重点是索引列、回表列与过滤条件的分工。

-- 创建用于观察 ICP 的示例表;地址列不放进索引,保留回表条件
CREATE TABLE people (
    id BIGINT PRIMARY KEY,
    zipcode VARCHAR(16) NOT NULL,
    lastname VARCHAR(80) NOT NULL,
    firstname VARCHAR(80) NOT NULL,
    address VARCHAR(255) NOT NULL,
    KEY idx_zip_last_first (zipcode, lastname, firstname)
);

-- zipcode 负责定位范围,lastname 条件有机会在索引条目上提前判断
EXPLAIN SELECT id, zipcode, lastname, firstname, address
FROM people
WHERE zipcode = '95054'
  AND lastname LIKE '%etrunia%'
  AND address LIKE '%Main Street%';

这里不要只把目光放在 key 列。key=idx_zip_last_first 只能说明优化器选择了这个索引,不能单独证明 ICP 已经生效;真正的直接证据要看 Extra。

从 EXPLAIN 的 Extra 确认 ICP

如果计划里出现 Extra=Using index condition,可以确认这次访问使用了索引条件下推。常见的组合类似于:

-- 下面是需要关注的字段关系,不是固定的完整输出
type: range
key: idx_zip_last_first
Extra: Using index condition

Using index condition 和 Using index 不是一回事:

计划信息它说明什么不要怎样误读
Using index condition部分条件在索引条目阶段判断,符合后还可能需要回表。不要理解成所有列都来自索引。
Using index查询可以直接从索引覆盖所需列,通常不需要读取基表整行。不要把它当作 ICP 的别名。
Using where服务器层仍需执行条件过滤。它本身不能证明发生了 ICP。

在需要读取完整行的 ICP 场景里,官方说明不会因为用了 ICP 就同时显示 Using index。所以看到 Using index condition 而没有 Using index,并不矛盾,反而符合“索引中先过滤,随后仍要回表”的含义。

用 EXPLAIN ANALYZE 观察实际执行

EXPLAIN 是优化器对执行计划的说明,EXPLAIN ANALYZE 则会实际执行查询并提供实际行数、循环等运行信息。MySQL 8.4 中,EXPLAIN ANALYZE 使用 TREE 形式输出,因此排查时可以把两条命令分工使用:

-- 先确认计划中的直接标记;只读查询适合用于排查
EXPLAIN SELECT id, zipcode, lastname, firstname, address
FROM people
WHERE zipcode = '95054'
  AND lastname LIKE '%etrunia%'
  AND address LIKE '%Main Street%';

-- 再观察实际执行路径;该语句会真正执行 SELECT
EXPLAIN ANALYZE
SELECT id, zipcode, lastname, firstname, address
FROM people
WHERE zipcode = '95054'
  AND lastname LIKE '%etrunia%'
  AND address LIKE '%Main Street%';

我通常先看前一条是否有 Using index condition,再用后一条判断估算行数和实际行数是否偏离、实际扫描是否仍然很大。TREE 输出可以解释真实执行代价,但不要因为 TREE 中没有出现传统表格里的 Extra 列,就反过来断定 ICP 没有使用;两种输出格式承担的观察任务不同。

MySQL EXPLAIN、EXPLAIN ANALYZE 与索引条件下推适用边界的证据关系图
图2:证据关系图,展示 EXPLAIN 与 EXPLAIN ANALYZE 在确认 ICP 时各自承担的作用,不是运行截图。

没有 Using index condition 时按边界排查

没有出现标记不等于索引完全没用。ICP 只有在满足适用条件时才有意义,建议按下面顺序检查。

  1. 先看访问方式。官方列出的典型访问方式包括 range、ref、eq_ref 和 ref_or_null,而且场景需要读取完整表行。先确认 type 没有落到完全不同的访问路径。
  2. 再看存储引擎和索引类型。对 InnoDB 来说,ICP 针对二级索引更有价值;聚簇索引的完整记录本来就已经在缓冲区中,额外减少的 I/O 可能不存在。虚拟生成列上的二级索引也有官方列出的限制。
  3. 拆开条件表达式。子查询、存储函数和触发条件不能直接下推;如果可判断部分没有只使用索引列,存储引擎也没有足够信息提前过滤。
  4. 确认优化开关。ICP 默认开启,但可以被会话级或全局配置关闭。检查当前值时,先记录原值,测试结束后恢复,避免把排查动作带进其他请求。
-- 查看当前会话的优化器开关,先保存结果再做对照测试
SELECT @@optimizer_switch;

-- 仅对当前会话临时关闭 ICP,便于比较计划差异
SET SESSION optimizer_switch = 'index_condition_pushdown=off';

-- 对照后恢复开启状态,不要把测试配置留给后续业务语句
SET SESSION optimizer_switch = 'index_condition_pushdown=on';

如果开关开启、索引也是二级索引,仍然没有标记,可以把查询缩小到一个明确的复合索引条件,再重新执行 EXPLAIN。不要一上来就通过强制索引或重写全部 SQL 来“制造”标记,先确认实际条件确实具备被下推的可能。

排查结论怎么落到实际决策

这次排查最后留下了三个判断点:第一,key 只回答“选了哪个索引”,Extra=Using index condition 才直接回答“是否在索引条目阶段做了条件判断”;第二,EXPLAIN ANALYZE 用来观察真实执行数据,适合发现回表或扫描仍然很大的情况;第三,ICP 是减少回表的一种优化,不会替代合理的索引前缀设计,也不会让不在索引中的条件自动消失。

把这三点分开后,执行计划里的“用了索引但还是慢”就更容易定位:先确认是否有 ICP,再看实际扫描和回表规模,最后才决定是否需要调整索引列顺序或查询条件。

常见相关问题

Using index condition 出现后还会回表吗?

会。它表示部分条件在索引条目上提前判断,满足后仍可能读取基表完整记录,再处理剩余条件。

Using index 和 Using index condition 哪个更快?

不能脱离查询列和数据分布直接比较。前者通常意味着索引覆盖,后者意味着仍可能回表但减少了无效整行读取,实际效果要结合扫描量、回表量和数据分布观察。

EXPLAIN ANALYZE 没显示 Using index condition,是不是 ICP 没生效?

不能这样判断。先用传统 EXPLAIN 看 Extra,再用 EXPLAIN ANALYZE 的 TREE 输出观察真实执行;两者不是同一种展示格式。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>