(03)索引

1. 概念

索引底层存储用的B+树

一张表中有2个索引,那就会有2个B+树。(索引就是B+树存储的,可以理解为索引就是B+树)

什么是B+树?参考连接
https://cloud.tencent.com/developer/article/1681803

特殊情况:mysql的 memery 存储引擎使用的是hash表存储,而不是B+树。InnoDB、MyISAM
的索引结构是 B+树索引

注意:使用索引时,尽可能的使用长度较小的列

2. 索引分类

2.1. 主键索引

建表时,mysql会自动为主键创建索引。主键索引属于特殊的唯一索引,不能为空值,且每张表只能有一个主键索引

2.2. 唯一索引

ALTER TABLE table_name ADD UNIQUE (column)

唯一索引允许空值,一张表中可以有多个唯一索引

1.主键一定是唯一性索引,唯一性索引并不一定就是主键;
2.一个表中可以有多个唯一性索引,但只能有一个主键;
3.主键列不允许空值,而唯一性索引列允许空值;

2.3. 组合索引

ALTER TABLE table_name ADD INDEX index_name (column1,column2,...)

使用组合索引时,需要注意组合索引的顺序

2.3.1. 案例

建立组合索引(a,b,c)
ALTER TABLE table_name ADD INDEX index_name (a,b,c)

执行上边sql语句,相当于建立了a、ab、abc三个索引
一个索引顶三个索引是好事。如果不使用联合索引,而是单独建立3个索引,那么每多一个索引都会增加写操作的开销和磁盘空间的开销

会存在覆盖索引(什么是覆盖索引,下边会讲)。有如下sql
select a,b,c from table where a=1and b = 2。
那么MySQL可以直接通过遍历索引取得数据,而无需回表查询(什么是回表,下边会讲),这减少了很多的随机io操作

组合索引匹配越多,筛选数据越快。
假设一张表中,有1000w条数据,a=1的数据占10%,b=2的数据占10%,c=3的数据占10%

不使用联合索引,把a,b,c拆分为3个单独索引,有如下sql
select * from table where a = 1 and b =2 and c = 3。
检索次数为1000W*10%=100w ,然后再回表从100w条数据中找到符合b=2 and c= 3的数据。

使用联合索引,(a,b,c)为一个整体。有如下sql
select * from table where a = 1 and b =2 and c = 3。
检索次数为1000w *10% *10% *10%=1w

2.4. 前缀索引

前缀索引,就是把一个很长的字符串,取前 length 个字符当索引,难点在于这个 length 值取多少合适

当索引列是很长的字符串,对该列创建索引对查询效率提升不是很大,因为索引的B+树会很大。
可以对很长的列创建前缀索引,语法如下
ALTER TABLE table_name ADD KEY(column_name(length));

前缀索引的长度length怎么设置合适?
可以通过 COUNT(DISTINCT LEFT(column_name(length))) / COUNT(*) 来推测长度

select
    count(distinct left(str1, 4)) / count(*),
    count(distinct left(str1, 5)) / count(*),
    count(distinct left(str1, 6)) / count(*),
    count(distinct left(str1, 7)) / count(*),
    count(distinct left(str1, 8)) / count(*),
    count(distinct left(str1, 9)) / count(*),
    count(distinct left(str1, 10)) / count(*)
from t_data;

计算结果如下

  • 根据上边计算结果,长度越大,求出结果越接近1,越接近1则代表该长度越合适
  • 但是,并不是长度越大越好,长度大代表索引的B+树大,会导致查询效率不高
  • 所以,在选取长度时,可以选取结果增长不是很大的长度

由上边结果可看出,最合适的长度是 7 或 8 。
因为长度为 4 ~ 6 时结果变动很大, 7 ~ 10 时结果趋向平稳,所以长度在 7 或 8 最合适

3. 聚簇索引/二级索引/非聚簇索引

聚簇索引/二级索引/非聚簇索引 不是单独的索引类型,而是一种数据存储方式

3.1. 聚簇索引

InnoDB主键id索引类型为聚簇索引

聚簇索引就是把InnoDB引擎表的主键构造一颗B+树,同时叶子节点(最底层节点)中存放的就是整张表的行记录数据
说白了聚簇索引的叶子节点存放的是主键值+数据行

假设user表id为主键,数据库类型为InnoDB,那么建表时MySql会自动为主键id创建聚簇索引,如下图

3.2. 二级索引

聚簇索引的叶子节点存放的是主键值+数据行,二级索引的叶子节点存放的是索引值+主键值

如果我手动创建一个普通索引
ALTER TABLE user ADD INDEX in_name (name)
那么name这个索引的数据结构如下图

3.3. 非聚簇索引

MyISAM主键id索引类型为非聚簇索引

非聚簇索引就是把MyISAM引擎表的主键构造一颗B+树,非聚簇索引将数据与索引分开存储,索引结构的叶子节点指向了数据对应的位置
说白了非聚簇索引的叶子节点存放的是主键值+数据行所在地址

假设user表id为主键,数据库类型为MyISAM,那么建表时MySql会自动为主键id创建非聚簇索引,如下图

4. 常见名词

4.1. 回表

假设user表有id,name,age三个字段,id为主键,name为普通索引(二级索引)。当执行
select id,name,age from user where name = 'xx'
时,由于name索引的B+树子节点存储的是索引值+主键值(找不到age字段),那么就会根据主键id再去执行聚簇索引匹配到age字段,这种方式就称为回表(只有建普通索引才存在回表的情况)

4.2. 覆盖索引

如果把上边的回表sql改为
select id,name from user where name = 'xx'
,由于name索引存储的是索引值+主键值,而此时查询结果也只有索引值+主键值不需要去聚簇索引重新查询数据,这种方式就称为索引覆盖

4.3. 最左匹配

假设user表有id,a,b,c四个字段,(a,b,c)是联合索引

那么只有以下3种情况会走索引

explain select * from user where a = '1';
explain select * from user where a = '1' and b = '2';
explain select * from user where a = '1' and b = '2'  and c='3';

不会走索引

explain select * from user where b = '2';
explain select * from user where c = '3';
explain select * from user where b = '2' and c = '3';

因为跳过了a条件,无法形成最左匹配

只走一半的索引

explain select * from user where a = '1' and c = '3'

a='1' 符合最左匹配,所以a='1'会走索引。但是c='3'是跳过了b,所以c='3'是在a='1'的结果中再进行筛选的,所以c='3'没有走索引

explain select * from user where a = '1' and b > '3' and c = '4'

a='1'符合最左匹配,所以a='1'走索引。b是范围查询,b>'3'走索引。由于b是范围查询,导致b之后的所有条件都不走索引,所以c='4'不走索引

4.4. 索引下推

假设user表有id,name,sex三个字段,(name,sex)是联合索引

假设数据为:

id name sex
1 张三
2 张四

执行sql为

select * from user where name like '张%' and sex='男'

没有索引下推时
索引先根据name最左匹配到主键(忽略sex),找到id分别为1、2两条数据,然后再根据两个id去匹配主键索引(聚簇索引),导致匹配了2次

有索引下推时
索引先根据name最左匹配到主键,再匹配sex,找到id为1的数据,然后再根据一个id去匹配主键索引,只需要匹配1次就得到结果

5. 索引匹配方式

有如下表结构

create table staffs(
    id int primary key not null,
    name varchar(24),
    age varchar(24) ,
    pos varchar(24),
    crtime timestamp
);
alter table staffs add index(name, age, pos);

5.1. 全值匹配

索引中所有列匹配
explain select * from staffs where name='' and age='' and pos='';

name走索引,age走索引,pos走索引。

5.2. 匹配最左前缀

只匹配最左几列

explain select * from staffs where name='' and age = '';

name走索引,age走索引。

explain select * from staffs where age = '' and name='';

name走索引,age走索引。sql优化器会自动把name排在前边

explain select * from staffs where age = '' and pos='';

age不走索引,pos不走索引。不符合最左前缀匹配

5.3. 匹配列前缀

匹配某列值开头部分
explain select * from staffs where name like 'a%';

name走索引

explain select * from staffs where name like '%a';

name不走索引。模糊查询%放在前边导致不走索引。把索引当做B+树来看待,开头是%导致不知道怎么去遍历索引,于是MySQL索性就全表扫描

5.4. 匹配范围值

查找一个范围数据
explain select * from staffs where name > 'a';

name走索引

explain select * from staffs where name > 'a' and age='13'

name走索引,age不走索引。因为范围查询之后的所有条件,都会索引失效

5.5. 精确匹配某一列,并范围匹配另一列

精确匹配某一列,并范围匹配另一列
explain select * from staffs where name = 'a' and age > '3';

name走索引,age走索引。如果age之后有pos='xx',则pos不走索引。因为范围查询之后的所有条件,都会索引失效。

5.6. 只访问索引的查询

只访问索引的查询
explain select name,age,pos from staffs where name = 'a';

name走索引,同时索引覆盖。查询时只查索引的列,其他列都不查询,本质上就是索引覆盖。
如果组合索引是(name,age),则该语句不满足索引覆盖,因为查询出来的pos不属于组合索引

6. crc32 的使用

crc32可以把一个很长的字符串,转为int类型

使用方式如select crc32('我是很长的字符串')查询结果为 468066450

案例:假设有很长的url存到了MySQL中,我要利用这个字段去精确查询某行记录,怎么创建索引更好?

  • 创建索引首先要明白,索引列的数据越小越好
  • 很明显url这个字段不适合当索引,因为有的url特别长
  • 为了解决url过长问题,可以新增一个列crc32Url,用来存储url的crc32结果(md5也同理,都是为了把长的字符串变小)
  • 然后再对 crc32Url 这个字段做索引,这样速度比直接用url做索引快很多
posted @ 2022-01-23 23:23  、嘎路的米。  阅读(152)  评论(0编辑  收藏  举报