什么是索引?索引的本质就是一种排好序数据结构。
数据库中的列存在磁盘上,存储不是连续的,索引记录着这些列在磁盘上的位置(地址)。同时,索引也存储在磁盘上。
推荐一个数据结构在线演示网https://www.cs.usfca.edu/~galles/visualization/Algorithms.html.
下面我们来看一下这些数据结构
当存储的数据为单边增长时,树太深,对于使用二叉树进行搜索时不太友好。
为什么使用B+树作为索引数据结构而不使用B树?
MySQL默认页文件大小16384(16K)假设一行数据大小为1K,那么一页就能存16条数据,也就是一个叶子节点能存16条数据;再看非叶子节点,假设主键ID为BigInt类型,即长度为8B,指针大小在Innodb源码中为6B,一共就是14B,那么一页里就可以存储16K/14=1170个(主键+指针)。一颗高度为3的 B+ 树能存储的数据为:1170117016=21902400
低版本的MySQL根节点常驻内存,高版本的MySQL非叶子节点常驻内存,内存操作对比与磁盘IO时间几乎可以忽略不计,如果使用了索引先在内存中进行定位叶子节点位置,再将该区间的叶子节点进行磁盘IO,Load到内存进行查询。
存储引擎一般都是表级生效,不同的表可以用不同的存储引擎进行存储
MyISAM索引文件和数据文件是分离的(非聚集/簇)
表存储在磁盘中,如果不改动,存储MySQL安装目录的该库的data目录下
对于MyISAM存储引擎用三个文件存储数据 xx.frm(表结构文件),xx.MYD (数据文件),xx.MYI(索引文件)
当我们使用一条SQL语句走MyISAM索引时:第一步先从MYI的B+树结构找到data (数据所在行磁盘地址),第二步有MYD文件找到具体的数据。
InnoDB存储引擎索引与数据是不分离的(聚集/簇)
表存储在磁盘中,如果不改动,存储MySQL安装目录的该库的data目录下
对于 InnoDB 存储引擎用两个文件存储数据 xx.frm(表结构文件),xx.idb (数据文件+索引文件)
当我们使用一条SQL语句走 InnoDB 索引时:直接从 idb 的 B+ 树结构的叶子节点中找到具体数据
主键索引
二级索引
使用 InnoDB 作为存储引擎时,建议表必须建立一个主键,并且推荐使用整型的自增主键。
原因:
1.如果不建立主键,MySQL 会找一列所有值都不相等的列来用于构建 B+树,如果没有这样的列,MySQL 会创建一个隐藏列来维护这个B+树。
2.整型比长串的占用空间更少,比较大小时相对来说更容易,能更快的定义数据的位置
3.B+Tree 需要维护自身平衡,自增不会导致树分裂。
4.在进行部分分页查询时,自增主键可以极大效率的提升搜索效率。
单从索引角度来说,非聚集索引查找速度不如聚集索引,非聚集索引找到索引位置后还需要根据索引找到数据对应的位置
最左前缀匹配原则: 在 MySQL 建立联合索引时会遵守最左前缀匹配原则,即最左优先,在检索数据时从联合索引的最左边开始匹配。
即:当a,b,c为联合索引时遵循最左匹配原则,即:a,ab,abc索引都会生效,但b,c,bc,ac等不会生效(如果使用执行计划会可以看到,type列为index,扫描索引树,效率相对于最左匹配的索引效率极低),所以一定要注意索引顺序,最常用的最段要放在最前面
。
3.例如,创建一个idx_name_age_postion
联合索引,它的索引树图如上图,由图可以看出 name 值是有序的,age 值是无序的,但是在name值相等的情况下age值又是有序的。由此可以看出MySQL创建联合索引时首先会对联合索引的最左边第一个字段排序,在第一个字段的排序基础上,然后在对第二个字段进行排序,以此类推。所以age或postion单独作为条件时,索引是无效的。
4.当a,b,c三个索引都用到时,只有全匹配,无论顺序如何,索引是有效的,MySQL执行计划会对其进行优化,自动使用最优方案执行。
使用 Explain 关键字用于模拟优化器执行SQL语句,在select 语句之前增加 explain 关键字查看执行计划(不会执行这条 SQL
)。
其后跟 show warnings
命令可以得到优化后的查询语句,从而看出优化器优化了什么,部分时候优化后的SQL是无法执行的。
注意: 如果 from 中包含子查询,仍会执行该子查询,将结果放入临时表中 。
explain select * from student_inndb where serialNo='1';
show warnning ;
select 的序列号,有几个 select 就有几个 id,id的顺序是按 select 出现的顺序增长的。
即:id 越大执行优先级越高,id相同则从上往下执行,id为NULL最后执行。
对应行是简单还是复杂的查询。它有以下几种类型:
子查询
和union
。explain select * from student_inndb where serialNo ='1';
set session optimizer_switch='derived_merge=off'; #on表示打开
explain select (select 1 from sys_role where id = 1) from (select * from sys_role where id = 1) derive;
explain select * from student_inndb where serialNo = '1'union all select * from student_inndb where serialNo='2';
这一行正在访问哪个表。
当 from 子句中有子查询时
,table列是
格式,表示当前查询依赖 id=N 的查询,于是先执行 id=N 的查询。
当有 union 时
,UNION RESULT 的 table 列的值为
explain select * from student_inndb where serialNo = '1'union select * from student_inndb where serialNo='2';
高版本 MYSQL 使用 explain
直接就可以展示的列,低版本MySQL使用explain partitions
才能展示。若查询是基于分区表的,会显示查询将访问的分区。
这一列非常非常重要,表示关联类型或访问类型。即MySQL决定如何查找表中的行,查找数据行记录的大概范围。
最优查询等级划分为:system > const > eq_ref > ref > range > index > ALL
一个好的SQL语句至少要达到range级别,最好达到ref。杜绝出现ALL级别.
NULL类型:一个特例,MySQL 执行时,部分查询没有必要访问表或索引。例如:在索引列中选取最小值,可以单独查找索引来完成,不需要在执行时访问表。此类型的查询语句效率也是极高的。
explain select min(serialNo) from student_inndb;
下面来描述具体了类型:
explain select * from (select * from sys_role where id = 1) tmp;
主键索引(primary key)
或唯一索引(unique key)
。只需匹配一行数据
,所有很快。如果将主键置于where列表中,mysql就能将该查询转换为一个const。explain select * from sys_user where id=1;
primary key
或unique key
索引的所有部分被连接使用 ,唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。简单的 select 查询不会出现这种类型。explain select * from sys_user_role left join sys_role on sys_user_role.role_id = sys_role.id;
普通索引
或者唯一性索引的部分前缀
,索引要和某个值相比较,可能会找到多个符合条件的行。explain select * from sys_user_role where user_id = 1;
explain select role_id from sys_role left join sys_user_role on sys_role.id = sys_user_role.role_id;
explain select * from sys_user where id > 0;
只遍历全索引树就能拿到结果,一般是扫描某个二级索引
。这种扫描不会从索引树根节点开始快速查找,而是直接对二级索引的叶子节点遍历和扫描
,速度还是比较慢的,这种查询一般为使用了覆盖索引 [^1],二级索引一般比较小,所以通常比 ALL 快。explain select * from sys_role;
全表扫描
,扫描你的聚簇索引的所有叶子节点。这种情况通常应该被杜绝,这是就要考虑添加索引来进行优化。explain select * from sys_user;
查询时可能使用哪些索引来查找。
该列值为 NULL,则未使用到索引。 有时会出现 possible_keys 有值,而 key 值为 NULL 的情况,这是因为表中数据不多,MySQL 认为索引对此查询帮助不大,而选择全表查询。
用哪个索引来优化。如果没有使用索引,则该列是 NULL。如果想强制 MySQL 使用或忽视 possible_keys 列中的索引,在查询中使用 force index、ignore index。
MySQL在索引里使用的字节数。通过这个值可以算出具体使用了索引中的哪些列。
explain select * from sys_user_role where role_id = 1;
通过该值,可以推算出该查询使用了 idx_role_user_id 的 role_id 来进行索引查找。
key_len计算规则如下:
这一列显示了在key列记录的索引中,表查找值所用到的列或常量,常见的有:const(常量),字段名(例:sys_role.id)
扫描行数。该值是预估值。非结果集里面的行数。
高版本 MYSQL 使用 explain
直接就可以展示的列,低版本MySQL使用explain extended
才能展示,一个百分比的值,rows * filtered/100 可以估算出将要和 explain 中前一个表进行连接的行数(前一个表指 explain 中的 id 值比当前表 id 值小的表)。
额外的详细说明信息。常见的不太友好的值有:Using filesort,Using temporary。
1)Using index:使用覆盖索引
覆盖索引定义:查询的结果集在索引树里面全部包含,无需回表
,这种情况一般可以说是用到了覆盖索引,extra里一般都有 using index;覆盖索引一般针对的是辅助(二级)索引,整个查询结果只通过辅助索引就能拿到结果,不需要通过辅助索引树找到主键,再通过主键去主键索引树里获取其它字段值
explain select name from sys_role; name在索引树中可以直接找到
2)Using where:使用 where 语句来处理结果,并且查询的列未被索引覆盖
explain select * from sys_user where name = 'a';
3)Using index condition:查询的列不完全被索引覆盖,where条件中是一个前导列的范围;
explain select * from sys_user_role where role_id > 1;
4)Using temporary:mysql需要创建一张临时表来处理查询。这种情况首先就要想到使用索引进行优化
对比下面两条SQL:
explain select distinct name from sys_user;
explain select distinct name from sys_role;
第一条SQL的name无索引,需要临表来去重,第二条SQL的name有索引,无需临时表
5)Using filesort:将用外部排序而不是索引排序,数据较小时从内存排序,数据量大时需要在磁盘进行排序。一般考虑使用索引优化。
对比下面两条SQL:
explain select * from sys_user order by name;
explain select * from sys_role order by name;
第一条SQL的name无索引,会检索整个表,保存排序关键字name和对应的id,然后排序name并检索行记录,第二条SQL的name有索引,且表中只又id,name两个索引字段,使用覆盖索引,第三张图片,是添加了一个字段后生成的,由于查询所有,需要检索整个表。
6)Select tables optimized away:使用了聚合函数(比如 max、min)访问某个索引字段
对比下面两条SQL:
explain select max(name) from sys_user;
explain select max(name) from sys_role;
CREATE TABLE `student_inndb` (
`serialNo` varchar(32) NOT NULL COMMENT '流水号',
`name` varchar(40) DEFAULT NULL COMMENT '姓名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`idCard` int(11) DEFAULT NULL COMMENT '身份证号码',
`sex` varchar(10) DEFAULT NULL COMMENT '性别',
PRIMARY KEY (`serialNo`),
KEY `name_age_idcard_idx` (`name`,`age`,`idCard`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1.全值匹配
2.字符串不加单引号索引失效
#有效使用索引
explain select * from student_inndb where idCard = '123456';
#索引失效
explain select * from student_inndb where idCard = 123456;
3.最左前缀法则
4.复合索引中范围条件右边的列的索引不能被使用
#使用索引name_age_idcard_idx
explain select * from student_inndb where name ='张三' and age = 20 and idCard = '123456';
#使用name_age_idcard_idx的前缀name_age
explain select * from student_inndb where name ='张三' and age > 20 and idCard = '123456';
5.尽量使用覆盖索引,减少 select * 语句
6.MySQL 使用 !=, <>,not in ,not exists 会导致索引失效
,从而全表扫描。使用< 、> 、<=,>=时 MySQL 内部优化器会根据检索比例、表大小等多个因素整体评估是否使用索引
7.不在索引列上做任何操作(计算、函数、类型转换),会导致索引失效而转向全表扫描
8.对于 is null,is not null 一般情况下也无法使用索引
9.对于 like 以通配符开头 '%xxx'
可能会导致索引失效而进行全表扫描(借助搜索引擎或覆盖索引优化)
对于 like 不以通配符开头 ‘xxx%’ 会使用下面的索引
explain select * from student_inndb where name like '张%';
对于 like ‘%xxx’ 如果使用了覆盖索引
explain select name,age,idCard from student_inndb where name like '%张%';
like ‘xx%‘相当于=常量,’%xx’ 和 '%xx%'相当于范围
10.范围查询优化:MYSQL内部优化器会根据检索比例、表大小等多个因素整体评估是否使用索引。对于大范围的查询可以将其拆分为多个小范围查询
11.如果查询的结果集没多少,走索引还需要回表操作,MySQL 内部优化器可能会直接让该查询走全部扫描
总结
WHERE语句(a_b_c_idx 为索引) | 索引是否被使用 |
---|---|
where a = 3 | Y,使用到 a |
where a = 3 and b = 4 | Y,使用到a,b |
where a = 3 and b = 4 and c = 5 | Y,使用到a,b,c |
where b = 3 或 c = 3 或 b = 3 and c = 4 | N |
where a = 3 and c = 4 | Y,使用到a |
where a = 3 and b > 4 and c = 5 | Y,使用到a,b |
where a = 3 and b like ‘xx%’ and c = 4 | Y,使用到a,b,c |
where a = 3 and b like ‘%xx’ and c = 4 | Y,使用到a, |
where a = 3 and b like ‘%xx%’ and c = 4 | Y,使用到a, |
where a = 3 and b like ‘x%xx%’ and c = 4 | Y,使用到a,b,c |