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热身案列
微信公众号