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%保证一致;

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/542020.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【保姆级】GPT的Oops问题快速解决方案

GPT的"Oops"问题通常指的是GPT在处理请求时突然遇到错误或无法提供预期输出的情况。要快速解决这个问题&#xff0c;可以尝试以下分步策略&#xff1a; 确认问题范围&#xff1a; 首先&#xff0c;确认问题是偶发的还是持续存在的。如果是偶发的&#xff0c;可能是临…

Html提高——HTML5 新增的语义化标签

引入&#xff1a; 以前布局&#xff0c;我们基本用 div 来做。div 对于搜索引擎来说&#xff0c;是没有语义的。 但是在html5里增加了语义化标签&#xff0c;如 <header>&#xff1a;头部标签 <nav>&#xff1a;导航标签 <article>&#xff1a;内容标签 &…

phpcms头像上传漏洞引发的故事

目录 关键代码 第一次防御 第一次绕过 第二次防御 第二次绕过 第三次防御 第三次绕过 如何构造一个出错的压缩包 第四次防御 第四次绕过 本篇文章是参考某位大佬与开发人员对于文件包含漏洞的较量记录下的故事&#xff0c;因为要学习文件包含漏洞&#xff0c;就将大佬…

Dense Distinct Query for End-to-End Object Detection

摘要 对象检测中的一对一标签分配成功地消除了作为后处理的非极大值抑制&#xff08; NMS &#xff09;的需要&#xff0c;并使流水线端到端。然而&#xff0c;这引发了一个新的困境&#xff0c;因为广泛使用的稀疏查询无法保证高召回率&#xff0c;而密集查询不可避免地带来更…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:ListItemGroup)

该组件用来展示列表item分组&#xff0c;宽度默认充满List组件&#xff0c;必须配合List组件来使用。 说明&#xff1a; 该组件从API Version 9开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。该组件的父组件只能是List。 使用说明 当List…

uni app 钓鱼小游戏

最近姑娘喜欢玩那个餐厅游戏里的钓鱼 &#xff0c;经常让看广告&#xff0c;然后就点点点... 自己写个吧。小鱼的图片自己搞。 有问题自己改&#xff0c;不要私信我 <template><view class"page_main"><view class"top_linear"><v…

1.2 课程架构介绍:STM32H5 芯片生命周期管理与安全调试

1.2 课程架构介绍&#xff1a;STM32H5 芯片生命周期管理与安全调试 下面开始学习课程的第二节&#xff0c;简单介绍下STM32H5芯片的生命周期和安全调试&#xff0c;具体课程大家可以观看STM32官方录制的课程&#xff0c;链接&#xff1a;1.2. 课程架构介绍&#xff1a;STM32H5…

使用IDEA2023创建传统的JavaWeb项目并运行与调试

日期:2024-0312 作者:dusuanyun 文档环境说明: OS:Deepin 20.9(Linux) JDK: OpenJDK21 Tomcat:10.1.19 IDEA: 2023.3.4 (Ultimate Edition) 本文档默认已经安装JDK及环境变量的配置。 关键词…

PytorchAPI的使用及在GPU的使用和优化

API 调用API&#xff1a;和手动实现的思路是一样的。#1&#xff0c;#2这两个步骤是通用的步骤&#xff0c;相当于建立一个模型&#xff0c;之后你具体的数据直接丢进去就行了。只需要按着这样的样式打代码就行&#xff0c;死的东西&#xff0c;不需要你自己创造。 import torc…

一款Jenkins的综合漏洞利用工具-JenkinsExploit-GUI

一、使用 jdk版本 在windows或linux使用jdk8的哪一个版本应该都可以,在macOS里需要jdk8u较高的版本,比如jdk8u321 二、外置payload 从release下载windows_tools,linux_tools或macOS_tools并放在与JenkinsExploit-GUI-*-SNAPSHOT.jar相同的目录,或者可以自行打包tools_source…

fs模块 之 文件读取

fs 文件读取&#xff1a; 利用文件读取而不是直接打开文本查看的目的是为了实现自动化 读取文件的应用场景:电脑开机/程序运行/播放视频音乐/上传文件... 一、异步读取 &#xff08;1&#xff09;语法&#xff1a;fs.readFile(path,[options],callback); 以之前写的文件写…

idea maven 项目融合

背景 &#xff1a;项目A 和项目B 是两个独立的多模块项目&#xff0c;项目A 和项目B &#xff0c;均为独立的数据源 。其中项目B 有两个数据原。 需要将项目B 以多模块的方式融合进项目A。 解决版本。建立项目C&#xff0c;只含有pom的&#xff0c;空项目&#xff0c;项目A和项…