MySQL进阶篇之二:索引

索引

一、索引概述

  • 介绍

索引(index)是帮助MySQL高效获取数据数据结构(有序)。

在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。

  • 演示查找语句:select * from user where age=45;

  • 无索引:在无索引的情况下,我们需要对整张表从第一个指针位开始,依次查找表中的所有字段,就算有一个字段的age=45,我们仍然需要查下去,因为后面的地址中可能还会存在age=45的字段。
    -在这里插入图片描述

  • 有索引:我们构思一个二叉排序树,建表的时候将每个字段的age值抽象到二叉排序树中,然后再次执行select操作的时候可以直接在二叉排序树进行高效的查找。在这里插入图片描述

备注:上述二叉树索引结构只是一个示意图,并不是真的索引结构。

  • 优缺点

在这里插入图片描述

二、索引结构

MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的结构,主要包含以下几种:

在这里插入图片描述

  • 不同索引支持的存储引擎

在这里插入图片描述

在后续的操作中,如果我们没有特意指定是哪种存储引擎、哪种索引结构,就默认是InnoDB下的B+tree索引。

2.1二叉树结构

在讲解B+tree之前,先介绍一下二叉排序树。

在这里插入图片描述

二叉排序树:在插入结点的时候,如果当前节点比根结点小,则插入到根结点的左孩子位置。

从上图左侧我们可以看到,如果数据的组成是杂乱无章,且复杂的,使用二叉排序树检索17这个数字,我们只需要查找四次即可。

但是如果数据的组成是有规律的,且不复杂的,如右侧组成的二叉排序树,数据依次排开形成链表结构,那么此时我们再去检索17这个数字速度就会非常慢。

所以二叉树的缺点就是,顺序插入时,会形成一个链表,查询性能大大降低。在大数据量的情况下,层级较深,检索速度慢。

2.2B-Tree结构

B树其实就是一种多路平衡查找树

多路:指一个结点下面可以包含多个子节点。

举例:以一颗最大度数(max-degree)为5(5阶)的b-tree为例(每个节点最多存储4个key,5个指针)

度:即树的度数,指一个节点的子节点个数

例如下面这个示意图,第一个节点位置存储了四个数据,五个指针,五个指针指向的子节点分别为:小于20、大于20小于30,大于30小于62,大于62小于89。

然后子节点再实现这种结构,这就是B-Tree。

在这里插入图片描述

这里我直接给出了插入一段数据形成的B-Tree结构

具体动态变化的过程可以参考网站:https://www.cs.usfca.edu/~galles/visualization/BTree.html

在这里插入图片描述

2.3B+Tree结构

以一颗最大度数为4(4阶)的b+tree为例:

  • 1、B+Tree中所有元素都会出现在叶子结点
  • 2、上半部分绿框所示的分叶子结点只是起到了索引作用
  • 3、最终的叶子结点才是存放数据的
  • 4、叶子结点形成了一个单向链表

在这里插入图片描述

在这里插入图片描述

  • MySQL对B+树的优化

MySQL索引数据结构对经典的B+Tree进行了又换。在原B+Tree的基础上,增加了一个指向相邻叶子节点的链表指针,形成了带有顺序指针的B+Tree,提高区间访问的性能。

通俗的说,就是将叶子结点从单链表结构优化成了双向循环链表结构

在这里插入图片描述

2.4Hash结构

哈希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中。

举例:给出一张表,表中有三个字段,id,name,age,id为主键,现在我们想为name字段创建一个hash索引的数据结构。

  • 1、先算出这张表当中每一行数据的哈希值。
  • 2、拿到name字段的所有值。
  • 3、针对name字段的所有值,通过内部的hash函数去计算每一个name值应该落在哪一个哈希表的槽位上。
  • 4、例如算出’金庸’该name字段的槽位值是005,此时在这个槽位中存储’金庸’这个key,以及’金庸’这个key对应的该行的哈希值。

在大数据量的情况下,如果两个(或多个)键值,映射到一个相同的槽位上,他们就产生了hash冲突,可以通过链表来解决。

在这里插入图片描述

  • Hash索引特点

    • 1、hash索引只能用于对等比较(=,int),不支持范围查询(between,>,<,…)
    • 2、无法利用索引完成排序操作
    • 3、查询效率高,通常只需要一次检索就可以了(不出现哈希冲突的情况下),效率通常要高于B+tree索引
  • 存储引擎支持

    • 在MySQL中,支持hash索引的是Memory引擎,而InnoDB中具有自适应hash功能,hash索引是存储引擎根据B+Tree索引在指定条件下自动构建的。

2.5思考

  • 为什么InnoDB存储引擎选择使用B+Tree索引结构?
    • 1、相对于二叉树:B+Tree层级更少,搜索效率高。
    • 2、相对于B-Tree:B-Tree结构无论是叶子结点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低。
    • 3、相对于Hash索引:B+Tree支持范围匹配以及排序操作。

三、索引分类

在这里插入图片描述

在InnoDB存储引擎中,根据索引的存储形式,又可以分为以下两种:

在这里插入图片描述

  • 聚集索引选取规则
    • 1、如果存在主键,主键索引就是聚集索引。
    • 2、如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引。
    • 3、如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引。

举例:

在这里插入图片描述

  • 回表查询

回表查询指的是:先根据二级索引拿到主键值,再根据主键值走聚集索引拿到这一行的数据

举例:select * from user where name = ‘Arm’;

先根据name=‘Arm’,到二级索引中查找‘Arm’字段,找到‘Arm’字段后,根据该字段存储的主键值,到聚集索引查找id为10的row数据。

在这里插入图片描述

  • 思考

1、以下SQL语句,哪个执行效率高?为什么?

select * from user where id = 10;
select * from user where name = 'Arm';
#id为主键,name字段创建的有索引。

第一条sql语句执行的效率高,因为第一条sql可以直接到聚合索引中查数据,然后拿到row结果;

但是第二条数据需要先到二级索引中查‘Arm’字段,根据查到的字段,拿到对应的主键值,再回到聚合索引中查row。也就是需要执行回表查询,效率不如直接查主键高。

2、InnoDB主键索引的B+Tree高度为多高?

在这里插入图片描述

四、索引语法

4.1创建索引

create [unique|fulltext] index 索引名 on 表名(字段名1,...);

在INDEX之前我们可以加上关键字UNIQUE或FULLTEXT,UNIQUE表示创建的是一个唯一索引,FULLTEXT表示创建的是一个全文索引。这两个关键字是一个可选项,如果不加这两个指定索引关键字的话,表示创建的是一个常规索引。

INDEX后是索引名称

ON指定表名,以及表中的字段名;

一个索引可以关联多个字段,如果一个索引只关联一个字段,这种索引称之为单列索引,如果关联多个字段的话,称为联合索引组合索引

4.2查看索引

show index from 表名;

4.3删除索引

drop index 索引名 on 表名;

4.4案例

按照下面的需求,完成索引的创建。

  • 涉及的表结构

在这里插入图片描述

  • 1、name字段为姓名字段,该字段的值可能会重复,为该字段创建索引。
create index idx_user_name on tb_user(name);
  • 2、phone手机号字段的值,是非空且唯一的,为该字段创建唯一索引。
create unique index idx_user_phone on tb_user(phone);
  • 3、为profession、age、status创建联合索引。
create index idx_user_pro_age_sta on tb_user(profession,age,status);
  • 4、为email建立合适的索引来提升查询效率
create index idx_user_email on tb_user(email);

五、SQL性能分析

5.1查看执行频次

  • SQL执行频率

MySQL客户端连接成功后,通过show[session|global] status 命令可以提供服务器状态信息。通过如下指令,可以查看当前数据库的INSERT、UPDATE、DELETE、SELECT的访问频次。

show global status like 'Com____';

'__'表示一个模糊查询字符占位。

举例:

在这里插入图片描述

5.2慢查询日志

如果我们现在想对当前数据库进行优化,我们应该对哪些SQL进行优化?

因为通过查看执行频次,我们只知道了SQL的执行频次,并不知道需要具体对哪些语句进行优化。

因此我们就需要数据库的慢查询日志来定位数据库的慢查询语句,从而对这些语句进行优化。

  • 慢查询日志

慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志。

MySQL的慢查询日志默认没有开启,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:

#开启MySQL慢查询日志开关
slow_query_log = 1
#设置慢日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time = 2

在这里插入图片描述

在这里插入图片描述

配置完毕之后,通过以下指令重新启动MySQL服务器进行测试,查看慢日志文件中记录的信息/var/lib/mysql/localhost-slow.log。

当我此时查询一个超过千万级别的数据表的count数据时,tail命令实时跟踪的slow.log文件输出的内容:

在这里插入图片描述

5.3show profiles

我们通过MySQL中提供的慢查询日志可以定位出哪些SQL语句的执行耗时比较长,从而对这一类SQL语句进行优化;

但是慢查询日志中记录的SQL语句是执行耗时超过了我们预设的指定时间之后才会记录;

比如我们设定的预设时间为2s,但是系统中有一些SQL语句的执行时间达到了1.99s或1.9999s,这些SQL语句并不会被记录在慢查询日志中,但是这些SQL语句性能同样很低,我们找到这些语句并对其进行优化。

  • profile详情

show profiles能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。

通过have_profiling参数,能够看到当前MySQL是否支持profile操作:

select @@have_profiling;

在这里插入图片描述

我们默认情况下profiling是关闭的(0表示当前profiling是关闭的),可以通过set语句在session/global级别开启profiling:

在这里插入图片描述

set profiling = 1;

举例:执行一系列的业务SQL的操作,然后通过如下指令查看指令的执行耗时:

#查看每一条SQL的耗时基本情况
show profiles;
#查看指定query_id的SQL语句各个阶段的耗时情况
show profiles for query query_id;
#查看指定query_id的SQL语句CPU的使用情况
show profile cpu for query query_id;

执行show profiles;

在这里插入图片描述

执行show profile for query query_id;

在这里插入图片描述

5.3explain执行计划

explain或者desc命令获取MySQL如何执行select语句的信息,包括在select语句执行过程中表如何连接和连接的顺序。

语法:

# 直接在select语句之前加上关键字explain/desc
explain select 字段列表 from 表名 where 条件;

在这里插入图片描述

  • EXPLAIN执行计划各字段含义

  • Id:select查询的序列号,表示查询中执行select子句或者是操作表的顺序(id相同,执行顺序从上到下;id不同,值越大,越先执行)。

    在这里插入图片描述

id值相同的情况:

这是给出的三张表,student和course表通过student_course表进行关联查询

可以看到我们的sql查询执行的顺序为,s、sc、c。

explain select s.*,c.* from student s,course c,student_course sc
where s.id = sc.studentid and c.id = sc.courseid;

在这里插入图片描述

id值不同的情况:

查询选修了MySQL课程的学生(用子查询)

首先查询MySQL课程的id号:

select id from course c where c.name = 'MySQL';

从关联表查询选修了该课程同学的id:

select studentid from student_course sc
where sc.courseid=(select id from course c where c.name = 'MySQL');

最后根据拿到的studentid再去查学生的信息:

select * from student s
where s.id in(select studentid from student_course sc where sc.courseid=(select id from course c where c.name = 'MySQL'));

在这里插入图片描述

  • select_type:表示select的类型,常见的取值有simple(简单表,即不使用表连接或子查询)、primary(主查询,即外层的查询)、union(union中第二个或后面的查询语句)、subquery(select/where之后包含了子查询)等
  • type:表示连接类型,性能由好到差的连接类型为NULL、system、const、eq_ref、range、index、all
    • 对唯一索引的查询是const
    • 对非唯一索引的查询是ref
  • possible_key:显示可能应用在这张表上的索引,一个或多个
  • key:显示实际用到的索引,如果为NULL,即没有使用索引
  • key_len:表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好。
  • rows:MySQL认为必须要执行查询的行数,在innoBD引擎的表中,是一个估计值,可能并不总是准确的。
  • filtered:表示返回的结果行数占需读取行数的百分比,filtered的值越大越好。

六、索引使用

6.1验证索引效率

在未建立索引之前,执行如下SQL语句,查看SQL的耗时。

select * from tb_sku where sn = '10000003145001';

在这里插入图片描述

耗时长达21s!!性能极低。

还是查询这条数据,但是我们通过id查询:

select * from st_sku where id = 1;

在这里插入图片描述

我们可以发现,同样是查询一条数据,用主键id为条件查询,效率极高。

这是因为,我们的id是主键,主键默认建立了主键索引,但是sn字段没有索引。

那么现在我们对sn字段创建索引,与刚刚的查询时间进行对比:

create index idx_sku_sn on tb_sku(sn);

在这里插入图片描述

此时再次执行查询操作:

在这里插入图片描述

效率提升了2000倍…

6.2最左前缀法则

如果索引了多列(联合索引),就要遵守最左前缀法则。最左前缀法则指的是从索引的最左列开始,并且不跳过索引中的列。

如果在查询过程中跳过了某一列,索引将部分失效(后面的字段索引失效)

举例:给出一张tb_user表,其中的profession、age、status三个字段用了联合索引,其中profession为第一个字段,age为第二个字段,status为第三个字段。

在这里插入图片描述

现在我们依次对以下五条SQL进行执行,并执行explain操作,查看索引的使用情况。

在这里插入图片描述

①执行第一条SQL,我们发现key_len为57,且三个字段的索引全部用上了。

在这里插入图片描述

②执行第二条SQL,即删去status字段筛选条件,我们发现key_len为49,所以我们可以推测status字段的索引长度为5。

在这里插入图片描述

③执行第三条SQL,即删除age和status字段筛选条件,我们发现key_len为47,所以可以推测status字段的索引长度为5,age字段的索引长度为2。

在这里插入图片描述

④执行第四条SQL,即删除profession字段筛选条件,根据age和status来查,我们发现并没有走索引,走的是全表扫描。这是因为没有满足最左前缀法则,最左边的列为profession,没有存在

在这里插入图片描述

⑤如果我们只删除age字段筛选条件,即用profession和status来查,我们发现索引长为47,即之前推断的profession字段的索引长度,status索引并没有执行,这是因为我们跳过了age索引,所以导致索引部分失效,即age之后的索引失效

在这里插入图片描述

6.3范围查询

在联合索引中,出现范围查询(>,<),范围查询右侧的列索引失效

举例:我们执行下面这两条SQL语句。

在这里插入图片描述

①执行第一条语句,我们发现索引长度为49,即status字段没有走索引,这是因为age字段用了范围查询,导致右侧的列索引失效

在这里插入图片描述

②如果想规避这种情况,我们尽量使用>=或<=,可以解决。

在这里插入图片描述

6.4索引失效情况

6.4.1索引列运算

不要在索引列上进行运算操作,索引会失效

举例:

在正常情况下我们对phone字段进行查找,用到了phone索引

在这里插入图片描述

此时我们对该SQL语句执行函数运算,即用substring()函数截取phone字段的数值进行匹配,发现索引失效

在这里插入图片描述

6.4.2字符串不加引号

字符串类型字段使用时,不加引号,索引会失效

举例:

还是对phone字段进行查询操作,我们的phone字段是一个varchar类型的,现在模拟不加引号的情况,我们发现,possible_key可能存在的索引确实为phone索引,但是实际key却为null。

在这里插入图片描述

6.4.3模糊查询

如果仅仅是尾部模糊匹配,索引不会失效。

如果是头部模糊匹配,索引会失效。

举例:

尾部模糊匹配

在这里插入图片描述

头部模糊匹配索引失效

在这里插入图片描述

6.4.4or连接的条件

用or分割开的条件,如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及到的索引都不会被用到。

举例:执行下面这两条SQL

在这里插入图片描述

注意:我们的age字段使用的是联合索引,并没有单独的索引。

执行第一条SQL,我们发现虽然id为主键索引,但是age的没有单独的age索引,所以此时索引不生效。

在这里插入图片描述

同样的,执行第二条SQL我们会得到同样的结果。

在这里插入图片描述

由于age没有索引,所以即使id、phone有索引,索引也会失效。如果想解决这个问题,我们只需要针对age也建立一个索引即可。

6.4.5数据分布影响

如果MySQL评估使用索引比全表更慢,则不使用索引。

6.5 SQL提示

SQL提示,是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。

例如,我现在给之前的profession字段新增一个索引idx_user_pro,此时该字段拥有两个索引。

那么我现在执行查询语句,该字段会执行哪个索引?

显而易见,执行了联合索引。那么如果我想指定该字段执行我想要执行的索引,需要进行什么操作?这就是我们的SQL提示功能,即“给SQL提示”。

在这里插入图片描述

  • use index(建议数据库使用该字段的哪个索引,并不是强制,MySQL会自己对速度、效率进行权衡)
explain select * from tb_user use index(idx_user_pro) where profession = '软件工程';

在这里插入图片描述

  • ignore index(告诉数据库不使用该字段的哪个索引)
explain select * from tb_user ignore index(idx_user_pro) where profession ='软件工程';

在这里插入图片描述

  • force index(强制该数据库使用指定的索引)
explain select* from tb_user force index(inx_user_pro) where profession = '软件工程';

6.6覆盖索引

在我们的查询过程中,尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到),减少select *的使用。

举例:where筛选条件都一样,我们分析select查询返回的对象不同的形式。

在这里插入图片描述

注意:

using index condition:查找使用了索引,但是需要回表查询数据。

using where;using index:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据。

通过执行四条SQL我们发现,①②④三条语句的extra为using where;using index;

但是第③条语句的extar为using index condition;

  • 解释

在第④条SQL语句中,我们直接通过聚集索引拿到了整行的数据,不需要进行回表查询。

原理解释:

在这里插入图片描述

在第①②条语句中,因为profession、age、status三个字段是联合索引,我们在查询的过程中,直接通过二级索引,既拿到了联合索引三个字段的值,又拿到了对应的主键id值,不需要走回表查询。

原理解释:

在这里插入图片描述

在第③条语句中,多出来了一个name字段!name字段在我们的二级索引中是没有的,此时再根据where筛选条件进行查询,我们不仅要在二级索引中拿到联合索引的各个字段的值,id主键的值,并且还要通过回表查询回到聚集索引,根据已经拿到的id的值,再去查询整张表的数据,然后再拿到name字段的值。

原理解释:

在这里插入图片描述

  • 思考

一张表,有四个字段(id、username、password、status),由于需求量大,需要对以下SQL语句进行优化,该如何进行才是最优方案?

select id,username,password from tb_user where username = 'itcast';

在id为主键索引的前提下,创建usernmae和password的联合索引即可。

6.7前缀索引

当字段类型为字符串(varchar、text等)时,在建立索引时,有时候需要很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO,影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率。

  • 语法
create index idX_XXXX on table_name(column(n));

和之前创建索引的语法基本一致,我们只需要在对应的字段后面加上一个n,表示提取该字符串前面的n个字符来构建索引。

  • 前缀长度

可以根据索引的选择性来决定,选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高,则查询效率越高,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。

求选择性公式:

#该表中不重复的email字段数量/该表的总字段数量
select count(distinct email)/count(*) from tb_user;
#表示截取emial字段的前五个字符,求这五个字符的选择性
select count(distinct substring(email,1,5))/count(*) from tb_user;

七、索引设计原则

  • 对于数据量较大,且查询比较频繁的表建立索引。
  • 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引。
  • 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
  • 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
  • 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。
  • 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。
  • 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值