4.4. 索引条件下推(ICP)
https://dev.mysql.com/doc/refman/5.7/en/index-condition-pushdown-optimization.html
drop table employees;
CREATE TABLE `employees` (
`emp_no` int(11) NOT NULL,
`birth_date` date NULL,
`first_name` varchar(14) NOT NULL,
`last_name` varchar(16) NOT NULL,
`gender` enum(‘M‘,‘F‘) NOT NULL,
`hire_date` date NULL,
PRIMARY KEY (`emp_no`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
alter table employees add index idx_lastname_firstname(last_name,first_name);
INSERT INTO `employees` (`emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`) VALUES (1,
NULL, ‘698‘, ‘liu‘, ‘F‘, NULL);
INSERT INTO `employees` (`emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`) VALUES (2,
NULL, ‘d99‘, ‘zheng‘, ‘F‘, NULL);
INSERT INTO `employees` (`emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`) VALUES (3,
NULL, ‘e08‘, ‘huang‘, ‘F‘, NULL);
INSERT INTO `employees` (`emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`) VALUES (4,
NULL, ‘59d‘, ‘lu‘, ‘F‘, NULL);
INSERT INTO `employees` (`emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`) VALUES (5,
NULL, ‘0dc‘, ‘yu‘, ‘F‘, NULL);
INSERT INTO `employees` (`emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`) VALUES (6,
NULL, ‘989‘, ‘wang‘, ‘F‘, NULL);
INSERT INTO `employees` (`emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`) VALUES (7,
NULL, ‘e38‘, ‘wang‘, ‘F‘, NULL);
INSERT INTO `employees` (`emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`) VALUES (8,
NULL, ‘0zi‘, ‘wang‘, ‘F‘, NULL);
INSERT INTO `employees` (`emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`) VALUES (9,
NULL, ‘dc9‘, ‘xie‘, ‘F‘, NULL);
INSERT INTO `employees` (`emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`) VALUES (10,
NULL, ‘5ba‘, ‘zhou‘, ‘F‘, NULL);
关闭 ICP:session级别的,全局加global
set optimizer_switch=‘index_condition_pushdown=off‘;
查看参数:
show variables like ‘optimizer_switch‘;
现在我们要查询所有姓 wang,并且名字最后一个字是 zi 的员工,比如王胖子,王
瘦子。查询的 SQL:
select * from employees where last_name=‘wang‘ and first_name LIKE ‘%zi‘ ;
这条 SQL 有两种执行方式:
1、根据联合索引查出所有姓 wang 的二级索引数据,然后回表,到主键索引上查询
全部符合条件的数据(3 条数据)。然后返回给 Server 层,在 Server 层过滤出名字以
zi 结尾的员工。
2、根据联合索引查出所有姓 wang 的二级索引数据(3 个索引),然后从二级索引
中筛选出 first_name 以 zi 结尾的索引(1 个索引),然后再回表,到主键索引上查询全
部符合条件的数据(1 条数据),返回给 Server 层。
很明显,第二种方式到主键索引上查询的数据更少。
注意,索引的比较是在存储引擎进行的,数据记录的比较,是在 Server 层进行的。
而当 first_name 的条件不能用于索引过滤时,Server 层不会把 first_name 的条件传递
给存储引擎,所以读取了两条没有必要的记录。
这时候,如果满足 last_name=‘wang‘的记录有 100000 条,就会有 99999 条没有
必要读取的记录。
执行以下 SQL,Using where:
explain select * from employees where last_name=‘wang‘ and first_name LIKE ‘%zi‘ ;

Using Where 代表从存储引擎取回的数据不全部满足条件,需要在 Server 层过滤。
先用 last_name 条件进行索引范围扫描,读取数据表记录,然后进行比较,检查是
否符合 first_name LIKE ‘%zi‘ 的条件。此时 3 条中只有 1 条符合条件。
开启 ICP:
set optimizer_switch=‘index_condition_pushdown=on‘;
此时的执行计划,Using index condition:
把 first_name LIKE ‘%zi‘下推给存储引擎后,只会从数据表读取所需的 1 条记录。
索引条件下推(Index Condition Pushdown),5.6 以后完善的功能。只适用于二
级索引。ICP 的目标是减少访问表的完整行的读数量从而减少 I/O 操作。
5. 索引的创建与使用
因为索引对于改善查询性能的作用是巨大的,所以我们的目标是尽量使用索引。
5.1. 索引的创建
1、在用于 where 判断 order 排序和 join 的(on)字段上创建索引
2、索引的个数不要过多
——浪费空间,更新变慢。
3、区分度低的字段,例如性别,不要建索引。
——离散度太低,导致扫描行数过多。
4、频繁更新的值,不要作为主键或者索引。
——页分裂
5、组合索引把散列性高(区分度高)的值放在前面。
6、创建复合索引,而不是修改单列索引。
7、过长的字段,怎么建立索引?
--fulltext全文索引 、前缀索引、hash索引等
8、为什么不建议用无序的值(例如身份证、UUID )作为索引?
--无序会产生页分裂、递增的ID离散度较高,不会重复;
5.2. 什么时候用不到索引?
1、索引列上使用函数(replace\SUBSTR\CONCAT\sum count avg)、表达式、
计算(+ - * /):
explain SELECT * FROM `t2` where id+1 = 4;
2、字符串不加引号,出现隐式转换
ALTER TABLE user_innodb DROP INDEX comidx_name_phone;
ALTER TABLE user_innodb add INDEX comidx_name_phone (name,phone);
explain SELECT * FROM `user_innodb` where name = 136;
explain SELECT * FROM `user_innodb` where name = ‘136‘;
3、like 条件中前面带%
where 条件中 like abc%,like %2673%,like %888 都用不到索引吗?为什么?
explain select *from user_innodb where name like ‘wang%‘;
explain select *from user_innodb where name like ‘%wang‘;
过滤的开销太大,所以无法使用索引。这个时候可以用全文索引
4、负向查询
NOT LIKE 不能:
explain select *from employees where last_name not like ‘wang‘
!= (<>)和 NOT IN 在某些情况下可以:
explain select *from employees where emp_no not in (1)
explain select *from employees where emp_no <> 1
注意一个 SQL 语句是否使用索引,跟数据库版本、数据量、数据选择度都有关系。
其实,用不用索引,最终都是优化器说了算。
优化器是基于什么的优化器?
基于 cost 开销(Cost Base Optimizer),它不是基于规则(Rule-Based Optimizer),
也不是基于语义。怎么样开销小就怎么来。
https://docs.oracle.com/cd/B10501_01/server.920/a96533/rbo.htm#38960
https://dev.mysql.com/doc/refman/5.7/en/cost-model.html
问题答疑:
https://gper.club/answers/7e7e7f7ff3g58gc6g6d