1.介绍
**Index Condition Pushdown(ICP)**是针对MySQL使用索引从表中检索行的情况进行的优化。
在没有ICP的情况下,存储引擎遍历索引以定位基表中的行,并将它们返回给MySQL服务器,MySQL服务器会评估这些行的WHERE条件。
启用ICP后,如果仅使用索引中的列 就可以计算WHERE条件的一部分 ,MySQL服务器会将WHERE条件的这一部分 推送到存储引擎。然后,存储引擎通过使用索引条目来评估推送的索引条件 ,只有当满足这一条件时,才从表中读取行。ICP可以减少存储引擎必须访问基表的次数和MySQL服务器必须访问存储引擎的次数。
2.触发限制
指数条件下推优化的适用性受以下条件的约束:
当需要访问完整的表行 时,ICP用于range 、ref 、eq_ref 和ref_or_null访问方法。
ICP可用于InnoDB 和MyISAM 表,包括分区的InnoDB 和MyISAM表。
对于InnoDB表,ICP仅用于二级索引。ICP的目标是减少整行读取的次数,从而减少I/O操作。对于InnoDB聚集索引 ,完整的记录已经读取到InnoDB缓冲区中。在这种情况下使用ICP不会减少I/O。
在虚拟生成列上创建的辅助索引 不支持ICP。InnoDB支持虚拟生成列的二级索引。
引用子查询的条件不能向下推。
包含存储函数的查询条件无法下推。存储引擎无法调用存储的函数。
触发式的条件查询无法下推。(有关触发条件的信息,请参阅"使用EXISTS策略优化子查询"。)
(MySQL 8.0.30及更高版本:)不能将条件查询下推到包含对系统变量的引用的派生表中。
要了解此优化是如何工作的,请首先考虑在不使用"索引条件下推"时索引扫描是如何进行的:
1.获取下一行,首先读取索引元组,然后使用索引元组定位并读取整个表行。
2.WHERE条件中匹配此表的部分行。根据匹配结果接受或拒绝该行。
使用索引条件下推,扫描按如下方式进行:
1.获取下一行的索引元组(但不是整个表行)。
2.WHERE条件中匹配此表的部分行,并且只能使用索引列进行检查。如果条件不满足,请转到下一行的索引元组。
3.如果满足条件,则使用索引元组来定位和读取整个表行。
4.匹配此表的WHERE条件的剩余部分。根据匹配结果接受或拒绝该行。
EXPLAIN输出显示当使用索引条件下推时,在Extra列中使用索引条件。它不显示Using index,因为当必须读取完整的表行时,这不适用。
3.示例
假设一个表包含有关人员及其地址的信息,
并且该表有一个定义为index(zipcode,lastname,firstname) 的索引。如果我们知道一个人的邮政编码值 ,但不确定他的姓氏,我们可以这样搜索:
sql
SELECT * FROM people
WHERE zipcode='95054'
AND lastname LIKE '%etrunia%'
AND address LIKE '%Main Street%';
MySQL可以使用索引扫描zipcode='95054'的人。
第二部分(lastname LIKE"%etrunia%" )不能用于缩小必须扫描的行数,因此如果没有索引条件下推,此查询必须为所有邮政编码为"95054"的人检索完整的表。
使用Index Condition Pushdown,MySQL在读取完整的表行之前检查姓氏LIKE"%etrunia%"部分。这样可以避免读取与zipcode条件匹配但与lastname条件不匹配的索引元组对应的完整行。
默认情况下,"索引条件下推"处于启用状态。
可以通过设置index_condition_pushdown标志 ,使用optimizer_switch系统变量进行控制:
ruby
SET optimizer_switch = 'index_condition_pushdown=off';
SET optimizer_switch = 'index_condition_pushdown=on';
博主PS:
这里官网的示例如果还不够清晰,我们回想一下,MySQL是有聚簇索引和二级索引(辅助索引)的,这里我们讨论Innodb引擎的情况,也就是说在index(zipcode,lastname,firstname) 的索引的索引列上,二级索引也是一个B+树。它每个节点存放的就是索引列的值,当索引走到zipcode=95054这个索引 下的时候,那么它的下一级子树肯定都是邮编为95054,姓lastname不确定,名firstname不确定的索引了。(因为联合索引的排序规则是先排序前面索引的key,再排序后面的。)
或者说此index(zipcode,lastname,firstname)联合索引所生成的二级索引B+树, 已经按zipcode排序,再按lastname排序,再按fisrtname排序了。所以当根据最左匹配原则匹配到了95054索引后,再多进行一次判断(对第二列的匹配判断),因为此时,第二列索引的值和95054在一个节点下,或之下,我只需要判断当前这个联合索引的第二个索引字段值是否匹配where的第二个字段的模糊查。这样不需要再去聚簇索引查找数据行了、如果不下推,那么我需要把95054这个索引所匹配的二级索引的所以数据行回表查询,去聚簇索引查出所有的数据行,再与where条件第二个判断。我们知道二级索引的叶子节点数据行存放的是聚簇索引的主键。这时候他需要全量回表查询,回表次数可能会相当大,但是我如果直接通过联合索引上的索引的key来判断where条件查询的值是否符合这个key,就可以过滤掉很大一部分二级索引上的数据,只有那种实在无法定位的数据,再通过主键回表查询。比如第三个where条件是地址,这个联合索引根本就没有地址字段的联合索引。那么此时数据库手里已经有一堆被邮编和姓名过滤过的数据了,再用这些数据的主键。去聚簇索引判断地址数据。
如果你还不懂 那么举一个相亲的例子。聚簇索引里,人们的【个人信息数据】通过身份证号组成一颗B+树。B+树的叶子节点就是那个人的全部个人信息。
现在建立了一个由国家,年龄,身高,性别为联合索引的二级索引树。
这时候你的where条件是:国家=中国,年龄=18岁,性别=女,的一个where查询。
正常来说这个国家,年龄,性别 的where查询。只有国家 和年龄 符合最左匹配,他可以使用到索引,但是性别用不了索引查询(不符合最左匹配),需要回表查询。
但是你现在手里已经查询到所有 "国家=中国 和 年龄18岁, 身高不确定,性别不确定的"人了索引集合了。
因为没有回表去匹配身高,和性别的人,索引这里是不限。但是聪明的你已经想到。
本来就只需要查性别了,性别的数据在当前的联合索引里是有的,我只需要在联合索引里判断一下联合索引里的性别数据,看看哪些索引是女性索引,是不是就能把where条件里的过滤条件满足完了??
没有ICP之前,数据库会把在二级索引查找到所有满足国家=中国 和 年龄18岁的主键返回,并去聚簇索引查找性别为女的数据行。
这就是索引下推。如果二级索引的索引key可以用来对where条件进行过滤,不用再使用二级索引的叶子节点数据行主键去聚簇索引回表查询,那么就可以下推判断。这时候可以破坏最左匹配原则。
参见"可切换优化"。