神奇的 SQL 之 ICP → 索引条件下推

开心一刻

  楼主:来,咱们先排练一遍html

  小伙伴们:好mysql

  嘿、哈、嚯sql

  楼主:很是好,就是这个节奏,咱们开始吧ide

  楼主:啊、啊、啊,疼 ! 大家是否是故意的 ?性能

回表与覆盖索引

  正式讲 ICP 以前了,咱们先将相关的概念捋一捋,知道的就当回顾,不知道的就当了解了,这有助于对 ICP 的理解优化

  建个示例表 tbl_index ui

CREATE TABLE tbl_index (
    c1 INT,
    c2 INT,
    c3 CHAR(1),
    PRIMARY KEY(c1),
    KEY idx_c2 (c2)
);spa

  覆盖索引

    若是 where 条件的列和 select 的列都在一个索引中,经过这个索引就能够完成查询,这就叫就叫覆盖索引;固然,覆盖索引基本针对的是组合索引(InnoDB 的聚簇索引有点特殊,具体能够看下面的图)3d

    针对上面的 tbl_index, select c2 from tbl_index where c2 = 4; 是覆盖索引查询,可是这条 SQL 没有意义,若是咱们在 tbl_index 表上增长索引 index idx_c2_c3 (c2,c3) ,那么 select c3 from tbl_index where c2 = 4; 走覆盖索引查询仍是颇有意义的,那问题又来了,覆盖索引的意义何在 ? 咱们往下看netty

  回表

    经过某个索引没法直接完成 SQL 查询(where 条件的列和 select 的列不所有存在于任何一个索引中),那么此时须要获取完整的数据记录来完成这次查询,从索引项记录到获取对应的完整数据记录的过程就叫回表;概念可能说的有些抽象,咱们结合 MySQL 来看看具体什么是回表

    InnoDB 的回表

    InnoDB 的索引结构有些特殊,非聚簇索引(二级索引)回表到聚簇索引的过程相似以下

    InnoDB的聚簇索引即数据,索引和数据是存在一块儿的;那么直接走聚簇索引查询的 SQL 是不存在回表一说的,好比 select * from tbl_index where c1 = 10; ,只有从二级索引出发,而且二级索引独自完成不了查询的时候才会回表到聚簇索引完成查询

    MyISAM 的回表

    有这样一种说法: MyISAM 中的索引都是二级索引 ,其实说的是聚簇索引和二级索引的结构基本一致,只是聚簇索引有个惟一性约束

    MyISAM 聚簇索引和二级索引,以及它们的回表过程相似以下

    MyISAM 的回表过程指的是根据叶子节点中的数据记录的地址来获取完整记录的过程,不管是聚簇索引仍是二级索引均可能存在回表的过程;MyISAM 的回表与 InnoDB 仍是有差异的

  不管是 InnoDB 的回表仍是 MyISAM 的回表,颇有可能会形成额外的磁盘 IO,这会严重影响查询效率,覆盖索引的目的就是尽可能可以一次完成 SQL 查询,避免有回表过程,从而提升效率

  如何确认 MySQL 是进行了覆盖索引查询,仍是进行了回表查询 ?

  看 MySQL 的执行计划,若是 Extra 中只有 using index 则说明使用了覆盖索引查询,若是 Extra 中出现了 using index condition 或 using index & using where 则说明进行了回表查询

ICP

  Index Condition Pushdown,MySQL 5.6 中引入的一种优化策略

  那么到底是将什么从哪 Push Down 到哪,优化了什么?要弄清楚这 4 个问题,咱们须要先弄清楚 where 条件的提取与应用,具体可查看:神奇的 SQL 之 WHERE 条件的提取与应用

  where 条件会被提取成 3 部分: Index KeyIndex Filter,Table Filter ,在 MySQL 5.6 以前,并不区分 Index Filter 与 Table Filter,通通将 Index First Key 与 Index Last Key 范围内的索引记录,回表读取完整记录,而后返回给 MySQL Server 层进行过滤,而在 MySQL 5.6 以后,Index Filter 与 Table Filter 分离,Index Filter 降低到引擎层(InnoDB和MyISAM)的索引层面进行过滤,减小了回表与返回 MySQL Server 层的记录交互开销,提升了 SQL 的执行效率

  ICP 优化过程

    假设咱们有表: tbl_icp 

create table tbl_icp (a int primary key, b int, c int, d int, e varchar(50));
create index idx_bcd on tbl_icp(b, c, d);
insert into tbl_icp values (4,3,1,1,'a');
insert into tbl_icp values (1,1,1,2,'d');
insert into tbl_icp values (8,8,7,8,'h');
insert into tbl_icp values (2,2,1,2,'g');
insert into tbl_icp values (5,2,2,5,'e');
insert into tbl_icp values (3,3,2,1,'c');
insert into tbl_icp values (7,4,0,5,'b');
insert into tbl_icp values (6,5,2,4,'f');
View Code

    若没有使用 ICP,则 SQL 查询相似以下

    没有使用 ICP 时,引擎层会将知足 Index Key 范围限制的全部数据记录(示例中一共 6 条)逐条返回给 Server 层,而后由 server 层应用 Index Filter 和 Table Filter (MySQL 5.6 以前不区分 Index Filter 和 Table Filter),最后将知足条件的数据返回给客户端;

    若使用 ICP,则 SQL 查询相似以下

    使用了 ICP,Server 层会将 Index Filter 下推到引擎层,引擎层在对 Index First Key 与 Index Last Key 范围内的索引项逐条进行过滤的时候,会应用上 Index Filter,对不知足 Index Filter 条件的索引项直接过滤掉,无需回表操做,也无需返回给 Server 层,从而提供执行效率;上图中的索引项: 3 1 1 、 3 2 1 不知足 Index Filter 中的 d != 1 , 4 0 5 不知足 c > 0 ,因此这 3 个索引项无需进行回表操做,也不须要返回给 Server 层

  相信到这里,你们对 ICP 的 4 个问题应该就比较清楚了

  ICP 的适用条件

    虽然说 ICP 能提升 SQL 执行效率,但也不是任何状况下都适用的,它只适用于某些状况

    一、当 SQL 须要全表访问时,ICP 的优化策略可用于 range, ref, eq_ref,  ref_or_null 类型的数据访问方式

    二、只适用于 InnoDB 和 MyISAM 两种存储引擎

    三、在 InnoDB 中,ICP 只适用于二级索引

      ICP 的目的就是为了减小回表致使的磁盘 I/O,而 InnoDB 的聚簇索引的叶子节点存放的就是完整的数据记录,只要索引数据被读到内存了,那么索引项对应的完整数据记录也就读到内存了,那么经过索引项获取数据记录的过程就在内存中进行了,无需进行磁盘 I/O;也就说聚簇索引上应用 ICP,不会减小磁盘 I/O,也就没有使用的意义了

    四、不支持覆盖索引

      其实和第 3 点同样,由于覆盖索引无需回表,ICP 也就没意义了

    五、不支持子查询条件的下推

    六、不支持存储过程条件、触发器条件的下推

  至于 ICP 的优化效果,取决于在存储引擎内经过 ICP 筛选掉的数据的比例,过滤掉的数据比例大,那就性能提高大,反之则性能提高小

总结

  一、索引覆盖与回表

    这两个每每是一块儿来考虑的,由于覆盖索引的目的就是减小因回表产生的磁盘 I/O,从而提升执行效率

    在实际应用中,咱们每每也须要考虑尽量用覆盖索引来完成咱们的 SQL 查询

  二、ICP的四个问题

    将什么从哪 Push Down 到哪,优化了什么

    将 Index Filter 从 Server 层 Push Down 到了引擎层,减小了因回表产生的磁盘 I/O,也减小了与 Server 层的交互,提升了 SQL 执行效率

  三、疑问点

    为何这么明显的优化策略到 MySQL 5.6 才引入,我的感受很容易就能考虑到呀,MySQL 的开发者们是肿么肥事 ?

    多是楼主在巨人的肩膀上,站着说话不腰疼吧......

参考

  Index Condition Pushdown Optimization

  Index Condition Pushdown

  MySQL的索引