1、MySQL索引优化分析
一、索引基础
1、索引
索引(Index)是帮助 MySQL 高效获取数据的数据结构。
索引是数据结构。可以简单理解为排好序的快速查找数据结构。
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上。
2、索引优缺点
优势:
- 提高数据检索的效率,降低数据库的IO成本。
- 通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗。
劣势:
索引大大提高了查询速度,同时却会降低更新表的速度
实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间
的。
二、MySQL索引
MySQL使用的索引的数据结构是BTree。
B-Tree是为磁盘等外存储设备设计的一种平衡查找树。
系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。
InnoDB存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位。InnoDB存储引擎中默认每个页的大小为16KB,可通过参数innodb_page_size将页的大小设置为4K、8K、16K,在MySQL中可通过如下命令查看页的大小:
mysql> show variables like 'innodb_page_size';
而系统一个磁盘块的存储空间往往没有这么大,因此InnoDB每次申请磁盘空间时都会是若干地址连续磁盘块来达到页的大小16KB。InnoDB在把磁盘数据读入到磁盘时会以页为基本单位,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘I/O次数,提高查询效率。
B-Tree结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述B-Tree,首先定义一条记录为一个二元组[key, data] ,key为记录的键值,对应表中的主键值,data为一行记录中除主键外的数据。对于不同的记录,key值互不相同
1、BTree(平衡多路查找树):
1.1 一颗 m 阶B树:
1、 树中每个节点最多有 m 棵子树(即含有最多 m-1 个关键字)
2.、若根节点不是终端节点,则最少含有2棵子树
3、除根节点外的所有非叶节点最少有Ceil( m/2 )棵子树(即最少含有Ceil( m/2 ) - 1 个关键字)注:ceil()是个朝正无穷方向取整的函数 如ceil(1.1)结果为2);
4、所有非叶子节点的结构为:
n P0 K1 P1 K2 P2 ... Kn Pn 指向子树的指针 关键字
- 5、所有叶节点都出现在同一个层次上,且叶节点不带任何值,代表查询失败
1.2 实际应用中 3 阶 B树:
每个节点占用一个盘块的磁盘空间,
一个节点上有两个升序排序的关键字和三个指向子树根节点的指针
指针存储的是子节点所在磁盘块的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。
1.3 BTree 的效果
磁盘块读入内存后,由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率。而磁盘I/O操作是影响整个B-Tree查找效率的决定因素。B-Tree相对于AVLTree缩减了节点个数,使每次磁盘I/O取到内存的数据都发挥了作用,从而提高了查询效率。
2、B+Tree
B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构,InnoDB存储引擎就是用B+Tree实现其索引结构。
从上一节中的B-Tree结构图中可以看到每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点(即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度。
2.1 B+Tree相对于B-Tree有几点不同:
- 非叶子节点只存储键值信息。
- 所有叶子节点之间都有一个链指针。
- 数据记录都存放在叶子节点中。其他节点作为索引
1.2 B+Tree的优点
- InnoDB存储引擎中页的大小为16KB
- 一般表的主键类型为INT(占用4个字节)或BIGINT(占用8个字节),一般表的主键类型为INT(占用4个字节)或BIGINT(占用8个字节),指针类型也一般为4或8个字节,
- 因此一个页(B+Tree中的一个节点)中大概存储16KB/(8B+8B)=1K个键值
- 也就是说一个深度为3的B+Tree索引可以维护10^3 * 10^3 * 10^3 = 10亿 条记录。
实际情况中每个节点可能不能填充满,因此在数据库中,B+Tree的高度一般都在24层。[mysql](http://lib.csdn.net/base/mysql)的InnoDB存储引擎在设计时是将根节点常驻内存的,也就是说查找某一键值的行记录时最多只需要13次磁盘I/O操作
三、 explain (id、type、key、rows、extra 最重要)
1. id
id 相同,执行顺序由上至下
id 不同,id 越大越先被执行。
- 如果是子查询,id 的序号会递增,id 值越大优先级越高,越先被执行
Id 有相同也有不同。
- id 如果相同,可以认为是一组,从上往下顺序执行;
- 在所有组中,id 值越大,优先级越高,越先执行衍生 = DERIVED
2. select_type
select_type: 代表查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询。
select_type 含义 1. SIMPLE 1. SIMPLE 代表单表查询。
2、简单的 select 查询,查询中不包含子查询或者 UNION
![image-20201025211851005](/Users/renren/Library/Application Support/typora-user-images/image-20201025211851005.png)2. PRIMARY 1. 查询中若包含任何复杂的子部分,最外层查询则被标记为 Primary
![image-20201025212702926](/Users/renren/Library/Application Support/typora-user-images/image-20201025212702926.png)3. DERIVED 1. 在 FROM 列表中包含的子查询被标记为 DERIVED(衍生)。
2. MySQL 会递归执行这些子查询, 把结果放在临时表里。4. SUBQUERY 在SELECT或WHERE列表中包含了子查询 5. DEPEDENT SUBQUERY 在SELECT或WHERE列表中包含了子查询,子查询基于外层 6. UNCACHEABLE SUBQUERY 无法使用缓存的子查询 7. UNION 1. 若第二个SELECT出现在UNION之后,则被标记为UNION;
2. 若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED
![image-20201025220553420](/Users/renren/Library/Application Support/typora-user-images/image-20201025220553420.png)8. UNION RESULT 从UNION表获取结果的SELECT
3.type
type 是查询的访问类型。是较重要的一个指标。结果从最好到最坏依次为:
system > const > eq_ref > ref > range > index > ALL
type 类型 含义 1、system 表只有一行记录(等于系统表)
这是 const 类型的特例,平时不会出现2、const 1. 表示通过索引一次就找到了,const 用于比较 primary key 或者 unique 索引。
2. 因为只匹配一行数据,所以很快
3. 如将主键置于 where 列表中用=查询,MySQL 就能将该查询转换为一个常量。
![image-20201025221923618](/Users/renren/Library/Application Support/typora-user-images/image-20201025221923618.png)3、eq_ref 1. 唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描。
![image-20201025222937706](/Users/renren/Library/Application Support/typora-user-images/image-20201025222937706.png)4、ref 非唯一性索引扫描,返回匹配某个单独值的所有行
本质上也是一种索引访问,它返回所有匹配某个单独值的行, 然而,它可能会找到多个符合条件的行
![image-20201025230942869](/Users/renren/Library/Application Support/typora-user-images/image-20201025230942869.png)5、range 只检索给定范围的行,使用一个索引来选择行。
在你的 where 语句中出现 了 between、<、>、in 等的查询这种范围扫描索引6、index 1. 是sql使用了索引但是没有通过索引进行过滤
2. 一般是使用了覆盖索引或者是利用索引进行了排序分组
3. index 只遍历索引树,all 遍历所有数据文件,索引文件小的多![image-20201025231639178](/Users/renren/Library/Application Support/typora-user-images/image-20201025231639178.png)7、all > Full Table Scan,将遍历全表以找到匹配的行。
> 未使用索引
4. possible_keys
- 显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一
定被查询实际使用。
5. key
- 实际使用的索引。如果为NULL,则没有使用索引。
6. key_len
- 表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。
- key_len 字段能够帮你检查是否充分的利用上了索引。ken_len 越长,说明索引使用的越充分。
![image-20201025232945792](/Users/renren/Library/Application Support/typora-user-images/image-20201025232945792.png)
7. ref
- 显示索引的哪一列被使用了
- 哪些列或常量被用于查找索引列上的值。
8. rows
- rows 列显示 MySQL 认为它执行查询时必须检查的行数。越少越好!
9. Extra
额外补充的重要信息
extra 含义 1、using filesort (文件内排序)
1. 说明 mysql 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。
2. MySQL 中无法利用索引完成的顺序操作称为“文件排序”。
3. 需要优化2、using temporary 1、使用临时表保存中间结果;
2、MySQL对查询结果进行排序的时候使用了临时表,常见于 order by 和分组查询 group by
3、需要优化3、 using index 1、表示select操作中使用了覆盖索引(covering index),避免访问表的数据行,效率高
2、如果同时出现了using where,表示索引被用来执行索引键值的查找
3、如果没有同时出现 using where,表示索引用来读取数据而非执行查找动作覆盖索引(covering index): select 的数据列只要从索引中就能取得,不必读取数据行。MySQL可以利用索引返回select 列表中的字段,而不必根据索引再次读取数据文件,即查询列要被所建的索引覆盖。select必须要和索引的个数、顺序相一致。
如果要使用覆盖索引,一定要注意select 列表中取所需要的列,而不要 select *,
如果将所有字段做索引会导致索引文件过大,查询性能下降
四、索引优化实例
1、创建表
![image-20201026002827369](/Users/renren/Library/Application Support/typora-user-images/image-20201026002827369.png)
2、分析查询结果
![image-20201026002913489](/Users/renren/Library/Application Support/typora-user-images/image-20201026002913489.png)
3、创建索引
![image-20201026003308721](/Users/renren/Library/Application Support/typora-user-images/image-20201026003308721.png)
4、查询结果
![image-20201026003334547](/Users/renren/Library/Application Support/typora-user-images/image-20201026003334547.png)
![image-20201026005721294](/Users/renren/Library/Application Support/typora-user-images/image-20201026005721294.png)
5、重新建立索引
![image-20201026005405674](/Users/renren/Library/Application Support/typora-user-images/image-20201026005405674.png)
6、重新查看结果
![image-20201026005442592](/Users/renren/Library/Application Support/typora-user-images/image-20201026005442592.png)
7、结论
按照 BTree 的工作原理,对于索引 idx_ccv,会先对 category_id 进行排序,再对comment进行排序,最后对views进行排序,当comment > 1 条件是一个范围值(range),MySQL 无法利用索引再对后面的views部分进行检索,即 range 类型查询字段后面的索引无效。
![image-20201026010642193](/Users/renren/Library/Application Support/typora-user-images/image-20201026010642193.png)