mysql性能优化系列7-索引与执行计划

1. 索引

1.1 索引分类

(1)主键索引
根据主键建立索引,不允许重复,不允许空值
(2)唯一索引
索引的列值必须唯一,允许空值
(3)普通索引
普通列构建的索引,无限制
(4)全文索引
用大文本对象的列构建的索引
(5)组合索引
多个列组合构建索引,每个列不允许有空值。组合索引遵循最左前缀原则

1.2 实现原理

MySQL数据库支持多种索引类型。
(1)哈希索引
只有memory存储引擎支持哈希索引,用索引列的值计算hashCode,然后在hashCode对应位置存该值所在行数据的物理位置。因为使用散列算法,速度快,但不支持范围查找和排序。
(2)全文索引
FULLTEXT(全文)索引,只能用于MyISAM和InnoDB,数据量太大生成全文索引非常消耗时间和空间。对于文本大对象或者较大的CHAR类型数据,如果使用普通索引匹配文本,比如LIKE %word%,这样响应时间会很长。这时候可以使用全文索引。
(3)BTree索引
对于BTree和B+Tree,这篇文章http://blog.jobbole.com/24006/不错,下面内容借鉴这篇文章。
为了描述B-Tree,首先定义一条数据记录为一个二元组[key, data],key为记录的键值,对于不同数据记录,key是互不相同的;data为数据记录除key外的数据。那么B-Tree是满足下列条件的数据结构:

1. d为大于1的一个正整数,称为B-Tree的度。

2. h为一个正整数,称为B-Tree的高度。

3. 每个非叶子节点由n-1个key和n个指针组成,其中d<=n<=2d。

4. 每个叶子节点最少包含一个key和两个指针,最多包含2d-1个key和2d个指针,叶节点的指针均为null 。

5. 所有叶节点具有相同的深度,等于树高h。

6. key和指针互相间隔,节点两端是指针。

7. 一个节点中的key从左到右非递减排列。

8. 所有节点组成树结构。

9. 每个指针要么为null,要么指向另外一个节点。

10. 如果某个指针在节点node最左边且不为null,则其指向节点的所有key小于v(key1),其中v(key1)为node的第一个key的值。

11. 如果某个指针在节点node最右边且不为null,则其指向节点的所有key大于v(keym),其中v(keym)为node的最后一个key的值。

12. 如果某个指针在节点node的左右相邻key分别是keyi和keyi+1且不为null,则其指向节点的所有key小于v(keyi+1)且大于v(keyi)。

看一个d=2的B-Tree示意图。
在这里插入图片描述
由于B-Tree的特性,在B-Tree中按key检索数据的算法非常直观:首先从根节点进行二分查找,如果找到则返回对应节点的data,否则对相应区间的指针指向的节点递归进行查找,直到找到节点或找到null指针,前者查找成功,后者查找失败。
(4)B+Tree
B-Tree有许多变种,其中最常见的是B+Tree,例如MySQL就普遍使用B+Tree实现其索引结构。与B-Tree相比,B+Tree有以下不同点:

1. 每个节点的指针上限为2d而不是2d+1。

2. 内节点不存储data,只存储key;叶子节点不存储指针。

在这里插入图片描述
带有顺序访问指针的B+Tree
在这里插入图片描述
我们一般使用磁盘I/O次数评价索引结构的优劣。先从B-Tree分析,根据B-Tree的定义,可知检索一次最多需要访问h个节点。数据库系统的设计者巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次I/O就可以完全载入。为了达到这个目的,在实际实现B-Tree还需要使用如下技巧:
每次新建节点时,直接申请一个页的空间,这样就保证一个节点物理上也存储在一个页里,加之计算机存储分配都是按页对齐的,就实现了一个node只需一次I/O。B-Tree中一次检索最多需要h-1次I/O(根节点常驻内存),渐进复杂度为O(h)=O(logdN)。一般实际应用中,出度d是非常大的数字,通常超过100,因此h非常小(通常不超过3)。综上所述,用B-Tree作为索引结构效率是非常高的。
MyISAM索引实现
MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址。
在这里插入图片描述
在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。
MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录。MyISAM的索引方式也叫做“非聚集”的,因为索引文件和数据文件不在一个文件。
InnoDB索引实现
第一个重大区别是InnoDB的数据文件本身就是索引文件。因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。第二个与MyISAM索引的不同是InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的所有辅助索引都引用主键作为data域。
聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。

1.3 语法

(1)查看索引

SHOW INDEX FROM table_name

(2)创建索引

CREATE  [UNIQUE ] INDEX indexName ON mytable(columnname(length));
ALTER TABLE 表名 ADD  [UNIQUE ]  INDEX [indexName] ON (columnname(length)) 

(3)删除索引

DROP INDEX [indexName] ON mytable;

2. 执行计划

SQL语句加上EXPLAIN关键字可以查询执行计划。执行计划的结果如下:
在这里插入图片描述

2.1 ID列

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

2.1.1 id相同

执行顺序由上至下。
(1)执行语句

EXPLAIN select t2.* from t1,t2,t3  where t1.id = t2.id and t1.id = t3.id and t1.other_column = '';

(2)执行结果
在这里插入图片描述
ID列的值相同,先查询t1,再t2,最后t3。

2.1.2 id完全不相同

如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行。
(1)执行语句

EXPLAIN select t2.* from  t2 where id = (select id from t1 where id =  (select t3.id from t3 where t3.other_column=''));

(2)执行结果
在这里插入图片描述

2.1.3 id不完全相同

id值越大越先执行,相同的由上向下执行。
(1)执行语句

EXPLAIN select t2.* from (select t3.id from t3 where t3.other_column = '') s1 ,t2 where s1.id = t2.id

(2)执行结果
在这里插入图片描述

2.2 select_type

Select_type表示查询的类型。类型如下:

  • simple:简单的查询,不包括子查询和union查询
  • primary:查询包含复杂的子查询,最外层查询为primary
  • union:若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在 FROM子句的子查询中,外层SELECT将被标记为:DERIVED
  • subquery:在SELECT或WHERE列表中包含了子查询
  • derived:在FROM列表中包含的子查询的标记
  • UNION RESULT:从UNION表获取结果的SELECT

2.2.1 SIMPLE

(1)执行语句

EXPLAIN select * from t1

(2)执行结果
在这里插入图片描述
查询中不包含子查询或者UNION

2.2.2 PRIMARY与SUBQUERY

PRIMARY:查询中若包含任何复杂的子部分,最外层查询则被标记为
SUBQUERY:在SELECT或WHERE列表中包含了子查询
(1)执行语句

EXPLAIN select t1.*,(select t2.id from t2 where t2.id = 1 ) from t1 

(2)执行结果
在这里插入图片描述

2.2.3 DERIVED

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

(1)执行语句

EXPLAIN select t1.* from t1 ,(select t2.* from t2 where t2.id = 1 ) s2  where t1.id = s2.id

(2)执行结果
在这里插入图片描述

2.2.4 UNION RESULT 与UNION

UNION:若第二个SELECT出现在UNION之后,则被标记为UNION;
UNION RESULT:从UNION表获取结果的SELECT
(1)执行语句

EXPLAIN select * from t1 UNION  select * from t2

(2)执行结果
在这里插入图片描述

2.3 table

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

2.4 Type

type显示的是访问类型,是较为重要的一个指标,结果值从最好到最坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL。
常见的是system>const>eq_ref>ref>range>index>ALL,一般来说,得保证查询至少达到range级别,最好能达到ref。

2.4.1 System与const

System:表只有一行记录(等于系统表),这是const类型的特列。
Const:表示通过索引一次就找到了
(1)执行语句

EXPLAIN SELECT * from (select * from t2 where id = 1) d1;

(2)执行结果
在这里插入图片描述

2.4.2 eq_ref

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

(1)执行语句

EXPLAIN SELECT * from t1,t2 where t1.id = t2.id;

(2)执行结果

在这里插入图片描述
注意表的执行顺序。

2.4.3 Ref

非唯一性索引扫描,返回匹配某个单独值的所有行。
(1)执行语句

EXPLAIN select count(DISTINCT col1) from ta where col1 = 'ac'

(2)执行结果
在这里插入图片描述

2.4.4 Range

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

EXPLAIN select * from t1 where id BETWEEN 30 and 60

(2)执行结果
在这里插入图片描述

2.4.5 Index

当查询的结果全为索引列的时候,虽然也是全部扫描,但是只查询的索引库,而没有去查询其他数据。

(1)执行语句

EXPLAIN select col1,col2 from ta

(2)执行结果
在这里插入图片描述

2.4.6 All

遍历全表以找到匹配的行
(1)执行语句

EXPLAIN select * from t1

(2)执行结果
在这里插入图片描述

2.5 possible_keys与Key

possible_keys是可能使用的key,Key为实际使用的索引。如果为NULL,则没有使用索引。如果查询中使用了覆盖索引,则该索引和查询的select字段重叠。
(1)执行语句

EXPLAIN select col1,col2 from ta

(2)执行结果
在这里插入图片描述

2.6 key_len

key_len表示索引使用的字节数,根据这个值,就可以判断索引使用情况,特别是在组合索引的时候,判断所有的索引字段是否都被查询用到。char和varchar跟字符编码也有密切的联系,latin1占用1个字节,gbk占用2个字节,utf8占用3个字节。
(1)索引字段为char类型并且字段定义不为Null

explain select * from s1 where name='enjoy';

在这里插入图片描述
name这一列为char(10),字符集为utf-8占用3个字节,Keylen=10*3。
(2)索引字段为char类型并且字段定义可以为Null

explain select * from s2 where name='enjoyedu';

在这里插入图片描述
name这一列为char(10),字符集为utf-8占用3个字节,外加需要存入一个null值,Keylen=10*3+1(null) 结果为31。
(3)索引字段为varchar类型并且字段定义不为Null

explain select * from s3 where name='enjoyeud';

在这里插入图片描述
Keylen=varchar(n)变长字段+不允许Null=n*(utf8=3,gbk=2,latin1=1)+2。varchar变长字段需要额外的2个字节。
(4)索引字段为varchar类型并且字段定义可以为Null

explain select * from s4 where name='enjoyeud';

在这里插入图片描述
Keylen=varchar(n)变长字段+允许Null=n*(utf8=3,gbk=2,latin1=1)+1(NULL)+2。

总结:
变长字段需要额外的2个字节(VARCHAR值保存时只保存需要的字符数,另加一个字节来记录长度(如果列声明的长度超过255,则使用两个字节),所以VARCAHR索引长度计算时候要加2),固定长度字段不需要额外的字节。而NULL都需要1个字节的额外空间,所以索引字段最好不要为NULL,因为NULL让统计更加复杂并且需要额外的存储空间。复合索引有最左前缀的特性,如果复合索引能全部使用上,则是复合索引字段的索引长度之和,这也可以用来判定复合索引是否部分使用,还是全部使用。
(5)整数/浮点数/时间类型
同样,NOT NULL=字段本身的字段长度。NULL=字段本身的字段长度+1(因为需要有是否为空的标记,这个标记需要占用1个字节)。datetime类型在5.6中字段长度是5个字节,datetime类型在5.5中字段长度是8个字节。
在这里插入图片描述
在这里插入图片描述

2.7 Ref

显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。
(1)执行语句

EXPLAIN select * from s1 ,s2 where s1.id = s2.id and s1.name = 'enjoy'

(2)执行结果
在这里插入图片描述
第一条表明使用了s1表name属性的常量。第二条表明使用了test库s1表的id列。

2.8 Rows

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

2.9 Extra

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

  • distinct:在select部分使用了distinc关键字
  • using filesort:mysql对数据使用一个外部的索引排序,而不是按照表内的索引进行排序读取。也就是说mysql无法利用索引完成的排序操作成为“文件排序” 。常见于order by和group by语句中。
  • using index:查询时不需要回表查询,直接通过索引就可以获取查询的数据。也就是覆盖索引
  • using join buffer:使用了连接缓存
  • using intersect:表示使用and的各个索引的条件时,该信息表示是从处理结果获取交集
  • using union:表示使用or连接各个使用索引的条件时,该信息表示从处理结果获取并集
  • using temporary:表示使用了临时表存储中间结果。临时表可以是内存临时表和磁盘临时表,执行计划中看不出来。mysql在对查询结果排序时候使用临时表
  • using where:表示存储引擎返回的记录并不是所有的都满足查询条件,需要根据where过滤
  • impossible where:where子句总是false

这里挑几个重要的说明下。

2.9.1 Using filesort

mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作称为“文件排序”。有Using filesort时代表这里可以优化。
这里对列col1,col2,col3建立了联合索引。
(1)执行语句

EXPLAIN select col1 from ta where col1='ac' order by col3

EXPLAIN select col1 from ta where col1='ac' order by col2,col3

(2)执行结果

第一个语句结果:
在这里插入图片描述
第二个语句结果:
在这里插入图片描述

2.9.2 Using temporary

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

EXPLAIN select col1 from ta where col1 in('ac','ab','aa') GROUP BY col2

EXPLAIN select col1 from ta where col1 in('ac','ab','aa') GROUP BY col1,col2

(2)执行结果

第一个语句结果:
在这里插入图片描述
第二个语句结果:
在这里插入图片描述

2.9.3 Using index

表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错。如果同时出现using where,表明索引被用来执行索引键值的查找;。如果没有同时出现using where,表明索引用来读取数据而非执行查找动作。
注意:
如果要使用覆盖索引,一定要注意select列表中只取出需要的列,不可select *,因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降,而且会严重影响修改维护的性能。

(1)执行语句

EXPLAIN select col2 from ta 

EXPLAIN select col2 from ta where col1 = 'ab'

(2)执行结果

第一个语句结果:
在这里插入图片描述
第二个语句结果:
在这里插入图片描述

2.9.4 Using where 与 using join buffer

Using where表明使用了where过滤。using join buffer使用了连接缓存。
(1)执行语句

EXPLAIN select * from t1  JOIN t2  on t1.other_column = t2.other_column

(2)执行结果
在这里插入图片描述

3. SQL优化

(1)尽量全值匹配
查询数据尽量使用条件精确匹配
(2)最佳左前缀法则
如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。
(3)不在索引列上做任何操作
不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
(4)范围条件放最后
存储引擎不能使用索引中范围条件右边的列
(5)尽量使用覆盖索引
(6)不等于要甚用
mysql 在使用不等于(!= 或者<>)的时候无法使用索引会导致全表扫描
(7)Like查询要当心
like以通配符开头(’%abc…’)mysql索引失效会变成全表扫描的操作
(8)字符类型加引号
字符串不加单引号索引失效
(9)OR改UNION效率高
(10)批量插入
提交前关闭自动提交,尽量使用批量insert语句,可以使用MyISAM存储引擎。如果需要大批量导入数据可以使用LOAD DATA INFLIE,比一般的insert语句快20倍。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章