MySQL 性能优化——「Explain 分析实践」

本文基于MySQL官方5.7版本的 MySQL 5.7 Reference Document.

DESC、DESCRIBE和EXPLAIN这三个关键字都是我们常用的,但是使用场景不同。从MySQL解析器的角度而言,他们的实际用法是一致的。
即实际使用过程中 DESC 等价于 DESCRIBE 等价于 EXPLAIN 。我们常常用于以下两种场景:

  1. 查看表结构信息
  2. 查看SQL语句执行计划

数据准备

建立两张数据表,用于后续的测试调用。

CREATE TABLE `department` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(50) NOT NULL COMMENT '部门名称',
  `depart_desc` varchar(50) DEFAULT NULL COMMENT '描述',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of department
-- ----------------------------
INSERT INTO `department` VALUES ('1', 'IT部', '程序猿');
INSERT INTO `department` VALUES ('2', '人事部', '很多美女');
INSERT INTO `department` VALUES ('3', '施工部', '糙汉子');
INSERT INTO `department` VALUES ('4', '财务部', '千万别惹他们!');
INSERT INTO `department` VALUES ('5', '行政部', '生活大管家');

-- ----------------------------
-- Table structure for user
-- ----------------------------
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  `age` int(11) DEFAULT NULL,
  `depart_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `user_index_1` (`name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('2', 'Jack', '20', '1');
INSERT INTO `user` VALUES ('3', 'Mike', '25', '1');
INSERT INTO `user` VALUES ('4', 'Lucy', '22', '2');
INSERT INTO `user` VALUES ('5', 'Mircle', '30', '3');
INSERT INTO `user` VALUES ('6', 'Josn', '45', '4');
INSERT INTO `user` VALUES ('7', 'Sgodon', '30', '5');

查看表结构信息

1. 执行语法

{EXPLAIN | DESCRIBE | DESC}
    tbl_name [col_name | wild]

2. 调用实例


mysql> DESC user;
+-----------+-------------+------+-----+---------+----------------+
| Field     | Type        | Null | Key | Default | Extra          |
+-----------+-------------+------+-----+---------+----------------+
| id        | int(11)     | NO   | PRI | NULL    | auto_increment |
| name      | varchar(50) | NO   | MUL | NULL    |                |
| age       | int(11)     | YES  |     | NULL    |                |
| depart_id | int(11)     | YES  |     | NULL    |                |
+-----------+-------------+------+-----+---------+----------------+
4 rows in set (0.02 sec)
-----------------------------------------------------------------
mysql> EXPLAIN user;
+-----------+-------------+------+-----+---------+----------------+
| Field     | Type        | Null | Key | Default | Extra          |
+-----------+-------------+------+-----+---------+----------------+
| id        | int(11)     | NO   | PRI | NULL    | auto_increment |
| name      | varchar(50) | NO   | MUL | NULL    |                |
| age       | int(11)     | YES  |     | NULL    |                |
| depart_id | int(11)     | YES  |     | NULL    |                |
+-----------+-------------+------+-----+---------+----------------+
4 rows in set (0.02 sec)

使用EXPLAIN和DESC的效果是一样的,相类似的语法有 SHOW CREATE TABLE.

查看SQL语句执行计划

1.执行语法

{EXPLAIN | DESCRIBE | DESC}
    [explain_type]
    {explainable_stmt | FOR CONNECTION connection_id}

explain_type: {
    EXTENDED
  | PARTITIONS
  | FORMAT = format_name
}

format_name: {
    TRADITIONAL
  | JSON
}

explainable_stmt: {
    SELECT statement
  | DELETE statement
  | INSERT statement
  | REPLACE statement
  | UPDATE statement
}

2.调用示例

mysql> EXPLAIN SELECT * FROM user WHERE id = 4 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: user
         type: const
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: const
         rows: 1
        Extra: NULL
1 row in set (0.00 sec)

#试一下用法-EXTENDED:多了一列 filtered
mysql> EXPLAIN EXTENDED SELECT * FROM user WHERE id= 4 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: user
         type: const
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: const
         rows: 1
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)

#试一下用法-PARTITIONS:多了一列partitions
mysql> EXPLAIN PARTITIONS SELECT * FROM user WHERE id=4 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: user
   partitions: NULL
         type: const
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: const
         rows: 1
        Extra: NULL
1 row in set (0.00 sec)

#试一下用法-FORMAT = JSON:默认就为Traditional
mysql> EXPLAIN FORMAT = JSON SELECT id FROM user WHERE id = 4 \G
*************************** 1. row ***************************
EXPLAIN: {
  "query_block": {
    "select_id": 1,
    "table": {
      "table_name": "user",
      "access_type": "const",
      "possible_keys": [
        "PRIMARY"
      ],
      "key": "PRIMARY",
      "used_key_parts": [
        "id"
      ],
      "key_length": "4",
      "ref": [
        "const"
      ],
      "rows": 1,
      "filtered": 100,
      "using_index": true
    }
  }
}

3.EXPLAIN输出格式

mysql> EXPLAIN SELECT * FROM user WHERE id = 4 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: user
         type: const
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: const
         rows: 1
        Extra: NULL
1 row in set (0.00 sec)
  1. id:SELECT的唯一标识,这是查询中SELECT的序号,唯一。
  2. select_type:查询类型
  3. table:查询的表
  4. partitions:查询调用到的分区信息
  5. type:Join类型
  6. possible_keys:指示了MySQL可以选择的索引,但是有可能不会使用
  7. key:MySQL真正使用的索引
  8. key_len:MySQL决定用的索引的长度,和key对应
  9. ref:将哪些列或常量与key列中命名的索引进行比较
  10. rows:执行查询需要扫描的行数,预估值
  11. filtered:将被表条件过滤掉数据的百分比的预估值。
  12. Extra:额外信息

select_type

  • SIMPLE:表示此查询不包含 UNION 查询或子查询
  • PRIMARY: 表示此查询是最外层的查询
  • UNION:表示此查询是 UNION 的第二或随后的查询
  • DEPENDENT UNION: UNION 中的第二个或后面的查询语句, 取决于外面的查询
  • UNION RESULT: UNION 的结果
  • SUBQUERY:子查询中的第一个 SELECT
  • DEPENDENT SUBQUERY: 子查询中的第一个 SELECT, 取决于外面的查询 即子查询依赖于外层查询的结果

大部分的查询都是SIMPLE,没有子查询和UNION连接。我们试一下 子查询和UNION

#使用子查询输出结果如下:
mysql> EXPLAIN SELECT name,age FROM user WHERE depart_id = (SELECT id FROM department WHERE name='人事部门')\G
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: user
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 6
        Extra: Using where
*************************** 2. row ***************************
           id: 2
  select_type: SUBQUERY
        table: department
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 5
        Extra: Using where
2 rows in set (0.00 sec)
----------------------------------------------------------------------------
#使用Union查询输出结果如下:
#union<1,2> 和使用union语句是相对应的,1、2指的是查询ID的序号
mysql> EXPLAIN SELECT name,age FROM user WHERE depart_id = 1 UNION SELECT name,age FROM user WHERE depart_id=2 \G
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: user
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 6
        Extra: Using where
*************************** 2. row ***************************
           id: 2
  select_type: UNION
        table: user
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 6
        Extra: Using where
*************************** 3. row ***************************
           id: NULL
  select_type: UNION RESULT
        table: 
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: NULL
        Extra: Using temporary
3 rows in set (0.00 sec)

table

有以下几种形式:
-

mysql> EXPLAIN SELECT u.name,u.age FROM (SELECT * FROM user WHERE depart_id=2) u \G
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: 
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 6
        Extra: NULL
*************************** 2. row ***************************
           id: 2
  select_type: DERIVED
        table: user
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 6
        Extra: Using where
2 rows in set (0.00 sec)

type

  • system: 表中只有一条数据. 这个类型是特殊的 const 类型。
# full_3 只有一条数据,但是调用全表查询时还是使用的ALL  

mysql> SELECT * FROM full_3;
+----+------+------+-------------+
| id | pkid | name | create_time |
+----+------+------+-------------+
|  1 |    1 | 3    | NULL        |
+----+------+------+-------------+
1 row in set (0.00 sec)

mysql> EXPLAIN SELECT * FROM full_3 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: full_3
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1
        Extra: NULL
1 row in set (0.00 sec)

# 通过这样的子查询时可以的
mysql> EXPLAIN SELECT d.* FROM (SELECT * FROM full_3 fu WHERE fu.id=1) d \G
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: 
         type: system
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1
        Extra: NULL
*************************** 2. row ***************************
           id: 2
  select_type: DERIVED
        table: fu
         type: const
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: const
         rows: 1
        Extra: NULL
2 rows in set (0.00 sec)

  • const: 针对主键或唯一索引的等值查询扫描, 最多只返回一行数据. const 查询速度非常快, 因为它仅仅读取一次即可。
mysql> EXPLAIN SELECT * FROM user WHERE id =3 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: user
         type: const
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: const
         rows: 1
        Extra: NULL
1 row in set (0.00 sec)
  • eq_ref: 此类型通常出现在多表的 join 查询, 表示对于前表的每一个结果, 都只能匹配到后表的一行结果. 并且查询的比较操作通常是 =, 查询效率较高.

  • ref: 此类型通常出现在多表的 join 查询, 针对于非唯一或非主键索引, 或者是使用了 最左前缀 规则索引的查询.
    例如下面这个例子中, 就使用到了 ref 类型的查询:

mysql> EXPLAIN SELECT name FROM user WHERE name = 'Jack' \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: user
         type: ref
possible_keys: user_index_1
          key: user_index_1
      key_len: 152
          ref: const
         rows: 1
        Extra: Using where; Using index
1 row in set (0.00 sec)
  • range: 表示使用索引范围查询, 通过索引字段范围获取表中部分数据记录. 这个类型通常出现在 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, IN() 操作中.
    当 type 是 range 时, 那么 EXPLAIN 输出的 ref 字段为 NULL, 并且 key_len 字段是此次查询中使用到的索引的最长的那个.
mysql> EXPLAIN SELECT * FROM user WHERE name LIKE 'Jack'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: user
         type: range
possible_keys: user_index_1
          key: user_index_1
      key_len: 152
          ref: NULL
         rows: 1
        Extra: Using index condition
1 row in set (0.00 sec)
  • index: 表示全索引扫描(full index scan), 和 ALL 类型类似, 只不过 ALL 类型是全表扫描, 而 index 类型则仅仅扫描所有的索引, 而不扫描数据.
    index 类型通常出现在: 所要查询的数据直接在索引树中就可以获取到, 而不需要扫描数据. 当是这种情况时, Extra 字段 会显示 Using index.
#如果用=就变成REF
mysql> EXPLAIN SELECT name FROM user WHERE name != 'Jack' \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: user
         type: index
possible_keys: user_index_1
          key: user_index_1
      key_len: 152
          ref: NULL
         rows: 6
        Extra: Using where; Using index
1 row in set (0.00 sec)
  • ALL: 表示全表扫描, 这个类型的查询是性能最差的查询之一. 通常来说, 我们的查询不应该出现 ALL 类型的查询, 因为这样的查询在数据量大的情况下, 对数据库的性能是巨大的灾难. 如一个查询是 ALL 类型查询, 那么一般来说可以对相应的字段添加索引来避免.
    下面是一个全表扫描的例子, 可以看到, 在全表扫描时, possible_keys 和 key 字段都是 NULL, 表示没有使用到索引, 并且 rows 十分巨大, 因此整个查询效率是十分低下的.
mysql> EXPLAIN SELECT name FROM user WHERE age = 1 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: user
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 6
        Extra: Using where
1 row in set (0.00 sec)

possible_keys

possibel_keys表示MySQL在查询过程中能够用到的索引,但是不一定会使用。具体使用的索引,由Key决定。

key

key这一列是MySQL真正使用的索引。

ken_len

key_len 表示查询优化器使用了索引的字节数. 这个字段可以评估组合索引是否完全被使用, 或只有最左部分字段被使用到。

rows

rows列显示MySQL执行查询预计会覆盖的行数,这个值越小越好。

Extra

有很多额外信息,举三个例子简单说明下。

Impossible WHERE noticed after reading const tables

预示着Where列有逻辑问题,可能一直是FALSE,无法获取数据。例如:

mysql> EXPLAIN SELECT * FROM user WHERE id >3 AND id<2 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: NULL
         type: NULL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: NULL
        Extra: Impossible WHERE noticed after reading const tables
1 row in set (0.00 sec)

Using filesort

查询结果进行排序,例如:

mysql> EXPLAIN SELECT * FROM user WHERE id >3 ORDER By age \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: user
         type: range
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 4
        Extra: Using where; Using filesort
1 row in set (0.00 sec)

Using index

覆盖索引,从辅助索引中就能够得到需要的数据,而不需要查询聚集索引中的记录。

mysql> EXPLAIN SELECT name FROM user WHERE name LIKE 'I%' \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: user
         type: range
possible_keys: user_index_1
          key: user_index_1
      key_len: 152
          ref: NULL
         rows: 1
        Extra: Using where; Using index
1 row in set (0.00 sec)

你可能感兴趣的:(mysql)