MySQL--索引精通详解
性能分析
MySQL常见瓶颈
-
SQL中对大量数据进行比较、关联、排序、分组时CPU的瓶颈
-
实例内存满足不了缓存数据或排序等需要,导致产生大量的物理IO。查询数据是扫描过多数据行,导致查询效率低。
SQL执行频率
(可以通过查询增删改查语句在数据库中的访问频次来确定优化策略)
MySQL客户端连接成功后,通过
show [session | global] status like 'Com_______' -- session查看当前绘画的状态信息 global 查看全局的状态信息-- 后面跟7个占位符 Com_delete,Com_insert
可以提供服务器状态信息。通过以下指令,可以查看当前数据库的增删改查语句的访问频次
慢查询日志
慢查询日志记录了所有执行时间超过指定参数(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 语句
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;
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'))
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;
-
分析查询语句(按联合索引顺序)
explain select * from user where profession = '软件工程' && age = 28 && status = 3;
发现使用索引查询,并且生效了三个常量索引,索引使用字节数为71
-
分析查询语句(将顺序上排在最后的status取出)
explain select * from user where profession = '软件工程' && age = 28;
发现使用索引查询,并且生效了两个常量索引,索引使用字节数为67,说明status普通索引占4个字节
-
分析查询语句(将status与age取出)
explain select * from user where profession = '软件工程';
发现使用索引查询,生效一个常量索引,索引使用字节数为63,说明age普通索引占4个字节。
-
分析查询语句(将profession取出)
explain select * from user where age = 28 && status = 3;
发现没有使用索引查询,而是全表查询,证明了最左前缀原则:查询从最左列开始,并且不跳过索引中的列,该案例就是跳过了左边第一个索引的列(profession),后面的索引都失效了
-
分析查询语句(将profession和age取出)
explain select * from user where status = 3;
与上面的案例一致,同上所述
-
分析查询语句(将age取出)
explain select * from user where profession = '软件工程' && status = 3;
发现使用索引查询,且只有一个常量索引,索引使用字节数为63,根据最左前缀原则,age是从左边数第二个索引,age被跳过了,则age后面的status索引失效。因此只有一个索引
-
分析查询语句(不按联合索引顺序)
explain select * from user where age = 28 && profession = '软件工程' && status = 3;
发现使用索引查询,并且生效了三个索引,索引使用字节数为71
说明:不在乎顺序,只要这个字段存在,就不算跳过
原因:因为profession索引树的叶子节点的数据域是拥有相同pro值的age根节点,直接找age肯定找不到
范围查询
联合索引中,出现范围查询(>,<),范围查询右侧的列索引失效
代码展示:
-
分析查询(条件中使用了(age > 28))
explain select * from user where profession = '软件工程' && age > 28 && status = 3;
发现使用索引查询,索引使用字节为67,说明生效了两个索引
-
分析查询(条件中使用了(age > =28))
explain select * from user where profession = '软件工程' && age >= 28 && status = 3;
发现使用索引查询,索引使用字节为71,说明生效了三个索引
索引列运算
不要在索引列上进行运算操作,如果有,则索引失效
代码展示:
-
分析查询
explain select * from user where phone = '13800138001';
发现使用索引查询
-
分析查询(有函数运算操作)
explain select * from user where substring(phone,10,2) = '01';
发现没有使用查询,索引失效
字符串类型不加单引号,隐式类型转换,索引会失效。
代码展示:
-
分析查询
explain select * from user where phone = '13800138001';
发现使用索引查询
-
分析查询(字符串类型没有加引号)
explain select * from user where phone = 13800138001;
发现没有使用查询,索引失效
模糊查询
如果仅仅是尾部模糊匹配,索引不会失效,如果是头部模糊匹配,索引失效。
代码展示:
-
分析查询(使用like模糊查询,尾部模糊查询,并且是联合查询中的左边第一条索引,符合最左前缀法则)
explain select * from user where profession like '软件%';
发现使用索引查询
-
分析查询(like头部模糊查询)
explain select * from user where profession like '%工程';
发现没有使用索引查询,索引失效
OR连接的条件
用or分割开的条件,如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都会失效
代码展示:
-
分析查询(使用or运算符,id为主键索引,age为联合索引中第二条索引,因此失效,相当于age没有索引)
explain select * from user where id = 31 or age = 21;
发现没有使用索引查询,主键索引失效
-
分析查询(使用or运算符,id为主键索引,name为唯一索引)
explain select * from user where id = 31 or name = '王伟';
发现使用索引查询,因为or运算符两边都为索引。
数据分布影响
如果使用MySQL评估(应该是底层算法)使用索引比全表查询慢的话,则不使用索引
代码展示:
-
分析查询(表中只有小部分数据符合查询条件)
explain select * from user where phone >= '13800138017';
发现使用索引查询
-
分析查询(表中绝大部分数据都符合查询条件)
explain select * from user where phone >= '13800138001';
发现没有使用索引查询,因为在绝大多数数据符合查询条件时,全表查询的性能是最高的,且也不会造成浪费,因为绝大部分数据都是要查询的。
在正常表中,一般字段中的值大多数都不会为空,因此在数据库评估后,is null 可以使用索引,但是is not null无法使用索引,因为绝大多数的数据都是不为空的,所以使用is not null时数据库会使用全表查询
代码展示:
-
分析查询(表中只有小部分数据符合查询条件)
explain select * from user where profession is null;
发现使用索引查询
-
分析查询(表中绝大部分数据都符合查询条件)
explain select * from user where profession is not null;
发现没有使用索引查询,因为在绝大多数数据符合查询条件时,全表查询的性能是最高的,且也不会造成浪费,因为绝大部分数据都是要查询的。
注意事项:但是如果个别表中个别字段里的值绝大多数为空,那么可能is null使用全局查询,is not null使用索引
SQL提示
前置引入:
show index from user;
profession字段有两个索引,一个是联合索引,一个是普通索引。
-
分析查询,看数据库会选择哪个索引
explain select * from user where profession = '软件工程';
possible_keys中有两个可能的索引,数据库需要从这两者选择一个
发现数据库选择的是联合索引查询,是MySQL优化器自动选择的结果
那如果我们需要告诉MySQL按照我们指定的索引来进行操作,如何做到,就是SQL提示
SQL提示,就是优化数据库的一个重要手段,就是在SQL语句中加入一些人为的提示来达到优化操作的目的
语法:
use index (推荐使用):
explain select * from user use index(idx_user_profession) where profession = '软件工程';
possible_keys中只有单列索引这一个可能的索引
注意事项:use index 是我们给数据库的建议,数据库还要根据评估来决定要不要使用。
ignore index(忽略使用):
explain select * from user ignore index(idx_user_profession) where profession = '软件工程';
possible_keys中只有联合索引这一个可能的索引
force index(必须使用):
explain select * from user force index(idx_user_profession) where profession = '软件工程';
必须使用,强制手段。
覆盖索引
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;
三条查询语句的分析效果一模一样,Extra都为Using index
Using index:表示索引被用来执行行索引键值的查找,避免访问了表的数据行,效率不错。
注意事项:
-
using where ;using index
:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据 -
using indexcondition
:查找使用了索引,但是需要回表查询数据
覆盖索引如图所示
问题:为什么要避免使用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;
可以先进行测试选择性,适当的评估自己需要的前缀长度
select count(distinct substring(email,1,n))/count(*) '选择性' from user;
建立前缀索引
create index `idx_user_email_4` on user(email(4));
展示索引
前缀索引查询流程
在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 ='胡军';
Extra中为null,说明进行了回表查询,效率较低
多条件联合查询时,MySQL优化器会评估哪个字段的索引效率更高,会选择该索引完成本次查询
联合索引情况
联合索引结构:
每一个节点中的键值是联合索引中的若干个字段的组合情况,依照最左前缀原则,会按照联合索引中的顺序来进行排序,而在叶子节点中的是联合索引中的字段值
联合索引查询流程:
和二级索引的查询流程相似,只是在查询时按照从左到右的字段依次对比
注意事项:建立联合索引的时候需要注意字段的顺序
索引设计原则
-
针对数据量较大,且查询比较频繁的表建立索引。
-
针对常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引,(因为在where之后是查询条件,order by之后是排序条件,group by之后是分组条件,都会用到索引)
-
尽量选择区分度较高的列作为索引,尽量建立唯一索引,区分度越高,使用的索引效率就高。区分度越低,使用的索引效率就越低,例如(性别,逻辑类型)
-
如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引
-
尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率
-
要控制索引的数量,索引不是多多益善,索引越多,维护索引结构的代价就越大,会影响增删改的效率
-
如果索引列不能存储NULL值,请在创建表的时使用NOT NULL约束,当SQL优化器知道每列是否包含NULL值,他可以更好的确定哪个索引最有效的用于查询。
以上就是索引的性能分析、使用原则以及设计原则,希望对大家有所帮助!