MySQL ICP(Index Condition Pushdown)特性

一、SQL的where条件提取规则

在ICP(Index Condition Pushdown,索引条件下推)特性之前,必须先搞明白根据何登成大神总结出一套放置于所有SQL语句而皆准的where查询条件的提取规则:所有SQL的where条件,均可归纳为3大类:Index Key (First Key & Last Key),Index Filter,Table Filter。

接下来,简单说一下这3大类分别是如何定义,以及如何提取的,详情请看:SQL语句中where条件在数据库中提取与应用浅析

Index Key(Fist key & Last Key),Index Filter,Table Filter

Index First Key

只是用来定位索引的起始范围,因此只在索引第一次Search Path(沿着索引B+树的根节点一直遍历,到索引正确的叶节点位置)时使用,一次判断即可;

Index Last Key

用来定位索引的终止范围,因此对于起始范围之后读到的每一条索引记录,均需要判断是否已经超过了Index Last Key的范围,若超过,则当前查询结束;

Index Filter

用于过滤索引查询范围中不满足查询条件的记录,因此对于索引范围中的每一条记录,均需要与Index Filter进行对比,若不满足Index Filter则直接丢弃,继续读取索引下一条记录;

Table Filter

则是最后一道where条件的防线,用于过滤通过前面索引的层层考验的记录,此时的记录已经满足了Index First Key与Index Last Key构成的范围,并且满足Index Filter的条件,回表读取了完整的记录,判断完整记录是否满足Table Filter中的查询条件,同样的,若不满足,跳过当前记录,继续读取索引的下一条记录,若满足,则返回记录,此记录满足了where的所有条件,可以返回给前端用户。

二、ICP特性介绍

Index Condition Pushdown (ICP)是MySQL 5.6版本中的新特性,是一种在存储引擎层使用索引过滤数据的一种优化方式。

我对Using index condition的理解是,首先mysql server和storage engine是两个组件,server负责sql的parse,执行; storage engine去真正的做数据/index的读取/写入。以前是这样:server命令storage engine按index key把相应的数据从数据表读出,传给server,然后server来按where条件(index filter和table filter)做选择。

而在MySQL 5.6加入ICP后,Index Filter与Table Filter分离,Index Filter下降到InnoDB的索引层面进行过滤,如果不符合条件则无须读数据表,减少了回表与返回MySQL Server层的记录交互开销,节省了disk IO,提高了SQL的执行效率。

原理

a. 当关闭ICP时,index仅仅是data access的一种访问方式,存储引擎通过索引回表获取的数据会传递到MySQL Server层进行where条件过滤,也就是做index filter和table filter。

b. 当打开ICP时,如果部分where条件能使用索引中的字段,MySQL Server会把这部分下推到引擎层,可以利用index filter的where条件在存储引擎层进行数据过滤,而非将所有通过index access的结果传递到MySQL server层进行where过滤。

优化效果:ICP能减少引擎层访问基表的次数和MySQL Server访问存储引擎的次数,减少io次数,提高查询语句性能。

三、测试ICP优化SQL

本文选用MySQL官方文档中提供的示例数据库之一:employees。这个数据库关系复杂度适中,且数据量较大。下图是这个数据库的E-R关系图(引用自MySQL官方手册):

MySQL ICP(Index Condition Pushdown)特性

MySQL官方文档中关于此数据库的页面为:https://dev.mysql.com/doc/employee/en,里面详细介绍了此数据库,并提供了下载地址和导入方法,如果有兴趣导入此数据库到自己的MySQL可以参考文中内容。

可以选择下载测试数据:https://github.com/datacharmer/test_db

关闭缓存(最好关闭后重启MySQL)

mysql> set global query_cache_size=0;
mysql> set global query_cache_type=OFF;

导入employees库,需要自己手动创建一个联合索引。

mysql> alter table employees add index first_last(first_name,last_name);

其表结构如下:

mysql> show create table employees\G
*************************** 1. row ***************************
Table: employees
Create Table: CREATE TABLE `employees` (
`emp_no` int(11) NOT NULL,
`birth_date` date NOT NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` enum('M','F') NOT NULL,
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`),
KEY `first_last` (`first_name`,`last_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

当开启ICP时(默认开启)

mysql> SET profiling = 1;
Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> select SQL_NO_CACHE * from employees where first_name='Anneke' and last_name like '%Preusig' ;
+--------+------------+------------+-----------+--------+------------+
| emp_no | birth_date | first_name | last_name | gender | hire_date |
+--------+------------+------------+-----------+--------+------------+
| 10006 | 1953-04-20 | Anneke | Preusig | F | 1989-06-02 |
+--------+------------+------------+-----------+--------+------------+
1 row in set (0.00 sec)

此时情况下根据MySQL的最左前缀原则,irst_name 可以使用索引,last_name采用了like 模糊查询,不能使用索引。

当关闭ICP时

mysql> set optimizer_switch='index_condition_pushdown=off';
Query OK, 0 rows affected (0.00 sec) mysql> select SQL_NO_CACHE * from employees where first_name='Anneke' and last_name like '%Preusig' ;
+--------+------------+------------+-----------+--------+------------+
| emp_no | birth_date | first_name | last_name | gender | hire_date |
+--------+------------+------------+-----------+--------+------------+
| 10006 | 1953-04-20 | Anneke | Preusig | F | 1989-06-02 |
+--------+------------+------------+-----------+--------+------------+
1 row in set (0.00 sec) mysql> SET profiling = 0;
Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> show profiles;
+----------+------------+---------------------------------------------------------------------------------+
| Query_ID | Duration | Query |
+----------+------------+---------------------------------------------------------------------------------+
| 1 | 0.00108900 | select * from employees where first_name='Anneke' and last_name like '%Preusig' |
| 2 | 0.00025375 | set optimizer_switch='index_condition_pushdown=off' |
| 3 | 0.00231650 | select * from employees where first_name='Anneke' and last_name like '%Preusig' |
+----------+------------+---------------------------------------------------------------------------------+
5 rows in set, 1 warning (0.00 sec)

当开启ICP时,查询在sending data环节时间消耗是 0.00108900s

当关闭ICP时,查询在sending data环节时间消耗是 0.00231650s

从上面的profile可以看出ICP开启时整个sql 执行时间是未开启的2/3,sending data 环节的时间消耗前者仅是后者的1/4。

ICP 开启时的执行计划 含有 Using index condition 标示 ,表示优化器使用了ICP对数据访问进行优化。ICP关闭时的执行计划显示use where。
 
mysql> set optimizer_switch='index_condition_pushdown=on';
Query OK, 0 rows affected (0.00 sec) mysql> explain select * from employees where first_name='Anneke' and last_name like '%nta' ;
+----+-------------+-----------+------+---------------+------------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------+------+---------------+------------+---------+-------+------+-----------------------+
| 1 | SIMPLE | employees | ref | first_last | first_last | 58 | const | 224 | Using index condition |
+----+-------------+-----------+------+---------------+------------+---------+-------+------+-----------------------+
1 row in set (0.00 sec) mysql> explain select * from employees where first_name='Anneke' ;
+----+-------------+-----------+------+---------------+------------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------+------+---------------+------------+---------+-------+------+-----------------------+
| 1 | SIMPLE | employees | ref | first_last | first_last | 58 | const | 224 | Using index condition |
+----+-------------+-----------+------+---------------+------------+---------+-------+------+-----------------------+
1 row in set (0.00 sec) mysql> set optimizer_switch='index_condition_pushdown=off';
Query OK, 0 rows affected (0.00 sec) mysql> explain select * from employees where first_name='Anneke' and last_name like '%nta' ;
+----+-------------+-----------+------+---------------+------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------+------+---------------+------------+---------+-------+------+-------------+
| 1 | SIMPLE | employees | ref | first_last | first_last | 58 | const | 224 | Using where |
+----+-------------+-----------+------+---------------+------------+---------+-------+------+-------------+
1 row in set (0.00 sec)

四、案例分析

以上面的查询为例,关闭ICP时,存储引擎通前缀index first_name(视为index key)访问表中数据,并在MySQL server层根据where条件last_name like ‘%nta’(视为index filter)进行过滤。

开启ICP时,MySQL server把index filter(last_name like ‘%nta’)推到存储引擎层,在存储引擎内部通过与where条件last_name like ‘%nta’的对比,直接过滤掉不符合条件的数据,然后返回最终数据给MySQL server层。该过程减少了回表操作,只访问符合条件的1条记录并返回给MySQL Server ,有效的减少了io访问和各层之间的交互。

ICP关闭时 ,仅仅使用索引作为访问数据的方式。

MySQL ICP(Index Condition Pushdown)特性

ICP 开启时 ,MySQL将在存储引擎层 利用索引过滤数据,减少不必要的回表。

MySQL ICP(Index Condition Pushdown)特性

注意虚线的using where表示如果where条件中含有没有被索引的字段,则还是要经过MySQL Server 层过滤。

五、ICP的使用限制

1. 当sql需要全表访问时,ICP的优化策略可用于range, ref, eq_ref, ref_or_null类型的访问数据方法 。

2. 支持InnoDB和MyISAM表。

3. ICP只能用于二级索引,不能用于主索引。

4. 并非全部where条件都可以用ICP筛选,如果where条件的字段不在索引列中,还是要读取整表的记录到server端做where过滤。

5. ICP的加速效果取决于在存储引擎内通过ICP筛选掉的数据的比例。

6. MySQL 5.6版本的不支持分表的ICP功能,5.7版本的开始支持。

7. 当sql使用覆盖索引时,不支持ICP优化方法。

 

原文链接:http://blog.itpub.net/22664653/viewspace-1210844/

上一篇:完善:HTML5表单新特征简介与举例——张鑫旭


下一篇:bzoj4730: Alice和Bob又在玩游戏