Mysql索引优化分析-第一篇

1.性能下降SQL慢 执行时间长 等待时间长

查询语句写的烂

索引失效(单值,复合)

关联查询太多join(设计缺陷或不得已的需求)

服务器调优及各个参数设置(缓冲\线程数等)

2.常见通用的join查询

2.1SQL执行顺序

2.1.1手写

在这里插入图片描述

2.1.2机读

在这里插入图片描述

2.1.3总结

在这里插入图片描述

2.2Join图

在这里插入图片描述

2.3建表SQL

2.4 7种Join

3.索引简介

3.1什么是索引

MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。
可以得到索引的本质:索引是数据结构

可以简单理解为"排好序的快速查找数据结构"。

详解(重要):

在这里插入图片描述
结论:

数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构就是索引。

一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以文件形式存储在硬盘上.

我们平时所说的索引,如果没有特别指明,都是指B树(多路搜索树,并不一定是二叉树)结构组织的索引。其中聚集索引,次要索引,覆盖索引,复合索引,前缀索引,唯一索引默认都是使用B+树索引,统称索引。当然,除了B+树这种类型的索引之外,还有哈希索引(hash index)等。

3.2索引优势

  • 类似大学图书馆建书目索引,提高数据检索效率,降低数据库的IO成本

  • 通过索引列对数据进行排序,降低数据排序成本,降低了CPU的消耗

3.3索引劣势

  • 实际上索引也是一张表,该表保存了主键和索引字段,并指向实体表的记录,所以索引列也是要占用空间的
  • 虽然索引大大提高了查询速度,同时却会降低更新表的速度,如果对表INSERT,UPDATE和DELETE。因为更新表时,MySQL不仅要不存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息.
  • 索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立优秀的索引,或优化查询语句

3.4MySQL分类

3.4.1单值索引

即一个索引只包含单个列,一个表可以有多个单列索引。

建议一张表索引不要超过5个;

优先考虑复合索引

3.4.2唯一索引

索引列的值必须唯一,但允许有空值

3.4.3复合索引

即一个索引包含多个列

3.4.4基本用法

  • 创建

CREATE [UNIQUE] INDEX indexName ON mytable(columnname(length));

如果是CHAR,VARCHAR类型,length可以小于字段实际长度;
如果是BLOB和TEXT类型,必须指定length。

ALTER mytable ADD [UNIQUE] INDEX [indexName] ON(columnname(length));

  • 删除

DROP INDEX [indexName] ON mytable;

  • 查看

SHOW INDEX FROM table_name\G

  • 使用ALTER命令

在这里插入图片描述

3.5MySQL索引架构

3.5.1BTree索引

在这里插入图片描述

在这里插入图片描述

Btree索引(或Balanced Tree),是一种很普遍的数据库索引结构,oracle默认的索引类型(本文也主要依据oracle来讲)。其特点是定位高效、利用率高、自我平衡,特别适用于高基数字段,定位单条或小范围数据非常高效。理论上,使用Btree在亿条数据与100条数据中定位记录的花销相同。

数据结构利用率高、定位高效
Btree索引的数据结构如下:

在这里插入图片描述

结构看起来Btree索引与Binary Tree相似,但在细节上有所不同,上图中用不同颜色的标示出了Btree索引的几个主要特点:

树形结构:由根节(root)、分支(branches)、叶(leaves)三级节点组成,其中分支节点可以有多层。
多分支结构:与binary tree不相同的是,btree索引中单root/branch可以有多个子节点(超过2个)。
双向链表:整个叶子节点部分是一个双向链表(后面会描述这个设计的作用)
单个数据块中包括多条索引记录
这里先把几个特点罗列出来,后面会说到各自的作用。

结构上Btree与Binary Tree的区别,在于binary中每节点代表一个数值,而balanced中root和Btree节点中记录了多条”值范围”条目(如:[60-70][70-80]),这些”值范围”条目分别指向在其范围内的叶子节点。既root与branch可以有多个分支,而不一定是两个,对数据块的利用率更高。

在Leaf节点中,同样也是存放了多条索引记录,这些记录就是具体的索引列值,和与其对应的rowid。另外,在叶节点层上,所有的节点在组成了一个双向链表。
了解基本结构后,下图展示定位数值82的过程:

在这里插入图片描述

演算如下:
读取root节点,判断82大于在0-120之间,走左边分支。
读取左边branch节点,判断82大于80且小于等于120,走右边分支。
读取右边leaf节点,在该节点中找到数据82及对应的rowid
使用rowid去物理表中读取记录数据块(如果是count或者只select rowid,则最后一次读取不需要)

在整个索引定位过程中,数据块的读取只有3次。既三次I/O后定位到rowid。

而由于Btree索引对结构的利用率很高,定位高效。当1千万条数据时,Btree索引也是三层结构(依稀记得亿级数据才是3层与4层的分水岭)。定位记录仍只需要三次I/O,这便是开头所说的,100条数据和1千万条数据的定位,在btree索引中的花销是一样的。

平衡扩张
除了利用率高、定位高效外,Btree的另一个特点是能够永远保持平衡,这与它的扩张方式有关。(unbalanced和hotspot是两类问题,之前我一直混在一起),先描述下Btree索引的扩张方式:

新建一个索引,索引上只会有一个leaf节点,取名为Node A,不断的向这个leaf节点中插入数据后,直到这个节点满,这个过程如下图(绿色表示新建/空闲状态,红色表示节点没有空余空间):

在这里插入图片描述

当Node A满之后,我们再向表中插入一条记录,此时索引就需要做拆分处理:会新分配两个数据块NodeB & C,如果新插入的值,大于当前最大值,则将Node A中的值全部插入Node B中,将新插入的值放到Node C中;否则按照5-5比例,将已有数据分别插入到NodeB与C中。

无论采用哪种分割方式,之前的leaf节点A,将变成一个root节点,保存两个范围条目,指向B与C,结构如下图(按第一种拆分形式):

在这里插入图片描述

当Node C满之后,此时 Node A仍有空余空间存放条目,所以不需要再拆分,而只是新分配一个数据块Node D,将在Node A中创建指定到Node D的条目:

在这里插入图片描述

如果当根节点Node A也满了,则需要进一步拆分:新建Node E&F&G,将Node A中范围条目拆分到E&F两个节点中,并建立E&F到BCD节点的关联,向Node G插入索引值。此时E&F为branch节点,G为leaf节点,A为Root节点:

在这里插入图片描述

在整个扩张过程中,Btree自身总能保持平衡,Leaf节点的深度能一直保持一致。

实际应用中的一些问题
前面说完了Btree索引的结构与扩张逻辑,接下来讲一些Btree索引在应用中的一些问题:

单一方向扩展引起的索引竞争(Index Contention)

若索引列使用sequence或者timestamp这类只增不减的数据类型。这种情况下Btree索引的增长方向总是不变的,不断的向右边扩展,因为新插入的值永远是最大的。

当一个最大值插入到leaf block中后,leaf block要向上传播,通知上层节点更新所对应的“值范围”条目中的最大值,因此所有靠右边的block(从leaf 到branch甚至root)都需要做更新操作,并且可能因为块写满后执行块拆分。

如果并发插入多个最大值,则最右边索引数据块的的更新与拆分都会存在争抢,影响效率。在AWR报告中可以通过检测enq: TX – index contention事件的时间来评估争抢的影响。解决此类问题可以使用Reverse Index解决,不过会带来新的问题。

Index Browning 索引枯萎(不知道该怎么翻译这个名词,就是指leaves节点”死”了,树枯萎了)

其实oracle针对这个问题有优化机制,但优化的不彻底,所以还是要拿出来的说。

我们知道当表中的数据删除后,索引上对应的索引值是不会删除的,特别是在一性次删除大批量数据后,会造成大量的dead leaf挂到索引树上。考虑以下示例,如果表100以上的数据会部被删除了,但这些记录仍在索引中存在,此时若对该列取max()

在这里插入图片描述
通过与之前相同演算,找到了索引树上最大的数据块,按照记录最大的值应该在这里,但发现这数据块里的数据已经被清空了,与是利用Btree索引的另一个特点:leaves节点是一个双向列表,若数据没有找到就去临近的一个数据块中看看,在这个数据块中发现了最大值99。

在计算最大值的过程中,这次的定位多加载了一个数据块,再极端的情况下,大批量的数据被删除,就会造成大量访问这些dead leaves。

针对这个问题的一般解决办法是重建索引,但记住! 重建索引并不是最优方案,详细原因可以看看这。使用coalesce语句来整理这些dead leaves到freelist中,就可以避免这些问题。理论上oracle中这步操作是可以自动完成的,但在实际中一次性大量删除数据后,oracle在短时间内是反应不过来的。

3.5.2Hash索引

3.5.3full-text全文索引

3.5.4R-Tree索引

3.6那些情况需要创建索引

1.主键自动建立唯一索引

2.频繁作为查询的条件的字段应该创建索引

3.查询中与其他表关联的字段,外键关系建立索引

4.频繁更新的字段不适合创建索引

因为每次更新不单单是更新了记录还会更新索引,加重IO负担

5.Where条件里用不到的字段不创建索引

6.单间/组合索引的选择问题,who?(在高并发下倾向创建组合索引)

7.查询中排序的字段,排序字段若通过索引去访问将大大提高排序的速度

8.查询中统计或者分组字段

3.7哪些情况不要创建索引

1.表记录太少

2.经常增删改的表

3.数据重复且分布平均的表字段,因此应该只为经常查询和经常排序的数据列建立索引。
注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。

在这里插入图片描述

4.性能分析

4.1MySQL Query Optimizer

在这里插入图片描述

4.2MySQL常见瓶颈

  • CPU:CPU在饱和的时候一般发生在数据装入在内存或从磁盘上读取数据时候
  • IO:磁盘I/O瓶颈发生在装入数据远大于内存容量时
  • 服务器硬件的性能瓶颈:top,free,iostat和vmstat来查看系统的性能状态

4.3Explain

4.3.1是什么(查看执行计划)

  • 使用EXPLAIN关键字可以模拟优化器执行SQL语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是结构的性能瓶颈。

4.3.2能干嘛

  • 表的读取顺序
  • 数据读取操作的操作类型
  • 哪些索引可以使用
  • 哪些索引被实际使用
  • 表之间的引用
  • 每张表有多少行被优化器查询

4.3.3怎么用

  • Explain+SQL语句

  • 执行计划包含的信息

在这里插入图片描述

4.3.4各个字段解释

  • id

    select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序

    三种情况:

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

在这里插入图片描述

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

在这里插入图片描述

id相同不同,同时存在

在这里插入图片描述

  • select_type

    • 有哪些

在这里插入图片描述

  • 查询的类型,主要用于区别普通查询、联合查询、子查询等的复杂查询

1.SIMPLE

简单的select查询,查询中不包含子查询或者UNION

2.PRIMARY

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

3.SUBQUERY

在SELECT或者WHERE列表中包含了子查询

4.DERIVED、

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

5.UNION

若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED

6.UNION RESULT

从UNION表获取结果的SELECT

  • table

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

  • type

在这里插入图片描述

访问类型排列

在这里插入图片描述

显示查询使用了何种类型
从最好到最差依次是:
system>const>eq_ref>ref>range>index>ALL

system

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

const

表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快。如将主键至于where列表中,MySQL就能将该查询转换为一个常量

在这里插入图片描述

eq_ref

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

在这里插入图片描述

ref

非唯一索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体

在这里插入图片描述

range

只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引一般就是在你的where语句中出现了between、<、>、in等的查询这种范围扫描索引扫描比全表扫描要好,因为他只需要开始索引的某一点,而结束语另一点,不用扫描全部索引

index

Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小。
也就是说虽然all和index都是读全表,但index是从索引中读取的,而all是从硬盘中读的
在这里插入图片描述

all

FullTable Scan,将遍历全表以找到匹配的行

备注:

一般来说,得保证查询只是达到range级别,最好达到ref

  • possible_keys

显示可能应用在这张表中的索引,一个或多个。
查询涉及的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用

  • key

实际使用的索引。如果为null则没有使用索引

查询中若使用了覆盖索引,则索引和查询的select字段重叠

  • key_len

表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好

key_len显示的值为索引最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的

  • ref

显示索引那一列被使用了,如果可能的话,是一个常数。那些列或常量被用于查找索引列上的值

在这里插入图片描述

  • rows

根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数,越少越好

在这里插入图片描述

  • Extra

包含不适合在其他列中显示但十分重要的额外信息

  • 1.Using filesort

说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成排序操作成为“文件排序”性能不好

在这里插入图片描述

  • 2.Using temporary

使用了临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序order by 和分组查询 group by

在这里插入图片描述

  • 3.USING index

表示相应的select操作中使用了覆盖索引(Coveing Index),避免访问了表的数据行,效率不错!
如果同时出现using where,表明索引被用来执行索引键值的查找;如果没有同时出现using where,表面索引用来读取数据而非执行查找动作。

在这里插入图片描述

覆盖索引(Covering Index)

在这里插入图片描述

  • 4.Using where

表面使用了where过滤

  • 5.using join buffer

使用了连接缓存

  • 6.impossible where

where子句的值总是false,不能用来获取任何元组

在这里插入图片描述

  • 7.select tables optimized away

在没有GROUPBY子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。

  • 8.distinct

优化distinct,在找到第一匹配的元组后即停止找同样值的工作

4.3.5热身案列

在这里插入图片描述

在这里插入图片描述

微信公众号

JAVA程序猿成长之路
JAVA程序猿成长之路
分享资源,记录程序猿成长点滴。专注于Java,Spring,SpringBoot,SpringCloud,分布式,微服务。

发布了234 篇原创文章 · 获赞 157 · 访问量 41万+

猜你喜欢

转载自blog.csdn.net/niugang0920/article/details/99684055