当前位置: 首页 > news >正文

MySQL--索引精通详解

性能分析

MySQL常见瓶颈

  • SQL中对大量数据进行比较、关联、排序、分组时CPU的瓶颈

  • 实例内存满足不了缓存数据或排序等需要,导致产生大量的物理IO。查询数据是扫描过多数据行,导致查询效率低。

SQL执行频率

(可以通过查询增删改查语句在数据库中的访问频次来确定优化策略)

MySQL客户端连接成功后,通过

 show [session | global] status like 'Com_______' -- session查看当前绘画的状态信息 global 查看全局的状态信息-- 后面跟7个占位符 Com_delete,Com_insert

可以提供服务器状态信息。通过以下指令,可以查看当前数据库的增删改查语句的访问频次

image-20250429232557797

慢查询日志

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

MySQL的慢查询日志默认没有开启,可以设置指令

 -- 开启MySQL慢查询日志开关set global slow_query_log = true;-- 设置慢查询日志的时间为2秒,sql语句执行时间超过两秒,就记录进慢查询日志set global long_query_time = 2;-- 查看时间:show variables like 'long_query_time%';

在设置之后,系统会自动生成慢查询日志文件,将慢SQL查询记录其中。

查看超时的SQL记录日志:MySQL的数据文件夹下:5.5/Data/设备名称-slow.log

作用:通过慢查询日志来定位哪些SQL语句执行效率较低从而对这类的SQL语句进行优化

注意:非调优场景下,一般不建议启动改参数,慢查询日志支持将日志记录写入文件,开启慢查询日志会或多或少带来一定的性能影响。

profile详情

指令:show proflies 能够在做SQL优化帮助我们了解每一条SQL语句的执行耗时是多少,而且这个耗时耗在哪里了,时间都耗费在哪里,通过have_profilling参数,能够看到当前MySQL是否支持profile操作:

 -- 查看当前数据库是否支持profile操作show variables like 'have_prof%';

默认profiling是关闭的,可以通过set语句在session/global级别开启profiling:

 -- 开启mysql profiling全局开关set  global profiling = true;-- 查看每一条sql的耗时基本情况show profiles ;-- 查看指定query_id 的SQL语句各个阶段的耗时情况show profile  all for query query_id;-- 查看指定query_id的SQL语句CPU的使用情况show profile cpu for query query_id;

Explain

使用Explain关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理SQL语句的。可以用来分析查询语句或是表的结构的性能瓶颈。包括在select语句执行过程中表如何连接和连接的顺序

其作用:

  • 表的读取顺序

  • 哪些索引可以使用

  • 数据读取操作的操作类型

  • 哪些索引被实际使用

  • 表之间的引用

  • 每张表有多少行被优化器查询

  • explain关键字使用起来比较简单:desc/explain + sql 语句

image-20250502163359992

explain重要字段名
id
  • select查询的序列号,表示查询中执行select子句或操作表的顺序

  • id相同时,执行顺序由上至下

  • id不同,如果是子查询,id的序号会递增,id值越大优先级越高,则先被执行

  • id相同和不同都存在时,id相同的可以理解为一组,从上往下顺序执行,所有组中,id值越大,优先级越高越先执行。

代码展示:

 explain select s.`name` '名字',c.`name` '课程' from student s right join student_course sc on s.id = sc.studentid join course c on sc.courseid = c.id;

image-20250502163448661

id相同时,执行顺序从上至下,所以执行顺序依次为sc,s,c,即 student-course,student,course

 --  查询选修了Mysql课程的学生;(子查询)explain select student.`name` from studentwhere student.id in (select student_course.studentid from student_course where courseid = (select id from course where course.name = 'MySqL'))

image-20250502165934258

id相同和不同都存在时,id相同的可以理解为一组,从上往下顺序执行,所有组中,id值越大,优先级越高越先执行。

subquery2:第二个子查询,

所以执行顺序为先执行子查询(查询course表:select id from course where course.name = 'MySqL'),再执行student-course表(select student_course.studentid from student_course where courseid =

id值相同,由上至下执行,因此先执行subquery2(

student.id in (select student_course.studentid from student_course where courseid = ( select id from course where course.name = 'MySqL' ))),最后再执行student表(select student.namefrom student where student.id in

表示查询类型

  • simple:简单的select查询,查询中不包含子查询或者union(联合查询)

  • primary:查询中若包含任何复杂的子部分,最外层查询则被标记为primary

  • derived:在from列表中包含的子查询被标记为derived(衍生),mysql会递归执行这些子查询,把结果放在临时表里。

  • subquery:在select或where列表中包含了子查询。

  • table:显示这一行的数据是关于哪张表的

  • union:union中的第二个或后面的查询语句

type

表示连接类型.

  • NULL:当你查询的时候不访问任何表。

  • system:表只有一行记录(等于系统表),这是const类型的特列,平时不会出现,这个也可以忽略不计

  • const:表示通过索引一次就找到了。

    const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快。

    如将主键置于where列表中,mysql就能将该查询转换为一个常量。

  • eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描

  • ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,它返回所有匹配某个单独值的行。

    然而,它可能会找到多个符合条件的行,所以他应该是数据查找和扫描的混合体。

  • range:只检索给定范围的行,使用一个索引来选择行。

    key列显示使用了哪个索引

    一般就是在你的where语句中出现了between、<、>、in等的查询这种范围扫描

    索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束于另一点,不用扫描全部索引。

  • index:full index scan,遍历所有的索引以找到匹配的行

  • all:full table scan,将遍历全表以找到匹配的行。

注意事项:

从最好到最差依次是null>system>const>eq_ref>ref>range>index>all。一般来说,最差保证查询能达到range级别,最好能达到ref

index与all的区别

index类型只遍历索引树。这通常比all快,因为索引文件通常比数据文件小,也就是说虽然all和index都是读全表,但index是从索引中读取的,而all是从硬盘中读的。

possible_keys

显示可能应用在这张表中的索引,一个或多个。

查询涉及到的字段上如果存在索引,则该索引将会被列出来,但不一定会被查询实际使用上。

key

查询中实际使用的索引,如果为null,则没有使用索引。

key-len

表示索引中使用的字节数,该职位索引字段最大长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好。

ref

显示索引的哪一列被使用了,哪些列或常量被用于查找索引列上的值

ref 字段的值可以是以下几种类型:

  • const:表示使用常量值进行索引查找。

  • NULL:表示没有使用索引条件,通常出现在 ALL 或 index 类型的扫描中。 也可能出现在range范围中,因为没有使用等值比较,而是使用>, <, BETWEEN, IN 等操作符,或者使用or运算符时,也为null

  • 列名:表示使用某个列的值进行索引查找。

  • 函数:表示使用某个函数的结果进行索引查找。

ref:通常为 NULL,因为只有一行数据。

rows

rows列显示MySQL认为它执行查询时必须检查的行数,是一个估计值,一般越少越好。

filtered

表示返回结果的行数占需要读取行数的百分比,filtered的值越大越好

extra

一些常见的重要的额外信息:

  • using filesort:MySQL无法利用索引完成的排序操作称为“文件排序”(排序时没有使用索引,需要优化)

  • Using temporary:MySQL在对查询结果排序时使用临时表,常见于排序order by和分组查询group by。(分组时没有使用索引,需要优化)

  • Using index:表示索引被用来执行行索引键值的查找,避免访问了表的数据行,效率不错。

  • Using where:表示使用了where过滤。

  • null:表示进行了回表查询

索引使用原则

索引对查询效率的提升十分巨大,如果数据库中的数据十分庞大,查询时可能需十几秒的时间,而添加索引之后,查询效率大大提升,只需要零点几秒。

最左前缀法则

如果索引了多列(联合索引),要遵守最左前缀法则,最左前缀法则是指查询从索引的最左列开始,并且不跳过索引中的列,如果跳跃某一列,索引将部分失效(后面的字段索引失效)

代码展示:

查询索引

 show index from user;

image-20250502235937181

  • 分析查询语句(按联合索引顺序)

 explain select * from user where profession = '软件工程' && age = 28 && status = 3;

image-20250502234419012

发现使用索引查询,并且生效了三个常量索引,索引使用字节数为71

  • 分析查询语句(将顺序上排在最后的status取出)

 explain select * from user where profession = '软件工程' && age = 28;

image-20250502234422526

发现使用索引查询,并且生效了两个常量索引,索引使用字节数为67,说明status普通索引占4个字节

  • 分析查询语句(将status与age取出)

 explain select * from user where profession = '软件工程';

image-20250502234508502

发现使用索引查询,生效一个常量索引,索引使用字节数为63,说明age普通索引占4个字节。

  • 分析查询语句(将profession取出)

 explain select * from user where age = 28 && status = 3;

image-20250502234735013

发现没有使用索引查询,而是全表查询,证明了最左前缀原则:查询从最左列开始,并且不跳过索引中的列,该案例就是跳过了左边第一个索引的列(profession),后面的索引都失效了

  • 分析查询语句(将profession和age取出)

 explain select * from user where status = 3;

image-20250502234905440

与上面的案例一致,同上所述

  • 分析查询语句(将age取出)

 explain select * from user where profession = '软件工程'  && status = 3;

image-20250502235051780

发现使用索引查询,且只有一个常量索引,索引使用字节数为63,根据最左前缀原则,age是从左边数第二个索引,age被跳过了,则age后面的status索引失效。因此只有一个索引

  • 分析查询语句(不按联合索引顺序)

 explain select * from user where  age = 28 && profession = '软件工程' && status = 3;

image-20250502235623003

发现使用索引查询,并且生效了三个索引,索引使用字节数为71

说明:不在乎顺序,只要这个字段存在,就不算跳过

原因:因为profession索引树的叶子节点的数据域是拥有相同pro值的age根节点,直接找age肯定找不到

范围查询

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

代码展示:

  • 分析查询(条件中使用了(age > 28))

 explain select * from user where profession = '软件工程' && age > 28  && status = 3;

image-20250503001713881

发现使用索引查询,索引使用字节为67,说明生效了两个索引

  • 分析查询(条件中使用了(age > =28))

explain select * from user where profession = '软件工程' && age >= 28  && status = 3;

image-20250503002416601

发现使用索引查询,索引使用字节为71,说明生效了三个索引

索引列运算

不要在索引列上进行运算操作,如果有,则索引失效

代码展示:

  • 分析查询

 explain select * from user where phone = '13800138001';

image-20250503151424117

发现使用索引查询

  • 分析查询(有函数运算操作)

explain select * from user where substring(phone,10,2) = '01';

image-20250503151614640

发现没有使用查询,索引失效

字符串类型不加单引号,隐式类型转换,索引会失效。

代码展示:

  • 分析查询

explain select * from user where phone = '13800138001';

image-20250503151424117

发现使用索引查询

  • 分析查询(字符串类型没有加引号)

 explain select * from user where phone = 13800138001;

image-20250503152057357

发现没有使用查询,索引失效

模糊查询

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

代码展示:

  • 分析查询(使用like模糊查询,尾部模糊查询,并且是联合查询中的左边第一条索引,符合最左前缀法则)

 explain select * from user where profession like '软件%';

image-20250503152834901

发现使用索引查询

  • 分析查询(like头部模糊查询)

 explain select * from user where profession like '%工程';

image-20250503153231885

发现没有使用索引查询,索引失效

OR连接的条件

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

代码展示:

  • 分析查询(使用or运算符,id为主键索引,age为联合索引中第二条索引,因此失效,相当于age没有索引)

 explain select * from user where id = 31 or age = 21;

image-20250503153926571

发现没有使用索引查询,主键索引失效

  • 分析查询(使用or运算符,id为主键索引,name为唯一索引)

 explain select * from user where id = 31 or name = '王伟';

image-20250503154435297

发现使用索引查询,因为or运算符两边都为索引。

数据分布影响

如果使用MySQL评估(应该是底层算法)使用索引比全表查询慢的话,则不使用索引

代码展示:

  • 分析查询(表中只有小部分数据符合查询条件)

 explain select * from user where phone >= '13800138017';

image-20250503160012994

发现使用索引查询

  • 分析查询(表中绝大部分数据都符合查询条件)

 explain select * from user where phone >= '13800138001';

image-20250503155330813

发现没有使用索引查询,因为在绝大多数数据符合查询条件时,全表查询的性能是最高的,且也不会造成浪费,因为绝大部分数据都是要查询的。

在正常表中,一般字段中的值大多数都不会为空,因此在数据库评估后,is null 可以使用索引,但是is not null无法使用索引,因为绝大多数的数据都是不为空的,所以使用is not null时数据库会使用全表查询

代码展示:

  • 分析查询(表中只有小部分数据符合查询条件)

 explain select * from user where profession is null;

image-20250503160654944

发现使用索引查询

  • 分析查询(表中绝大部分数据都符合查询条件)

 explain select * from user where profession is not null;

image-20250503160937094

发现没有使用索引查询,因为在绝大多数数据符合查询条件时,全表查询的性能是最高的,且也不会造成浪费,因为绝大部分数据都是要查询的。

注意事项:但是如果个别表中个别字段里的值绝大多数为空,那么可能is null使用全局查询,is not null使用索引

SQL提示

前置引入:

 show index from user;

profession字段有两个索引,一个是联合索引,一个是普通索引。

  • 分析查询,看数据库会选择哪个索引

 explain select * from user where profession = '软件工程';

image-20250503162653342

possible_keys中有两个可能的索引,数据库需要从这两者选择一个

发现数据库选择的是联合索引查询,是MySQL优化器自动选择的结果

那如果我们需要告诉MySQL按照我们指定的索引来进行操作,如何做到,就是SQL提示

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

语法:

use index (推荐使用):

 
explain select * from user use index(idx_user_profession) where profession = '软件工程';

image-20250503163510101

possible_keys中只有单列索引这一个可能的索引

注意事项:use index 是我们给数据库的建议,数据库还要根据评估来决定要不要使用。

ignore index(忽略使用):

 
explain select * from user ignore index(idx_user_profession) where profession = '软件工程';

image-20250503163732367

possible_keys中只有联合索引这一个可能的索引

force index(必须使用):

 
explain select * from user force index(idx_user_profession) where profession = '软件工程';

image-20250503164016782

必须使用,强制手段。

覆盖索引

select * 的劣势:

  • 代码不直观,可读性差,效率低下

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

代码展示:

  • 分析查询

 explain select * from user where profession = '软件工程' && age = 28 && status = 3;explain select id,profession from user where profession = '软件工程' && age = 28 && status = 3;explain select id,profession,age,status from user where profession = '软件工程' && age = 28 && status = 3;

image-20250503175206818

三条查询语句的分析效果一模一样,Extra都为Using index

Using index:表示索引被用来执行行索引键值的查找,避免访问了表的数据行,效率不错。

注意事项:

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

  • using indexcondition:查找使用了索引,但是需要回表查询数据

覆盖索引如图所示

image-20250503184933936

问题:为什么要避免使用select *

解答:因为select * 很容易就会出现回表查询,性能就会降低,除非表中的联合索引包含了所有字段

思考:

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

 select id,username,password from user where username = '王刚'; 

最优解:id为主键索引,即聚集索引,username和password建立联合索引,联合索引就是二级索引,二级索引叶子节点的数据就是聚集索引的索引值,即id,这样就不需要回表查询。

前缀索引

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

语法:

 create index idx_xxxx on table_name(column(n));

前缀长度:

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

简而言之:就是不同的数据量比上总数,不同的越多,前缀索引发挥的价值越大。

代码展示:

 select count(distinct email)/count(*)  '选择性' from user;

image-20250503200656373

可以先进行测试选择性,适当的评估自己需要的前缀长度

 select count(distinct substring(email,1,n))/count(*)  '选择性' from user;

建立前缀索引

 create index `idx_user_email_4` on user(email(4));

展示索引

image-20250503201736563

前缀索引查询流程

在user表中,主键索引为聚集索引,那么email的前缀索引就是二级索引。

在查询语句select * from user where email = 'wangwei@example.com'时,数据库会先查询二级索引,但不是整个字段匹配,而是只截取他的前缀,去和索引值匹配。

然后拿到二级索引对应的聚集索引的索引值(id),然后再拿到聚集索引中进行匹配拿到对应的数据。(回表查询)

然后将对应数据中的email拿出对比查询语句中的email是否一致,如果一致,就将这一行的数据返回。

然后再去查询二级索引中该索引值的下一个节点,去对比下一个元素是否为查询的email值,如果不是,直接返回数据。

如果是,接下来还要继续往下查询,再将下一行的数据拿到,最终组装数据并返回。

单列索引和联合索引的选择

单列索引:即一个索引只包含单个列

联合索引:即一个索引中包含了多个列

在业务场景中,如果存在多个查询条件,考虑针对查询字段建立索引时,建议建立联合索引,而非单列索引。

单列索引情况

 
explain select id,phone,name from user where phone = '13800138015' and name ='胡军';

image-20250503204029155

Extra中为null,说明进行了回表查询,效率较低

多条件联合查询时,MySQL优化器会评估哪个字段的索引效率更高,会选择该索引完成本次查询

联合索引情况

联合索引结构:

每一个节点中的键值是联合索引中的若干个字段的组合情况,依照最左前缀原则,会按照联合索引中的顺序来进行排序,而在叶子节点中的是联合索引中的字段值

image-20250503215851724

联合索引查询流程:

和二级索引的查询流程相似,只是在查询时按照从左到右的字段依次对比

注意事项:建立联合索引的时候需要注意字段的顺序

索引设计原则
  1. 针对数据量较大,且查询比较频繁的表建立索引。

  2. 针对常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引,(因为在where之后是查询条件,order by之后是排序条件,group by之后是分组条件,都会用到索引)

  3. 尽量选择区分度较高的列作为索引,尽量建立唯一索引,区分度越高,使用的索引效率就高。区分度越低,使用的索引效率就越低,例如(性别,逻辑类型)

  4. 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引

  5. 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率

  6. 控制索引的数量,索引不是多多益善,索引越多,维护索引结构的代价就越大,会影响增删改的效率

  7. 如果索引列不能存储NULL值,请在创建表的时使用NOT NULL约束,当SQL优化器知道每列是否包含NULL值,他可以更好的确定哪个索引最有效的用于查询。

以上就是索引的性能分析、使用原则以及设计原则,希望对大家有所帮助!

相关文章:

  • TestBench激励与待测
  • 驱动开发硬核特训 · Day 27(上篇):Linux 内核子系统的特性全解析
  • 引入spdlog后程序链接很慢
  • 系统思考:核心价值与竞争力
  • Python绘制误差棒:深入解析数据的不确定性
  • 比HashTable更先进的ConcurrentHashMap及其多线程协助扩容机制
  • 汇编常用语法
  • Spring的数据库编程
  • Linux:web服务
  • Warp调度器:藏在显卡里的时间管理大师
  • 【vue3】黑马程序员前端Vue3小兔鲜电商项目【八】
  • Flowable7.x学习笔记(十九)归还我的待办
  • ARM寻址方式
  • 【时时三省】(C语言基础)怎样定义和引用一维数组
  • 【Redis】Redis常用命令
  • 排序功法入门指南【江湖算法笔记】
  • 13.Excel:分列
  • 【论文阅读】LLMOPT:一种提升优化泛化能力的统一学习框架
  • Cona编译问题
  • 工程师 - What is EMF?
  • 巴基斯坦宣布禁止印度船只入港
  • 洪纬读《制造三文鱼》丨毒素缠身的水生鸡
  • 单阶段遭遇零封偶像奥沙利文,赵心童要让丁俊晖预言成真
  • 马上评丨上热搜的协和“4+4”模式,如何面对舆论审视
  • 济南高新区一季度GDP增长8.5%,第二产业增加值同比增长14.4%
  • 许峰已任江苏省南京市副市长