MYSQL 索引 结构 以及常见优化

Mysql索引

  1. 索引概述

索引是帮助Mysql高效获取数据的排好序的数据结构

比如我们做查询的时候需要查询col2=89的数据

首先我们的数据在磁盘上的表不一定是挨着的,第一条数据插入后,可能其它程序在磁盘上写入了数据,然后再插入第二条,一条一条去进行IO从磁盘中拿数据,效率就很低下,所以我们就需要减少磁盘IO的次数;

所以就有了索引,索引的key是我们的查找条件col2的值,value是它的文件地址

  1. 索引数据结构

二叉树

二叉树,右边比左边大,但是这种单边增长的数据,变成二叉树就容易星辰链表结构,它查找的次数还是很多;

红黑树

又叫二叉平衡树,当右边高于左边很多时,它会自动做一次平衡,但是当数据量比较大的时候,会造成树结构比较高,可能查找效率越来越低,比如数据在最下面的叶子节点,那么查找的次数就变得很多了

hash表

当进行=值查找时,它可能查找更快,但是因为其不支持范围查找(范围查找最终还是会用到全表扫描),所以实际用的不多

  1. BTree

对于红黑树数据量大树太高的问题,原来节点在磁盘中存放的时候,把之前一个节点的磁盘空间给它扩大一点,原来是只能存放一个根节点,现在我们能存放很多根节点,而每个根节点向下还能有许多节点,这样高度就变小了

B+tree(变种的b树)

把节点数据全部放在叶子节点,叶子节点上的数据也是从左到右依次递增,

非叶子节点不存储data,值存储冗余索引,

叶子节点之间用指针连接(也就是相邻页存放的彼此的地址),提高区间访问的性能

Mysql一次性分配的根节点大小是16Kb,大概放非叶子节点1100个,大概放叶子节点16个,所以高度为3的b+tree可以存放1100*1100*16=两千万左右的数据,也就是说,两千万的数据,只需要进行3次磁盘io就能找到数据,和之前循环查找,上万次的io效率当然是天差地别;

B-tree因为非叶子节点存放了data,所以它一层的tree结构只能存放16个非叶子节点,而同样存放两千万的数据时,树的高度16*16*16....乘的次数就等于树的高度大约为6,当数据量越来越大时,差距就会越来越明显了

而且B+tree叶子节点用指针连接,当进行范围查找时,就能很方便的找到下一个节点的数据

存储引擎是对于数据库表来说的,同一个库中的不同表可以选择不同的存储引擎

不同存储引擎在磁盘中的存储

Myisam(B+tree)

查找数据过程:先从myi索引文件中查到索引所在地址,再根据地址去myd data文件中查找数据

Innodb(B+tree )

所以innodb引擎,与myisam引擎虽然都是用的b+tree,但是myisam引擎在叶子节点存放的是数据的地址,而innodb在叶子节点存放的是索引+数据(整行数据对应的所有列的数据)

为什么innodb的表必须建主键?并且推荐使用整型的自增主键?

因为innodb在构建b+tree的时候,会根据主键去构建,如果没有主键,mysql会自己去维护一个隐藏列,来构建b+tree,这就浪费了mysql资源,所以能自己建一个就自己建一个主键,当主键是整型的时候,它去比较的效率会更高,而且所占用的磁盘空间也更小;

推荐使用自增,是因为在进行b+tree构建时,如果不是自增,则可能对b+tree的重构会更多,因为b+tree的叶子节点是自增的,所以位置是定好的,比如前面都排序好了,这时再向中间插入数据肯定是相比于向最后面添加的效率更低;

索引类型:

  1. 千万级数据表如何用B+树索引快速查找
  2. 聚集索引(聚簇索引), 非主键索引(非聚集索引)
聚集索引:

其实就是叶子节点包含了完整的数据记录(一行数据的所有列),也就是innodb引擎在用的

非聚集索引:

其实就是索引和数据分开存储的,也就是myisam引擎在用的(叶子节点只存放了地址,而真正的数据在另一个文件中)

非主键索引:

Myisam的非主键索引和主键索引没区别,但是innodb的非主键索引的叶子节点没有直接存放数据,是存放的主键的值,所以查找出来后,需要根据主键值去主键索引中再去进行一次查找,也就是回表操作;

为什么非主键索引叶子节点存储的是主键值而不是数据?

为了节省空间,如果二次索引有很多,那么磁盘的空间是原来的很多背,还有一个是这样就多出来一个问题,就是数据的一致性要去解决;

联合索引:

它进行排序的时候,就是按照索引字段,从左到右排序的,所以查询时也要满足最左前缀原则,

Explain详解以及索引优化实践

Show warning:mysql对sql进行优化,查出来为可能执行的步骤

Explain中的列:

Select_type:查询的类型

Primary:复杂查询的最外层的select

Deriverd:衍生查询,在from之后查询,生成出来的临时表

Subquery:子查询,在select后查询出来的表

Simple:简单查询

Table:

查询的哪一张表,这里的《devived3》表示这个查询是从一个衍生查询中查询出来的结果,3表示查询的id;

Id:

id表示了查询顺序,id越大越先执行;不一定是唯一的,相同时先查询上面的,再查询下面的

Type:

这一列表示mysql如何去查找表中的行数据,、

依次从最优到最差分别为:system>const>eq_ref>ref>range>index>all

一般我们的查询要优化到range级别,最好是能达到ref;

null值:mysql会在优化阶段分解查询语句,在执行阶段不需要访问表或者索引(性能非常高);例如:

System:它是const的特例,表示from时,表里面总共只有1条数据

Const:用于primary key或者 unique key的所有列和常数进行比较,所以表中最多有一行数据进行匹配,读取1次,速度比较快;

Eq_ref:在进行关联查询时,使用的是主键(或者唯一键)关联;

Ref:查询时用到了普通索引,但是没有用到主键或者唯一Id,查询结果可能是多条记录;

Range:范围查找,对于结果是between,<>,=等,它也是走了索引,用的索引类型在key字段,虽然是走了索引,但是当查询结果集比较大的时候,最好还是做优化,比如分页;

Index:这种扫描不会从索引树的根节点,而是直接对叶子节点进行遍历,所以速度还是比较慢的;但是有的时候mysql会去优化到用index,当所有的二级索引存了所有字段的信息(比如一张表只有两个字段,一个字段建了主键索引,一个字段建了二级索引,因为二级索引会存放主键值,这种情况下二级索引就有了所有字段的值,查询时,因为二级索引小,没有像主键索引那样还存放了表信息,所以就会使用二级索引);总结就是不管是用的是不是主键索引,我们都应该避免这种情况;

All:用主键索引,不从根节点查找,从叶子节点上,进行全部索引节点遍历查找;

Key_len

Key-len一般是用来查看联合索引的,索引匹配的长度,比如联合索引index(col1,col2),他们都是Int类型,我进行查找的时候,如果只用到了col1,那么key_len=4,如果都用到了key_len=8

Key-len计算规则

Ref

索引关联查询的字段,或者是使用索引时添加的条件为常量const

Rows

mysql预估当前查询可能要扫描多少行

Extra

常见的值:

Using index:覆盖索引,查询的数据,在查询的索引树种就全部包含了,不需要再进行回表,他只是表示查询的一种方式,并不是索引

Using where:使用where来处理查询结果,并且查询的列没有索引,这种情况一般需要优化,添加索引

Using temporary:需要创建一张临时表来处理查询,比如select distenct name from actor;

当actor.name没有索引时,就会创建一张临时表,把name所有值拿出来,再进行distinct,这种通常是需要创建索引来进行优化的;

Using filesort:使用外部排序,而不是索引排序,数据量小的时候在内存中进行排序,否则就需要在磁盘中来进行排序,这种情况一般也是要进行优化的,创建索引

索引优化实践1

1.全值匹配

联合索引,尽量使用全值匹配,也就是把索引字段全部利用上;

  1. 最左前缀

满足最左前缀匹配法则,而且写的时候,尽量按照索引顺序去写,虽然MySQL会去进行优化,但是还是自己完成比较好;

  1. 不在索引列上做任何操作(计算,函数,类型转换)从而导致索引失效而走全表扫描

存储引擎不能使用索引中,范围条件右边的列

  1. 尽量使用覆盖索引,减少select*的语句,尽量不去回表

  1. Mysql在使用(!=或<>)的时候,无法使用索引会导致全表扫描

  1. is null和is not null一般情况下也无法使用索引

  1. like以通配符开头(‘%lisa’)mysql索引失效会导致全表扫描

但是如果是’lisa%’则会走索引,它和函数不一样,mysql中like,底层当做=去对待的,而函数比如left(3,name),则不会走索引,mysql不会把函数结果计算出来再去判断的;

  1. 字符串不加单引号导致索引失效

其实就是这种情况mysql是会帮我们做数据转换(类似通过函数),转换完再比较

  1. 少用or或in,用它来查询时,mysql不一定使用索引

  1. 范围查找优化

当范围查找量过大时,mysql评估这种跨度较大,还不如走全表扫描,所以可以减小范围

索引优化实践2

扫描行数多,不一定就慢

第一条查询走的是全表扫描预计是9万条数据,第二条查询是我们强制它走索引,预计扫描条数是4万,但是结果是第一条更快一点(个人理解:虽然是走了索引,但是二级索引有一个回表的过程,MySQL会结合数据量判断回表整个综合下来会慢一点,所以它在优化时就选择了全表扫描)

索引下推:

前置条件,这张表有一个name,age,position的联合二级索引

在mysql5.6之前,这条SQL的执行逻辑是,先查询name条件,然后拿到主键id,再根据主键id,去主键索引中,把所有满足name条件的数据查询出来,然后再进行age和position的条件判断;

5.7开始,这条SQL的执行逻辑发生改变,它是查询name的条件,然后继续判断它是否满足age和position的条件,满足才保留主键id,这样它从次级索引中查询到的主键id的集合的size就小了,去主键索引中回表的次数也少了,更高效,这就是索引下推;

用trace工具做SQL分析

MySQL索引选择时,会进行一个cost成本预计算,通过trace工具来查看到;

开启trace功能,(一般是不开启,会浪费资源,做SQL分析的时候再开),然后我们的查询语句和最后一条 一起执行;

我们就可以看到,MySQL在执行前做了判断,预估走全表扫描的成本,预估走某个索引的成本,还会对我们的SQL做优化,比如联合索引index(a,b,c),我们给的条件顺序是b,a,c它就会帮我们把顺序调整为a,b,c,

预估走次级索引的成本

常见SQL深入优化

Order by与group by优化

前置条件:index(name,age,position)

这里order by走了索引,using index condition ,因为name= lilei 的所有数据,在索引树中,它的age肯定是有顺序的;

跳过了age,name=lilei 的数据,position不一定是有序的,所以用到了文件排序

这里name=lilei 的数据,age是有序的,排序顺序为age,position,而age有序的数据,也就是age相等的情况下,position当然也是有序的

 相较于上面的情况,我们把排序方式变换一下,排序就不会走索引了,因为name=lilei的数据,只能保证age是有序的,不能保证position有序

先限制了name和age的值,position就是有序的了,所以这里的排序是using index

MySQL 8支持了降序排序,会走,但是我们经常用的5.6,和5.7版本不会

范围查找,排序不走索引,很容易理解,name=lilei和name=zhuge的数据,在索引树上age数据全部依次拿出来,是没有顺序的,只能是name相等的才有序;

当数据量太大,因为还有回表的操作,MySQL可能也会使用全表扫描,

Group by默认其实是先进行排序,再分组,也是满足索引创建的最左前缀法则,对于group by的优化,如果不需要排序的可以加上 order by null来禁止排序;

Using filesort文件排序

文件排序的方式分为

单路排序:一次性取出满足条件的行的所有字段,然后进行排序,它对内存要求会多一点

双路排序:先根据条件,去出满足条件行的主键id,和需要排序的字段,到内存中,排完序过后再回表取需要查询的字段

当需要排序的数据量很大的时候,分配的排序内存空间不足以一次放下,会创建临时文件,先吧数据放到临时文件,再放到内存空间中进行排序

单路和双路排序我们可以设置,但是mysql一般都会优化选择合适的排序方式(比如所有字段的总长度小雨max_length_for_sort_date(默认1024字节)就会使用单路排序),所以 我们也基本不会去改变;

索引设计原则

  1. 代码先行,索引后上
  2. 联合索引尽量覆盖所有条件
  3. 不要在字段值很多相同的字段上建立索引,比如性别字段,只有男女两种值
  4. 长字符串,可以才用前缀索引,不然占用磁盘空间高,比如name的钱二十个字符来建立索引key index(name(20),age,position),比如
  5. Where 与order by 冲突时,先满足where
  6. 对于慢sql查询对sql做优化;参考 mysql慢查询-CSDN博客

Limit分页优化

Limit本质是把查询结果全部拿出来,然后再舍弃其它不需要的数据;

如果是对于主键自增,且连续的数据

Select * from table1 limit 9000,5;

可以改成

Select * from table1 where id>9000 limit 5;

工作中,经常遇到按照二级索引字段排序,然后limit,虽然排序字段有索引,但是因为MySQL认为查询(需要排序)数量大,走二级索引还要回表,所以走的全表扫描; 然后排序还用到了file sort,所以效率不是很好;

对于这种,我们可以先根据二级索引,查询需要的数据的主键id,并排好序,然后再去主键索引里面去关联查询,这样我们的排序因为是在二级索引上,用的索引排序

Join 关联优化

前置条件,t1,t2的a字段建了二级索引,t2有100条数据,t1有10000条数据;

Select * from t1 inner join t2 on t1.a=t2.a;

嵌套循环连接算法(NLJ):

大概意思就是一次一次循环一张表,根据关联条件,去另一张表中去拿满足条件的数据;

这条SQL的执行过程为,首先t2表的数据小于t1,MySQL会优化成小表驱动大表,所以把t2表中的数据一条一条去遍历,根据关联字段a,去t1表中去查找数据,因为a字段上有索引,所以t2表中的一条数据,t1表中也只会进行一次回表,所以总共就是t1扫描100次,t2扫描100次;

优化过后的执行计划

基于块的嵌套循环连接算法(BNL):

次性从小表中,拿出一部分数据,放到join_buffer缓存中,然后去大表中的聚簇索引,一条一条的拿数据,拿一条,然后去join_buffer里面和小表中的所有数据进行全部匹配,join_buffer也是有大小限制的,默认值是256k,当小表的数据一次放不下的时候,它会分几次放入join_buffer;

这里a字段都是没有索引,小表t2有100条数据,大表t1有10000条数据;

Select * from t1 inner join t2 on t1.a=t2.a;

根据BNL算法,它一次性拿出小表100条进缓存,就是100次扫描,然后大表10000条数据,一条一条进缓存,每次都要和100条小表数据匹配,所以最终扫描的次数是100+10000=10100次,在缓存中判断的次数10000*100=100万次判断;(虽然在缓存中判断快,但是架不住基数大);

如果没有索引,按照NLJ算法去执行的话,t2表一条一条拿出来是100次扫描,然后因为没有索引,去t1表做对比的时候,是也是一条一条扫描,所以t2的一条数据,在t1中就是一万次扫描,最终结果是100*10000=100万次的扫描;

MySQL关联的时候,一般是小表驱动大表(left join 和right join 是已经指定了,除外),小表称为驱动表,大表称为被驱动表,被驱动表的关联字段最好是建立上索引,当然能都建立上索引最好;

MySQL关联优化

1.:小表驱动大表(先执行表一定要为小表,一般MySQL也是这么选择,但是如果没选择上的话,用straight_join来指定驱动表)

小表:这里的小表是指满足关联条件的数据,比如关联条件上加了t2.a=lisa,是满足这个条件的数据量小

2.关联字段建立上索引(大表一定建立上索引)

In和exist优化

用in,mysql会先查询in里面的语句,然后循环in,和外边的作比较

用exist,会先查询外边的语句,再循环外边的结果,和里面表的过滤

所以上面的案例,对于B表比较小的场景,用in,对于A表比较小的场景,用exist(原则就是小表驱动大表)

Count()优化

MySQL官方其实是推荐用count(*),有索引的情况下,count(字段)会比count(主键)快,因为字段的二级索引存储的数据量小,而innodb的聚簇索引存了所有列的信息;

对于存储引擎为myisam的表,MySQL它去维护了一个表的总数,所以直接就能拿到,

但是innodb的表,不会存储表的总记录数(因为有MVCC机制),查询需要实时计算;

其它方式:

将总数维护到Redis中,不能100%保证一致;

增加数据库技术表,利用数据库本地事务来保证基数准确

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值