学习使用mysql explain进行性能分析及优化

(未完,待续。。。)

转载并参考自:

http://www.itpub.net/thread-1034410-1-2.html

https://segmentfault.com/a/1190000008131735

https://www.cnblogs.com/yycc/p/7338894.html

http://blog.csdn.net/zly9923218/article/details/51007554

http://blog.csdn.net/SkySuperWL/article/details/52583579

今天想学习,了解mysql explain使用方法,看到上面那几篇文章,觉得作者们写的不错,整理一下,以备以后使用。

先说一个最左前缀的概念,后面会用到这个词:

最左前缀原则:顾名思义是最左优先,以最左边的为起点任何连续的索引都能匹配上,
注:如果第一个字段是范围查询需要单独建一个索引
注:在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。
当创建(a,b,c)复合索引时,想要索引生效的话,只能使用 a和a,b和a,b,c三种组合
第一个实例:以下是常见的几个查询:
mysql>SELECT `a`,`b`,`c` FROM A WHERE `a`='aa' ;
mysql>SELECT `a`,`b`,`c` FROM A WHERE  `b`='bb' AND `c`='cc';
mysql>SELECT `a`,`b`,`c` FROM A WHERE `a`='aa' AND `c`='cc';
请问:想要索引最大化的使用需要至少建几个索引

答:需要建立两个复合索引:a,c、b,c

另一种理解例子:

多列字段做索引,state/city/zipCode,想要索引生效的话,只能使用如下的组合 
state/city/zipCode 
state/city 
state 

其他方式(如city,city/zipCode),则索引不会生效 

有人认为,所谓最左前缀原则就是先要看第一列,在第一列满足的条件下再看左边第二列,以此类推。有位网友描述得很形象: 
可以认为联合索引是闯关游戏的设计
例如这个联合索引是state/city/zipCode
那么state就是第一关 city是第二关, zipCode就是第三关
必须匹配了第一关,才能匹配第二关,匹配了第一关和第二关,才能匹配第三关
不能直接到第二关的
索引的格式就是第一层是state,第二层才是city

一、什么是MYSQL EXPLAIN

        MySQL 提供了一个 EXPLAIN 命令, 它可以对 SELECT 语句进行分析, 并输出 SELECT 执行的详细信息, 以供开发人员针对性优化。EXPLAIN 命令用法十分简单, 在 SELECT 语句前加上 Explain 就可以了, 例如:


为了演示方便,创建两张数据表:

CREATE TABLE `user_info` (
  `id`   BIGINT(20)  NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(50) NOT NULL DEFAULT '',
  `age`  INT(11)              DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `name_index` (`name`)
)
  ENGINE = InnoDB
  DEFAULT CHARSET = utf8
CREATE TABLE `order_info` (
  `id`           BIGINT(20)  NOT NULL AUTO_INCREMENT,
  `user_id`      BIGINT(20)           DEFAULT NULL,
  `product_name` VARCHAR(50) NOT NULL DEFAULT '',
  `productor`    VARCHAR(30)          DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `user_product_detail_index` (`user_id`, `product_name`, `productor`)
)
  ENGINE = InnoDB
  DEFAULT CHARSET = utf8
给这两张表中分别添加测试数据:
INSERT INTO user_info (name, age) VALUES ('xys', 20);
INSERT INTO user_info (name, age) VALUES ('a', 21);
INSERT INTO user_info (name, age) VALUES ('b', 23);
INSERT INTO user_info (name, age) VALUES ('c', 50);
INSERT INTO user_info (name, age) VALUES ('d', 15);
INSERT INTO user_info (name, age) VALUES ('e', 20);
INSERT INTO user_info (name, age) VALUES ('f', 21);
INSERT INTO user_info (name, age) VALUES ('g', 23);
INSERT INTO user_info (name, age) VALUES ('h', 50);
INSERT INTO user_info (name, age) VALUES ('i', 15);
INSERT INTO order_info (user_id, product_name, productor) VALUES (1, 'p1', 'WHH');
INSERT INTO order_info (user_id, product_name, productor) VALUES (1, 'p2', 'WL');
INSERT INTO order_info (user_id, product_name, productor) VALUES (1, 'p1', 'DX');
INSERT INTO order_info (user_id, product_name, productor) VALUES (2, 'p1', 'WHH');
INSERT INTO order_info (user_id, product_name, productor) VALUES (2, 'p5', 'WL');
INSERT INTO order_info (user_id, product_name, productor) VALUES (3, 'p3', 'MA');
INSERT INTO order_info (user_id, product_name, productor) VALUES (4, 'p1', 'WHH');
INSERT INTO order_info (user_id, product_name, productor) VALUES (6, 'p1', 'WHH');
INSERT INTO order_info (user_id, product_name, productor) VALUES (9, 'p8', 'TE');

数据库表如下:

user_info:


order_info:


二、具体列以及参数说明

(1)列说明

从上面的结果可以看到有几列内容,分别介绍:

id:SELECT查询的标识符,每个SELECT都会自动分配一个唯一的标识符。

select_type:SELECT查询的类型。

table:查询的是哪个表。

type:这是重要的一个列,显示了连接使用了何种类型。从最好到最差的连接类型为const、eq_reg、ref、rang、index和All

注意:type显示的是访问类型,是比较重要的一个指标,结果值从好到坏一次是:

system>const>eq_ref>ref>fulltext>ref_or_null>index_merge>unique_subquery>index_subquery>range>index>ALL

一般来说,得保证查询至少达到range级别,最好能达到ref。

possible_keys:此次查询中可能选用的索引

key:此次查询中确切使用到的索引

key_len:使用的索引的长度,在不损失精确度的情况下,长度越短越好

ref:显示索引的哪一列被使用了,如果可能的话,是一个常数

rows:MYSQL认为必须检查的用来返回请求数据的行数,这是一个预估值

filtered:表示此查询条件所过滤的数据的百分比

Extra:额外的信息

(2)重要列和参数说明

下面再详细列一下比较重要的几个字段及各自参数:

select_type

表示了查询的类型,它的主要取值有以下几种:

(1):SIMPLE, 表示此查询不包含 UNION 查询或子查询

(2):PRIMARY, 表示此查询是最外层的查询

(3):UNION, 表示此查询是 UNION 的第二或随后的查询

(4):DEPENDENT UNION, UNION 中的第二个或后面的查询语句, 取决于外面的查询

(5):UNION RESULT, UNION 的结果

(6):SUBQUERY, 子查询中的第一个 SELECT

(7):DEPENDENT SUBQUERY: 子查询中的第一个 SELECT, 取决于外面的查询. 即子查询依赖于外层查询的结果.

table

表示查询涉及的表或衍生表

type

(1)system: 表中只有一条数据. 这个类型是特殊的 const 类型.

(2)const: 针对主键或唯一索引的等值查询扫描, 最多只返回一行数据. const 查询速度非常快, 因为它仅仅读取一次即可.

例如下面的这个查询, 它使用了主键索引, 因此 type 就是 const 类型的.


(3)eq_ref: 此类型通常出现在多表的 join 查询, 表示对于前表的每一个结果, 都只能匹配到后表的一行结果. 并且查询的比较操作通常是 =, 查询效率较高. 例如:


(4)ref: 此类型通常出现在多表的 join 查询, 针对于非唯一或非主键索引, 或者是使用了 最左前缀 规则索引的查询. 

例如下面这个例子中, 就使用到了 ref 类型的查询:


(5)range: 表示使用索引范围查询, 通过索引字段范围获取表中部分数据记录. 这个类型通常出现在 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, IN() 操作中.

当 type 是 range 时, 那么 EXPLAIN 输出的 ref 字段为 NULL, 并且 key_len 字段是此次查询中使用到的索引的最长的那个.


(6)index: 表示全索引扫描(full index scan), 和 ALL 类型类似, 只不过 ALL 类型是全表扫描, 而 index 类型则仅仅扫描所有的索引, 而不扫描数据.

index 类型通常出现在: 所要查询的数据直接在索引树中就可以获取到, 而不需要扫描数据. 当是这种情况时, Extra 字段 会显示 Using index.


(7)ALL表示全表扫描, 这个类型的查询是性能最差的查询之一. 通常来说, 我们的查询不应该出现 ALL 类型的查询, 因为这样的查询在数据量大的情况下, 对数据库的性能是巨大的灾难. 如一个查询是 ALL 类型查询, 那么一般来说可以对相应的字段添加索引来避免.

下面是一个全表扫描的例子, 可以看到, 在全表扫描时, possible_keys 和 key 字段都是 NULL, 表示没有使用到索引, 并且 rows 十分巨大, 因此整个查询效率是十分低下的.


type类型的性能比较

通常来说, 不同的 type 类型的性能关系如下:
ALL < index < range ~ index_merge < ref < eq_ref < const < system
ALL 类型因为是全表扫描, 因此在相同的查询条件下, 它是速度最慢的.
index 类型的查询虽然不是全表扫描, 但是它扫描了所有的索引, 因此比 ALL 类型的稍快.

后面的几种类型都是利用了索引来查询数据, 因此可以过滤部分或大部分数据, 因此查询效率就比较高了.

possible_keys

possible_keys 表示 MySQL 在查询时, 能够使用到的索引. 注意, 即使有些索引在 possible_keys 中出现, 但是并不表示此索引会真正地被 MySQL 使用到. MySQL 在查询时具体使用了哪些索引, 由 key 字段决定.

key

此字段是 MySQL 在当前查询时所真正使用到的索引.

key_len
表示查询优化器使用了索引的字节数. 这个字段可以评估组合索引是否完全被使用, 或只有最左部分字段被使用到.
key_len 的计算规则如下:
    字符串
        char(n): n 字节长度
        varchar(n): 如果是 utf8 编码, 则是 3 n + 2字节; 如果是 utf8mb4 编码, 则是 4 n + 2 字节.
    数值类型:
        TINYINT: 1字节
        SMALLINT: 2字节
        MEDIUMINT: 3字节
        INT: 4字节
        BIGINT: 8字节
    时间类型
        DATE: 3字节
        TIMESTAMP: 4字节
        DATETIME: 8字节

字段属性: NULL 属性 占用一个字节. 如果一个字段是 NOT NULL 的, 则没有此属性.


上面的例子是从表 order_info 中查询指定的内容, 而我们从此表的建表语句中可以知道, 表 order_info 有一个联合索引:

KEY `user_product_detail_index` (`user_id`, `product_name`, `productor`)
不过此查询语句 WHERE user_id < 3 AND product_name = 'p1' AND productor = 'WHH' 中, 因为先进行 user_id 的范围查询, 而根据 最左前缀匹配 原则, 当遇到范围查询时, 就停止索引的匹配, 因此实际上我们使用到的索引的字段只有 user_id, 因此在 EXPLAIN 中, 显示的 key_len 为 9. 因为 user_id 字段是 BIGINT, 占用 8 字节, 而 NULL 属性占用一个字节, 因此总共是 9 个字节. 若我们将user_id 字段改为 BIGINT(20) NOT NULL DEFAULT '0', 则 key_length 应该是8.
上面因为 最左前缀匹配 原则, 我们的查询仅仅使用到了联合索引的 user_id 字段, 因此效率不算高.

Extra

该列包含MYSQL解决查询的详细信息,下面介绍:

(1):Distinct——一旦MYSQL找到了与行相联合匹配的行,就不在搜索了

(2):Not exists——MYSQL优化了LEFT JOIN,一旦它找到了匹配LEFT JOIN标准的行,就不在搜索了

(3):Range checked for each

(4):Using filesort——看到这个的时候,查询就需要优化了。MYSQL需要进行额外的步骤来发现如何对返回的行排序。它根据连接类型以及存储排序键值和匹配条件的全部行的行指针来排序全部行。

(5):Using index——列数据是从仅仅使用了索引中信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候

(6):Using temporary——看到这个的时候,查询需要优化了。这里,MYSQL需要创建一个临时表来存储结果,这通常发生在对不同的列集进行ORDER BY上,而不是GROUP BY上。

(7):Using where——使用了WHERE从句来限制哪些行将与下一张表匹配或者是返回给用户。如果不想返回表中的全部行,并且连接类型ALL或index,这就会发生,或者是查询有问题。


猜你喜欢

转载自blog.csdn.net/ruijiao_ren/article/details/79533363
今日推荐