深入理解mysql索引(四)之索引使用原则

上一篇学习了索引的具体2种落地,这一篇学习索引的使用原则,我们在日常sql调优的时候,第一个想到的可能就是“加个索引”,但是你有没有考虑过这样的做法有没有问题,任何事物都是物极必反,什么东西都不是越多越好的,索引也是如此

我们都知道,在mysql中,索引也是存储在文件中,并且索引是一种树型的数据结构,他的这种数据结构是需要维护的,所以,如果一个表中的索引太多的话(一般一张表不超过10个索引),那么这张表的索引就占用很大的空间,并且,在这样表增删改的时候,也会消耗大量的资源,引起性能问题。

索引的使用原则

1. 列的离散度

公式:
count(distinct(column_name)) : count(*),列的全部不同值和所有数据行的比例。
数据行数相同的情况下,分子越大,列的离散度就越高。

用白话说就是:如果列的重复值越多,离散度就越低,重复值越少,离散度就越高。
例子:在 name和gender分别建立索引
当我们用在 gender上建立的索引去检索数据的时候,由于重复值太多,需要扫描的行数就更多。例如,我们现在在 gender列上面创建一个索引,然后看一下执行计划。

ALTER TABLE user_innodb DROP INDEX idx_user_gender;
ALTER TABLE user_innodb ADD INDEX idx_user_gender (gender); -- 耗时比较久
EXPLAIN SELECT * FROM `user_innodb` WHERE gender = 0;

在这里插入图片描述
而 name 的离散度更高,比如“张三”的这名字,只需要扫描一行。

ALTER TABLE user_innodb DROP INDEX idx_user_name;
ALTER TABLE user_innodb ADD INDEX idx_user_name (name);
EXPLAIN SELECT * FROM `user_innodb` WHERE name = '张三';

在这里插入图片描述
结论: 建立索引,要使用离散度(选择度)更高的字段。
如果在 B+Tree 里面的重复值太多,MySQL 的优化器发现走索引跟使用全表扫描差
不了多少的时候,就算建了索引,也不一定会走索引。
在这里插入图片描述

2. 联合索引最左匹配原则

这里需要先说明两点:

  • 单列索引可以看成是特殊的联合索引
  • 联合索引是 一个 索引

我们在 user 表上面,给 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=‘Mic’ and phone = '133xx’去查询数据的时候,B+Tree 会优先比较 name 来确定下一步应该搜索的方向,往左还是往右。如果 name相同的时候再比较 phone。但是如果查询条件没有 name,就不知道第一步应该查哪个节点,因为建立搜索树的时候 name 是第一个比较因子,所以用不到索引。

大家可以试着在自己的数据库中试一下。

在联合索引(ABC)中,使用where 后面跟着ABC、A、AB可以使用索引 。用 where B 和 where BC 和 where AC是不能使用到索引的。
结论: 在建立联合索引的时候,一定要把最常用的列放在最左边。不能不用第一个字段、按顺序、不能中断。

假如有个联合索引ab, 那么where条件后面跟的是 where b = XX and a = XX 也是可以用到索引的,为什么?因为底层优化器会自动优化,知道你想用ab这个联合索引。

图示理解:
在这里插入图片描述

另:

CREATE INDEX idx_name on user_innodb(name);
CREATE INDEX idx_name_phone on user_innodb(name,phone);
当我们创建一个联合索引的时候,按照最左匹配原则,用左边的字段 name 去查询
的时候,也能用到索引,所以第一个索引完全没必要。

如果我们创建三个字段的索引 index(a,b,c),相当于创建三个索引:
index(a)
index(a,b)
index(a,b,c)
虽然说相当于三个索引,但是 **注意** :联合索引算一个索引

以上就是 MySQL 联合索引的最左匹配原则。

覆盖索引

在这里插入图片描述

**回表:**非主键索引,我们先通过索引找到主键索引的键值,再通过主键值查出索引里面没有的数据,它比基于主键索引的查询多扫描了一棵索引树,这个过程就叫回表。

在辅助索引里面,不管是单列索引还是联合索引,如果 select 的数据列只用从索引中就能够取得,不必从数据区中读取,这时候使用的索引就叫做覆盖索引,这样就避免了回表

我们先来创建一个联合索引:

-- 创建联合索引
ALTER TABLE user_innodb DROP INDEX comixd_name_phone;
ALTER TABLE user_innodb ADD INDEX `comixd_name_phone` (`name`,`phone`);

这三个查询语句都用到了覆盖索引:

EXPLAIN SELECT name,phone FROM user_innodb WHERE name= '张三' AND phone = '13888888888';
EXPLAIN SELECT name FROM user_innodb WHERE name= '张三' AND phone = ' 13888888888';
EXPLAIN SELECT phone FROM user_innodb WHERE name= '张三' AND phone = ' 13888888888';

Extra 里面值为“Using index”代表使用了覆盖索引。
在这里插入图片描述
select * ,用不到覆盖索引。
很明显,因为覆盖索引减少了 IO 次数,减少了数据的访问量,可以大大地提升查询
效率。

4. 索引条件下推( ICP )

索引条件下推(Index Condition Pushdown)(了解),5.6 以后完善的功能。只适用于二级索引。ICP 的目标是减少访问表的完整行的读数量从而减少 I/O 操作。

这里说的下推,其实是意思是把过滤的动作在存储引擎做完,而不需要到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
  DEFAULT CHARSET = utf8;

alter table employees add index idx_lastname_firstname(last_name,first_name);

现在我们要査询所有姓wang,并且名字最后一个字是zi的员工,比如王胖子,王瘦子。查询的SQL:

select * from employees where last_name='wang' and first_name LIKE '%zi'

正常情况来说,因为字符是从左往右排序的,当你把%加在前面的时候,是不能基于索引去比较的,所以只有last_name (姓)这个字段能够用于索引比较和过滤。

所以查询过程是这样的

  1. 根据联合索引查出所有姓wang的二级索引数据(3个主键值:6、7、8)。
  2. 回表,到主键索引上查询全部符合条件的数据(3条数据)。
  3. 把这3条数据返回给Server层,在Server层过滤出名字以zi结尾的员工。

在这里插入图片描述
注意,索引的比较是在存储引擎进行的数据记录的比较是在Server层进行的。 而当first_name的条件不能用于索引过滤时,Server层不会把first_name的条件传递给存储引擎,所以读取了两条没有必要的记录。
这时候,如果满足last_name='wang’的记录有10万条,就会有99999条没有必要读取的记录。所以,根据first_name字段过滤的动作,能不能在存储引擎层完成呢?

第二种查询方法:

  1. 根据联合索引查出所有姓wang的二级索引数据(3个主键值:6、7、8)
  2. 然后从二级索引中筛选出first_name以zi结尾的索引(1个索引)
  3. 然后再回表,到主键索引上查询全部符合条件的数据(1条数据),返回给Server 层。

在这里插入图片描述
很明显,第二种方式到主键索引上査询的数据更少。

ICP是默认开启的,也就是说针对于二级索引,只要能够把条件下推给存储引擎,它就会下推,不需要我们干预:

set optimizer_switch = 'index_condition_pushdown=on';

此时的执行计划:using index condition;
在这里插入图片描述
把first_name LIKE %zi’下推给存储引擎后,只会从数据表读取所需的1条记录。

关闭ICP:

set optimizer_switch = 'index_condition_pushdown=off;

查看参数:

show variables like 'optimizer_switch';

执行以下 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条符合条件。

索引的创建与使用

因为索引对于改善查询性能的作用是巨大的,所以我们的目标是尽量使用索引。

索引的创建

  1. 在用于 where 判断 order 排序和 join 的(on)、group by 字段上创建索引
  2. 索引的个数不要过多。(一般不超过10个) – 浪费空间,更新变慢
  3. 离散度(区分度)的字段,例如性别,不要建索引。——离散度太低,导致扫描行数过多。有可能不会用到索引
  4. 频繁更新的值,不要作为主键或者索引。-- 导致页分裂
  5. 组合索引把散列性高(区分度高)的值放在前面。
  6. 能创建复合索引的时候,就不要创建单列索引。
  7. 不建议用无序的值(例如身份证、UUID )作为索引 – 无序,分裂
  8. 过长的字段,建立前缀索引
    CREATE TABLE 'pre_test' (
    `content` varchar(20) DEFAULT NULL,
    KEY `pre_idx` (`content` (6))
    )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    

什么时候用不到索引

  1. 索引列上使用函数(replace\SUBSTR\CONCAT\sum count avg)、表达式、计算(+ - * /):
explain SELECT * FROM `student` where id+1 = 4;
  1. 字符串不加引号,出现隐式转换
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'; -- 用到了索引

注:字段类型是int,where 条件加了单引号 ‘’ 是可以走索引的。 例如 where id = ‘123’ 是可以用到索引的

  1. like 条件中前面带% – 前面带%那不相当于所有数据都符合条件吗?肯定用不到索引。过滤的开销太大,这个时候可以使用全文索引。
  2. 负向查询
    1. NOT LIKE 不能:
    explain select *from employees where last_name not like 'wang'
    
    1. != (<>)和 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),
也不是基于语义。怎么样开销小就怎么来。

使用索引有基本原则,但是没有具体细则,没有什么情况一定用索引,什么情况一定不用索引的规则。

猜你喜欢

转载自blog.csdn.net/nonage_bread/article/details/108432157