一、索引失效的场景
MySQL索引失效的场景,什么情况下会造成MySQL索引失效
二、列的离散度
什么是列的离散度?我们先看一下列的离散度 公式:count(distinct(column_name)):count(*),列的全部不同值和所有数据行的比例。数据行数相同的情况下,分子越大,列的离散度就越高。
我们看一下下面的数据,CName的离散度更高,还是CCRedits的离散度更高?很显然CName的离散度更高。
简单来说,如果列的重复值越多,离散度就越低,重复值越少,离散度就越高
。
不建议在离散度低的字段上建立索引
。
没有索引的时候查一遍:
select * from course where ccredits=4;
建立索引之后再查一遍:
ALTER TABLE course DROP INDEX idx_course_ccredits;
ALTER TABLE course ADD INDEX idx_course_ccredits(ccredits); -- 耗时比较久
select * from course where ccredits=4;
我们发现消耗的时间更久了。如果在B+Tree里面的重复值太多,MySQL的优化器发现走索引跟使用全表扫描差不了多少的时候,就算建了索引,也不一定会走索引。
三、联合索引最左匹配
多条件查询的时候,我们通常会建立联合索引。单列索引可以看成是特殊的联合索引。
比如我们在user表上面,给name和phone建立了一个联合索引:
-- name和phone创建联合索引
ALTER TABLE user_innodb DROP INDEX comidx_name_phone;
ALTER TABLE user_innodb add INDEX comidx_name_phone (name,phone);
联合索引在B+Tree中是复合的数据结构,它是按照从左到右的顺序来建立搜索树的(name在左边,phone在右边)。从上图可以看出来,name是有序的,phone是无序的。当name相等的时候,phone才是有序的
。
这个时候我们使用where name = ‘Bob’ and phone = '133xx’去查询数据的时候,B+Tree会优先比较name来确定下一步应该搜索的方向,往左还是往右。如果name相同的时候再比较phone。但是如果查询条件没有name,就不知道第一步应该查询哪个节点,因为建立搜索树的时候,name是第一个比较的因子,所以用不到索引。
1、什么时候用联合索引
我们在建立联合索引的时候,一定要把最常用的列放在最左边。
举个例子:
-- (1)使用两个字段,用到联合索引:
EXPLAIN SELECT * FROM user_innodb WHERE name= '青山' AND phone = '13666666666';
-- (2)用最左边的name字段,用到联合索引:
EXPLAIN SELECT * FROM user_innodb WHERE name= '青山';
-- (3)用最右边的phone字段,无法使用索引,走全表扫描:
EXPLAIN SELECT * FROM user_innodb WHERE phone = '13666666666';
2、什么是覆盖索引
非主键索引,我们先通过索引找到主键索引的键值,再通过主键值查出索引里面没有的数据,它比基于主键索引的查询多扫描了一棵索引树,这个过程就叫回表
。
例如:
select * from user where name = '张三';
在二级索引里面,不管是单列索引还是联合索引,如果select的数据列只用从索引中就能够取得
,不必从数据区中读取,这时候使用的索引就叫做覆盖索引,这样就避免了回表。
我们先建一个联合索引:
-- name和phone创建联合索引
ALTER TABLE user_innodb DROP INDEX comidx_name_phone;
ALTER TABLE user_innodb add INDEX comidx_name_phone (name,phone);
以下三个查询语句属于覆盖索引:
--覆盖索引
EXPLAIN SELECT name,phone FROM user_innodb WHERE name= '青山' AND phone = '13666666666';
EXPLAIN SELECT name FROM user_innodb WHERE name= '青山' AND phone = '13666666666';
EXPLAIN SELECT phone FROM user_innodb WHERE name= '青山' AND phone = '13666666666';
注意:select * 不是覆盖索引。
如果改成只用where phone 查询呢?按照我们之前的分析,它是用不到索引的。实际上可以用到覆盖索引,优化器觉得用索引更快,所以还是用到了索引。
很明显,因为覆盖索引减少了IO次数,减少了数据的访问量,可以大大地提升查询效率。
四、索引条件下推(ICP)
索引条件下推(Index Condition Pushdown),5.6以后完善的功能。只适用于二级索引。ICP的目标是减少访问表的完整行的读数量 从而减少IO操作。
这里说的下推,其实意思是把过滤的动作在存储引擎做完,而不需要到Server层过滤。
举个例子,我们有这样一张表,在last_name 和first_name上面创建联合索引:
-- 索引下推
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 ;
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);
现在我们要查询所有姓wang,并且名字最后一个字是zi的员工,比如王胖子,王瘦子:
select * from employees where last_name='wang' and first_name LIKE '%zi' ;
正常情况来说,因为字符是从左往右排序的,当你把%加在前面的时候,是不能基于索引去比较的,所以只有lase_name(姓)这个字段能够用于索引比较和过滤。
所以,正常的查询过程是这样的:
(1)根据联合索引查出所有姓wang的二级索引数据(拿到3个主键:6、7、8)。
(2)回表,到主键索引上查询全部符合条件的数据(3条数据)。
(3)把这3条数据返回给Server层,在Server层
过滤出名字以zi结尾的员工。
注意,索引的比较是在存储引擎进行的,数据记录的比较,是在Server层进行的。而当firse_name的条件不能用于索引过滤时,Server层不会把firse_name的条件传递给存储引擎,所以读取了两条没有必要的记录。比如说,如果满足lase_name='wang’的记录有10万条,就会有99999条没有必要读取的记录。
所以,根据firse_name字段过滤的动作,能不能在存储引擎层完成呢?所以,MySQL使用了索引条件下推
:
(1)根据联合索引查出所有姓wang的二级索引数据(拿到3个主键:6、7、8)。
(2)然后从二级索引中筛选出first_name以zi结尾的索引(1个索引)。
(3)回表,到主键索引上查询全部符合条件的数据(1条数据),返回给Server层。
很明显,使用索引条件下推的方式到主键索引上查询的数据更少。
ICP是默认开启的,也就是说针对于二级索引,只要能够把条件下推给存储引擎,它就会下推,不需要我们干预:
-- 关闭ICP
set optimizer_switch='index_condition_pushdown=off';
-- 开启ICP
set optimizer_switch='index_condition_pushdown=on';
-- 查看ICP参数
show variables like 'optimizer_switch';
开启后ICP后的执行计划,Uning index condition表示使用了索引下推:
关闭ICP后的执行计划,Using where表示在Server层过滤了: