索引是一种数据结构(有序),是帮助MySQL高效获取数据的。在我们的数据表结构当中,除了要去保存我们的原始数据之外,数据库还需要去维护索引这种数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。
备注:上述二叉树结构的只是一个示意图,并不是真正的索引结构。
·优势:
①提高数据检索的效率,降低数据库的IO成本
②通过索引对数据进行排序,降低数据排序的成本,降低CPU的消耗
劣势:基本可以忽略,对于1磁盘是很便宜的,对于②对于正常的业务系统来说增删改的比例低
①索引列也是要占空间的
②索引大大提高了查询效率,同时也降低更新表的速度,如对表进行INSERT、UPDATE、DELETE时,效率降低
·索引结构
MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的结构,主要包含以下几种:
我们平常所说的索引,如果没有特别指明,都是指B+树结构组织的索引。
·结构-Btree
红黑树能很好的解决顺序插入时,会形成一个链表导致查询性能大大降低的问题。
因为每个节点下面只能接两个子节点,所以一旦数据多,那层级就会越来越深。
红黑树也存在着大数据量情况下,层级较深,检索速度慢的问题,那该怎么办?
B-Tree树:
数据都存放在对应的key下面
B+Tree:
①所有的元素都会出现在叶子节点。上面那部分只是起到索引的作用,而下面部分即叶子节点是用来存放数据的。
②叶子节点形成了单向链表。每一个节点都会通过一个指针指向下一个节点元素。
黄色框的是页,一个黄色框是一个页,讲innoDB引擎时我们讲过,一页存储空间是16K,是固定的
·结构-hash
hash索引特点:
①hash索引只能用于对等比较(=,in),不支持范围查询(between,<,>,...)
②无法利用索引完成排序操作
③查询效率高,通常只需要一次检索就可以了,效率通常要高于B+Tree索引
在MySQL中,支持hash索引的是Memory引擎,而innoDB中具有自使用hash功能,hash索引是存储引擎根据B+Tree索引在指定条件下自动构建的。
面试问题:
为什么InnoDB存储引擎使用B+tree索引结构?
·相对于二叉树,层级更少,搜索效率更高;
·相对于B-tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值跟着减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低。(因为一页只有16K,你要是键值和数据一起装那你就放不了更多的key,那你层级就会增加,效率就相对没那么高啊,要是你只装key,那一页可以装更多key,那层级就没有那么多查找效率就高呀)
·相对于Hash索引,B+tree支持范围匹配及排序操作,而Hash不支持;
·索引分类
聚集索引选取规则:
> 如果存在主键,主键索引就是聚集索引
>如果不存在主键,将使用第一个唯一索引作为聚集索引
>如果表没有主键,或没有合适的唯一索引,则nnoDB会自动生成一个rowid作为隐藏的聚集索引
理解聚集索引和二级索引的结构,理解了结构,很多SQL的优化的策略我们才能更好的去理解低层的原理。
聚集索引叶子节点下面保存的是这一行的数据,如5那个,下面的row保存的就是id为5那一行的id=5、name=Kit、gender=男的数据。二级索引叶子节点下面保存的是对应的主键值这里即id。
例如:select * from user where name ='Arm';
首先根据二级索引name去查询Arm的主键值为10,可是它要 查询的是这一行的数据呀,所以会回到聚集索引去查10的那行的所有数据。这也叫回表查询。
思考:
1.以下SQL语句,哪个执行效率高?为什么?
select * from user where id=10;
select * from user where name='Arm';
备注:id为主键,name字段创建的有索引;
理解了上面的结构,显而易见第一条那个执行效率高
2.InnoDB主键索引的B+tree高度为多高呢?
假设:
一行数据大小为1k,一页中可以存储16行这样的数据,innoDB的指针占用6个字节的空间,主键即使为bigint,占用字节数为8.
高度为2:
n*8+(n+1)*6=16*1024,算出n约为1170
1171*16=18736
高度为3:
1171*1171*16=21939856
最终可以发现,innoDB引擎的表即使你存储2000多万的记录,树的结构也才3层,所以它的检索效率是很高的(即使2000多万我只检索3层就可以查到数据了,所以说效率是很高的)
·索引语法
1.如何创建索引:
create [unique|fulltext] index index_name on table_name (index_col_name,...);
2.如何查看索引:
show index from table_name;
3.如何删除索引:
drop index index_name on table_name;
例如:
1.name字段为姓名字段,该字段的值可能会重复,为该字段创建索引。
create index idx_user_email 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性能分析
我们为什么要学习SQL性能分析?因为我们要做SQL优化,那你得先定位出我们要对哪一类SQL进行优化。
·SQL执行频率
通过SQL执行频率来看可以知道当前数据增删改查比例,我们知道执行频率后就知道这个数据库是以增删改查哪个为主,从而确定对应的优化方案。
如果我们发现一个数据库它的查询占了绝大多数,那么此时我们就需要针对这类数据库的SQL来进行优化。如果是增删改为主,则优化的比重就可以放轻。
MySQL客户端连接成功后,通过show [sessionlglobal] status命令可以提供服务器状态信息。通过如下命令,可以查看当前数据库的insert、update、delete、select的访问频次:
show global status like 'com___(解释一下,多少个_即多少个字符)';
·慢查询日志
比如我们通过上面的执行频率知道了select即查询的频率较高,但是我到底应该针对哪些select语句进行优化呢?这时候就需要借助慢查询日志来定位哪些select语句执行效率低,从而对这类的select语句进行优化。
慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志。
MySQL的慢查询日志默认没有开启,需要在MySQL的配置文件(/ect/my.cnf)中配置如下信息:
#开启MySQL慢日志查询开关:
slow_query_log=1
#设置慢日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志:
long_query_time=2
配置完成后,通过以下指令重新启动MySQL服务器进行测试,查看慢日志文件中记录的信息/var/lib/mysql/localhost-slow.log。
比如我们慢日志时间设置为2秒,那假设这时候我们有一些SQL语句它的执行耗时只有1.8秒左右,那这类是不会被记录到慢查询日志中的,那么假如说,这些业务很简单它的执行达到了1.9秒,那这类数据呢也相对来说性能是比较低,那么我们也需要对这类SQL进行优化,可是它因为没有超过我们设定的2秒,没有被记录在慢日志中,那我们怎么定位到这类SQL呢?我们可以借助下面的profile详情。
·profile详情
show profiles能够在做SQL优化时帮助我们了解时间都耗费到哪里去了,可以了解到每一条SQL语句耗时多少且耗时到底耗费在哪里了。要想使用show profiles这个指令,首先我们要看当前数据库是否支持profile操作。那么我们可以看一个参数叫have_profiling参数,就能够看到当前MySQL是否支持profile操作:
select @@have_profiling;
默认profiling是关闭的,可以通过set语句在session/global级别开启profiling:
set profiling=1;
然后我们就可以执行一系列的业务SQL的操作,然后通过如下指令查看指令的执行耗时:
#查看每一条SQL的耗时基本情况:
show profiles;
#查看指定query_id的SQL语句各个阶段的耗时情况:
show profile for query query_id;
#查看指定query_id的SQL语句CPU的使用情况:
show profile cpu for query query_id;
上面的只是根据SQL执行的时间来判断,执行时间长则执行性能低,执行时间短则执行性能高,根据时间长短来判断性能其实只是粗略的判断,并不能真正的评判一条SQL语句的性能。我们要想看一条SQL语句的性能还需要借助下面这些。
·explain执行计划
explain或者desc命令获取MySQL如何执行select语句的信息,包括在select语句执行过程中如何连接和连接的顺序。即可以看到执行过程当中是否用到了索引、表的连接情况和表的连接顺序。
语法:
#直接在select语句之前加上关键字explain/desc
explain select 字段列表 from 表名 where 条件;
explain执行计划各字段含义:需要重点关注的是type,根据type我们基本就能知道一条SQL语句它的性能大概是什么样子的,然后还需要关注的是possible_key、key、key_len
>id
select查询的序列号,表示查询中执行select子句或者是操作表的顺序(id相同,执行顺序从上到下;id不同,值越大,越先执行)
>select_type
表示select的类型,常见的取值有simple(简单表,即不使用表连接或子查询)、primary(主查询,即外层的查询)、union(union中的第二个或者后面的查询语句)、subquery(select/where之后包含了子查询)等,知道一下就可以了。
>type
表示连接类型,性能由好到差的连接类型为NULL、system、const、eq_ref、ref、range、index、all。这个相对比较重要。优化尽量往前面靠。一般使用唯一索引进行访问时会出现const,如果使用非唯一性索引会出现ref。ALL一般是做了全表扫描。
> possible_key
显示可能应用在这张表上的索引,一个或多个。
>key
实际使用的索引,如果为NULL,则没有使用索引.
>key_len
表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好。
>rows
MySQL认为必须要执行查询的行数,在innodb引擎的表中,是一个估计值,可能并不总是准确的
>filtered
表示返回结果的行数占需读取行数的百分比,filtered的值越大越好。
后面讲索引使用的时候还会对explain做加强巩固。
·索引使用
我们索引是创建了,那么我们怎么去正确的使用索引呢,以及使用索引能不能对我们的效率进行提升呢?
先验证一下索引效率:
可知索引能提高我们的效率,而且不是一点。
接下来我们学习索引的使用原则:
·最左前缀法则
如果索引了多列(联合索引),要遵守最左前缀法则。最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。如果跳跃某一列,索引将部分失效(后面的字段索引失效)。
最左前缀法则指的是查询必须包含索引最左边的列,如果最左边的列不存在,那么索引将全部失效;如果在查询的时候跳跃了某一列,索引将会部分失效。索引失效了,那就没用上索引,效率就不如用索引时高了呀,所以要避免索引失效。
例如:
之前讲的那个把profession和age和status这3个结合在一起的联合索引:
idx_user_pro_age_sta ,那个profession =' 软件工程' 就是最左列。
比如explain select * from tb_user where profession='软件工程' and age=31 and status='0'; 是对的。
如果写为explain select * from tb_user where age=31 and status='0';则索引全部失效,即type就会出现ALL,possible_key、key、key_len就全部变为NULL
如果写成explain select * from tb_user where profession='软件工程'and status='0';即跳过了某一列即那个age,所以rows(查询的行数)只会显示profession的,而不会显示status的,因为此时索引已经部分失效
如果写成explain select * from tb_user where profession='软件工程' and age=31 and status='0';这个是可以的
如果写成explain select * from tb_user where age=31 and status='0' profession='软件工程' ;思考一下这个索引会不会失效,如果不失效那索引长度是包含那个的?索引是不会失效的,因为最左列存在就行跟你放的位置是没有关系的,索引长度是包含profession和age和status三个的长度。
·范围查询
联合索引中,出现范围查询(<、>),那么范围查询右侧的列索引失效
如果写成explain select * from tb_user where profession='软件工程' and age>31 and status='0';这时候索引长度即key_len显示的只是profession 和age的索引长度而没有status的索引长度,因为此时age出现了范围查询,所以age右侧的列即status索引失效。
那有没有什么办法可以规避呢?在业务允许的情况下我们尽量使用>=或<=这样的范围查询,这样不会出现索引失效。
·索引列运算
不要在索引列上进行运算操作,否则索引将失效。
·字符串不加引号
字符串类型字段使用时,不加引号,索引将失效。
如果写成explain select * from tb_user where profession='软件工程' and age>31 and status=0;(0没加引号)那么这时候索引长度就只包含了profession和age的没有status的,说明status的索引失效了。
·模糊查询
如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效。
在模糊查询中我们经常会使用Like进行模糊匹配,前面加上%或者后面加上%,那么代表前面模糊还是后面模糊。
如果是explain select * from tb_user where profession like '软件%';是走索引的。
如果是explain select * from tb_user where profession like '%工程';此时是不走索引的。
·or连接的条件
用or分割开的条件,如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到。
比如age字段没有索引,id有索引,然后你输出语句为:explain select * from tb_user where id=10 or age=23; 本应该用到索引id,结果实际用到索引为NULL,因为age没有索引。所以需要针对age也建立索引。
·数据分布影响
如果MySQL评估使用索引比全表更慢,则不使用索引。
·SQL提示
就是你给数据库一个提示告诉它你用哪个索引。
SQL提示,是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。
from +表名+SQL提示(哪个索引)
use index(就是建议数据库用哪个索引):
如: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(idx_user_pro) where profession='软件工程';
·覆盖索引
尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到),减少select *。
知识小贴士:
using index condition:查找使用了索引,但是需要回表查询数据
using where;using index:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据。
如图 上面哪个extra是using where,using index ,下面那个是using index condition,不需要回表那个效率相对高。
上面那个profession与age与status联合索引,而二级索引叶子节点下面放的是id呀,所以就不用再回表到聚集索引中找profession与age与status对应的id,直接在那个二级索引就可以直接得到id,即不用回表就可以得到要的值:id与profession与age与status (这种就叫覆盖索引)
而下面那个,id与profession与age与status同上面可以直接在二级索引获得即不用回表,但是那个name,因为name字段在这个二级索引中是没有的,所以需要查询name的id再回表即回到聚集索引中查询此name的id与profession与age与status等,就需要回表查询了,性能就低了。
因为使用*,很容易就造成回表查询,所以尽量少用*。
思考:面试题
一张表,有四个字段(id,usename,password,status),由于数据量大,需要对以下SQL语句进行优化,该如何进行才是最优方案:
select id,usename,password from tb_user where usename='itcast';
最优方案应该针对usename和password建立联合索引,这样就不用回表查询,直接覆盖索引,性能是最好的。
·前缀索引
注意什么时候用前缀索引(即下面那段)以及前缀索引的语法。
前缀索引是用来解决一些长字符串或者是大文本字段在整个字段进行索引的时候体积过于庞大而造成的浪费大量磁盘io的情况,对这种情况我们可以使用前缀索引来降低索引的体积,提高索引的效率。
当字段类型为字符串(varchar,text等)时,有时候需要索引很长的字符串,这会让索引变的很大,查询时,浪费大量的磁盘io,影响查询效率。此时可以只将字符串的一部分前缀,这样可以大大节约索引空间,从而提高索引效率。
创建前缀索引的语法:
create index idx_xxxx_n on table_name(xxxx(n)); n指的是截取前几个字符构建前缀索引
例如:create index idx_email_5 on table_user(email(5));
即创建email的前缀索引,截取前5个字符
前缀长度:可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
比如查找email不重复的值的总数量,distinct是去重,再除以全部的email总值即/count(*)。:
select count(distinct email)/count(*) from tb_user;
substring即截取email第1-5个字符做前缀索引的长度
select count(distinct substring(email(1,5))/count(*) from tb_user;
·单列索引与联合索引
单列索引:即一个索引只包含单个列。
联合索引:即一个索引包含了多个列。
在业务场景中,如果存在多个查询条件,考虑针对查询字段建立索引时,建议建立联合索引,而非单列索引。
多条件联合查询时,MySQL优化器会评估哪个字段的索引效率更高,会选择该索引完成本次查询。
建立联合索引时,注意考虑最左前缀法则,即如create unique index idx_phone_name on tb_user(phone,name), 这时候()里phone是在前,据最左前缀法则,后面查询时phone这个列一定要存在,select * from tb_user where phone='18467676767';这就对,但是如果这时候()里name是在前,即create unique index idx_phone_name on tb_user(name,phone),那select * from tb_user where phone='18467676767';就不对啦。
·索引设计原则
加入我要对某张表某个字段建立索引提高SQL语句的查询效率,那我应该针对于什么样的表什么样的字段建立什么样的索引呢?
针对哪些表建立索引?
数据量大并且查询次数多的表建立索引
针对这些表的哪些字段建立索引?
针对作为查询条件where,排序order by,分组group by操作的字段建立索引
针对它们建立什么样的索引?
①如果这一列是唯一的尽量建唯一索引,区分度高
②如果能建立联合索引就建立建立联合索引
③如果涉及到字符串较长或者说大文本字段,尽量使用前缀索引