“索引下推”是数据库领域的一个术语,主要出现在MySQL(尤其是InnoDB存储引擎)中,英文名叫 Index Condition Pushdown,简称 ICP。就是过滤的动作由下层的存储引擎层通过使用索引来完成,而不需要上推到Server层进行处理。ICP是在MySQL5.6之后完善的功能
假设有个user_consum表,有主键索引id和联合索引(name, phone),id是用户的消费记录,一个用户有很多条消费记录且name是用户昵称,可能撞车:
CREATE TABLE `user_consum` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`phone` varchar(11) DEFAULT NULL,
`gender` tinyint(1) DEFAULT NULL,
`user_role` int DEFAULT 0,
PRIMARY KEY (`id`),
INDEX IDX_NAME_PHONE (name, phone)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
假设查询语句为:
SELECT * FROM user_innodb WHERE name = "luna" AND phone LIKE "%8848";
注意like是模糊匹配,无法用到索引。
此时假如在二级索引树中找到了几千个id即该用户有几千条消费记录,由于用户昵称可能撞车,这里不一定都是目标用户的,还需要手机尾号符合,那这里:
如果用上索引下推。
就是过滤的动作由下层的存储引擎层通过使用索引来完成,而不需要上推到Server层进行处理:
也就是在联合索引的查找中,即查找符合name = "luna"的数据,也直接在联合索引的叶子节点中去逐个判断phone的尾号是否为8848,然后找到那10条记录,再回表,从而将回表次数降低至10次!
在主键索引树中找到符合条件的10行数据后,再返给服务层10行,减少了存储引擎层到服务层的拷贝开销
,也减轻了服务层的过滤处理压力
!
执行:
EXPLAIN SELECT * FROM user_innodb WHERE name = "luna" AND phone LIKE "%8848";
如果 Extra 字段出现 Using index condition,说明用了索引下推(ICP)。
索引下推(Index Condition Pushdown, ICP)是MySQL的一项索引优化技术,通过在索引遍历阶段尽量多地应用WHERE条件,减少回表次数,提高查询效率。
联合索引、最左匹配,回表、索引覆盖,
(a,b)字段建立联合索引的话
按照a来进行排序,在a相等的情况下,才按b来排序
联合索引与最左匹配博客:
https://www.cnblogs.com/rjzheng/p/12557314.html
所谓最左原则指的就是如果你的 SQL 语句中用到了联合索引中的最左边的索引,那么这条 SQL 语句就可以利用这个联合索引去进行匹配,值得注意的是,当遇到范围查询(>、<、between、like前缀匹配)就会到此为止停止匹配(含当前这个)。
如有索引 KEY idx_abcd(a, b, c, d)
查询 WHERE a = 1 AND b = 2 AND c > 3 AND d = 4
a = 1 —— 用到了索引的第1列
b = 2 —— 用到了索引的第2列
c > 3 —— 范围查询(>),用到了索引的第3列
d = 4 —— 用不到索引,因为c是范围查询,后面的索引列就失效了 ,如果满足a = 1 AND b = 2 AND c > 3的行很多,那就要做很多匹配
所以sql应该这样写:WHERE a = 1 AND b = 2 AND d = 4 AND c > 3,这样都能用上索引
注意范围查询中like的情况
因为像联合索引还是单列普通索引这种非主键索引,都是查二级索引树,其value是主键id,所以如果要查一些在二级索引树没有的信息,就需要拿着主键id到聚簇索引(主键索引)树去查,这个过程就是回表。
回表的问题主要是,回表是一行数据回表一次,就要查一次聚簇索引!所以要想方法减少回表次数
比如假如查 select x from user where a=1 and c > 3;
其中x没有索引,a和c有索引,但是符合这个条件的有1000个数据,那回表会产生1000次查找,因为MySQL 只能从二级索引(idx_ac)中,依次取出每个满足条件的主键ID,每拿到一个主键值,就去聚簇索引树读取对应的整行数据,然后取出 x 字段。
因为二级索引返回的主键ID通常是离散的,这些主键对应的数据行物理上可能散布在磁盘不同位置,所以每次回表可能都需要一次磁盘I/O(如果不在缓冲池内),这就是“随机I/O”,效率低下(如果不离散的话倒是能直接在叶子节点顺序找到)
也可以打开BKA批量回表机制(Batched Key Access
),比如每批次为500,那么对这500个主键id先排序,这样对于一些连续id就可以一次查找,减少随机I/O。注意:不是所有场景都能用BKA,主要在Join和二级索引回表时生效。
如果这种情况多,那最好就是给x也放到联合索引里,(a, c, x) — — 索引覆盖
如上user表里,如果
SELECT id, name, phone FROM user_info WHERE name = "userxxxx";
因为name和phone是联合索引,而id是主键,所以直接在一次就能在联合索引树上查到所有信息,所以不会有回表,这就是索引覆盖
大部分情况下,MySQL会先从存储层获取主键ID对应的完整行数据,然后在服务层根据过滤条件进行过滤
服务层按照WHERE条件、HAVING条件等进行筛选、聚合等操作
。