• MySQL之explain详解


    前言

    开发环境:MySQL5.7.31

    参考的博文:青石路大佬的博文https://www.cnblogs.com/youzhibing/p/11909681.html

    在日常工作中,我们会有时会开慢查询去记录一些执行时间比较久的SQL语句,找出这些SQL语句并不意味着完事了,些时我们常常用到explain这个命令来查看一个这些SQL语句的执行计划,查看该SQL语句有没有使用上了索引,有没有做全表扫描,这都可以通过explain命令来查看。也就是说explain是MySQL的一个命令,用来查看 SQL 的执行计划(SQL 如何执行),根据其输出结果,我们能够知道以下信息:表的读取顺序,数据读取类型,哪些索引可以使用,哪些索引实际使用了,表之间的连接类型,每张表有多少行被优化器查询等信息,根据这些信息,我们可以找出 SQL 慢的原因,并做针对性的优化。

    创建表:

    DROP TABLE IF EXISTS tbl_user;
    CREATE TABLE tbl_user (
      id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主键',
      user_name VARCHAR(50) NOT NULL COMMENT '用户名',
      sex TINYINT(1) NOT NULL COMMENT '性别, 1:男,0:女',
      create_time datetime NOT NULL COMMENT '创建时间',
      update_time datetime NOT NULL COMMENT '更新时间',
        remark VARCHAR(255) NOT NULL DEFAULT '' COMMENT '备注',
      PRIMARY KEY (id)
    ) COMMENT='用户表';
    
    DROP TABLE IF EXISTS tbl_user_login_log;
    CREATE TABLE tbl_user_login_log (
      id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主键',
      user_name VARCHAR(50) NOT NULL COMMENT '用户名',
      ip VARCHAR(15) NOT NULL COMMENT '登录IP',
      client TINYINT(1) NOT NULL COMMENT '登录端, 1:android, 2:ios, 3:PC, 4:H5',
      create_time datetime NOT NULL COMMENT '创建时间',
      PRIMARY KEY (id)
    ) COMMENT='登录日志';
    INSERT INTO tbl_user(user_name,sex,create_time,update_time,remark) VALUES
    ('何天香',1,NOW(), NOW(),'朗眉星目,一表人材'),
    ('薛沉香',0,NOW(), NOW(),'天星楼的总楼主薛摇红的女儿,也是天星楼的少总楼主,体态丰盈,乌发飘逸,指若春葱,袖臂如玉,风姿卓然,高贵典雅,人称“天星绝香”的武林第一大美女'),
    ('慕容兰娟',0,NOW(), NOW(),'武林东南西北四大世家之北世家慕容长明的独生女儿,生得玲珑剔透,粉雕玉琢,脾气却是刚烈无比,又喜着火红,所以人送绰号“火凤凰”,是除天星楼薛沉香之外的武林第二大美女'),
    ('苌婷',0,NOW(), NOW(),'当今皇上最宠爱的侄女,北王府的郡主,腰肢纤细,遍体罗绮,眉若墨画,唇点樱红;虽无沉香之雅重,兰娟之热烈,却别现出一种空灵'),
    ('柳含姻',0,NOW(), NOW(),'武林四绝之一的添愁仙子董婉婉的徒弟,体态窈窕,姿容秀丽,真个是秋水为神玉为骨,芙蓉如面柳如腰,眉若墨画,唇若点樱,不弱西子半分,更胜玉环一筹; 摇红楼、听雨轩,琵琶一曲值千金!'),
    ('李凝雪',0,NOW(), NOW(),'李相国的女儿,神采奕奕,英姿飒爽,爱憎分明'),
    ('周遗梦',0,NOW(), NOW(),'音神传人,湘妃竹琴的拥有者,云髻高盘,穿了一身黑色蝉翼纱衫,愈觉得冰肌玉骨,粉面樱唇,格外娇艳动人'),
    ('叶留痕',0,NOW(), NOW(),'圣域圣女,肤白如雪,白衣飘飘,宛如仙女一般,微笑中带着说不出的柔和之美'),
    ('郭疏影',0,NOW(), NOW(),'扬灰右使的徒弟,秀发细眉,玉肌丰滑,娇润脱俗'),
    ('钟钧天',0,NOW(), NOW(),'天界,玄天九部 - 钧天部的部主,超凡脱俗,仙气逼人'),
    ('王雁云',0,NOW(), NOW(),'尘缘山庄二小姐,刁蛮任性'),
    ('许侍霜',0,NOW(), NOW(),'药王谷谷主女儿,医术高明'),
    ('冯黯凝',0,NOW(), NOW(),'桃花门门主,娇艳如火,千娇百媚');
    INSERT INTO tbl_user_login_log(user_name, ip, client, create_time) VALUES
    ('薛沉香', '10.53.56.78',2, '2019-10-12 12:23:45'),
    ('苌婷', '10.53.56.78',2, '2019-10-12 22:23:45'),
    ('慕容兰娟', '10.53.56.12',1, '2018-08-12 22:23:45'),
    ('何天香', '10.53.56.12',1, '2019-10-19 10:23:45'),
    ('柳含姻', '198.11.132.198',2, '2018-05-12 22:23:45'),
    ('冯黯凝', '198.11.132.198',2, '2018-11-11 22:23:45'),
    ('周遗梦', '198.11.132.198',2, '2019-06-18 22:23:45'),
    ('郭疏影', '220.181.38.148',3, '2019-10-21 09:45:56'),
    ('薛沉香', '220.181.38.148',3, '2019-10-26 22:23:45'),
    ('苌婷', '104.69.160.60',4, '2019-10-12 10:23:45'),
    ('王雁云', '104.69.160.61',4, '2019-10-16 20:23:45'),
    ('李凝雪', '104.69.160.62',4, '2019-10-17 20:23:45'),
    ('许侍霜', '104.69.160.63',4, '2019-10-18 20:23:45'),
    ('叶留痕', '104.69.160.64',4, '2019-10-19 20:23:45'),
    ('王雁云', '104.69.160.65',4, '2019-10-20 20:23:45'),
    ('叶留痕', '104.69.160.66',4, '2019-10-21 20:23:45');
    

    试一试:

    普通的查询语句

    mysql> select * from tbl_user where user_name = "薛沉香";
    +----+-----------+-----+---------------------+---------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    | id | user_name | sex | create_time         | update_time         | remark                                                                                                                                                                                                                |
    +----+-----------+-----+---------------------+---------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    |  2 | 薛沉香    |   0 | 2021-02-13 21:28:32 | 2021-02-13 21:28:32 | 天星楼的总楼主薛摇红的女儿,也是天星楼的少总楼主,体态丰盈,乌发飘逸,指若春葱,袖臂如玉,风姿卓然,高贵典雅,人称“天星绝香”的武林第一大美女                                                                          |
    +----+-----------+-----+---------------------+---------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    1 row in set (0.00 sec)
    

    explain只是在查询语句前加上explain即可:

    mysql> explain select * from tbl_user where user_name = "薛沉香";
    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
    | id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | tbl_user | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   13 |    10.00 | Using where |
    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    

    我们可以看到这个语句返回来的结果似乎并不存在表中,其实这些字段都是执行计划,通过这些字段我们可以判断这条sql语句是如何执行的,我们该如何进行优化。

    主要就是如下字段:

    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
    | id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
    

    接下来逐一看看是什么意思

    explain字段解释

    id(不重要,了解即可)

    选择标识符

    输出的是整数,用来标识整个 SQL 的执行顺序。id 如果相同,从上往下依次执行;id不同,id 值越大,执行优先级越高,越先被执行;如果行引用其他行的并集结果,则该值可以为NULL

    select_type(一般重要,学会几个类型用于面试加分)

    就是select类型,表示查询的类型。

    有以下多种:

    (1) SIMPLE(简单SELECT,不使用UNION或子查询等)

    (2) PRIMARY(子查询中最外层查询,查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY)

    (3) UNION(UNION中的第二个或后面的SELECT语句)

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

    (5) UNION RESULT(UNION的结果,union语句中第二个select开始后面所有select)

    (6) SUBQUERY(子查询中的第一个SELECT,结果不依赖于外部查询)

    (7) DEPENDENT SUBQUERY(子查询中的第一个SELECT,依赖于外部查询)

    (8) DERIVED(派生表的SELECT, FROM子句的子查询)

    (9) UNCACHEABLE SUBQUERY(一个子查询的结果不能被缓存,必须重新评估外链接的第一行)

    SIMPLLE、PRIMARY、SUBQUERY、DERIVED 这 4 个在实际工作中碰到的会比较多,记住这四个含义就行了,其他了解即可

    SIMPLE

    SIMPLE:简单的 SELECT 查询,没有 UNION 或者子查询,包括单表查询或者多表 JOIN 查询

    示例:

    EXPLAIN 
    SELECT t1.*, tu.sex, tu.remark 
    FROM tbl_user_login_log t1 
    LEFT JOIN tbl_user tu 
    ON t1.user_name = tu.user_name;
    

    结果:

    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                              |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
    |  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   16 |   100.00 | NULL                                               |
    |  1 | SIMPLE      | tu    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   13 |   100.00 | Using where; Using join buffer (Block Nested Loop) |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
    2 rows in set, 1 warning (0.00 sec)
    

    说明:

    • select_type列都是SIMPLE,说明是简单的select语句,不使用UNION或子查询等
    • 补充一下id的讲解,这里id都是1,执行就按照从上到下的顺序执行,也就是table列,想执行tl再执行tu

    PRIMARY

    PRIMARY: 最外层的 select 查询,常见于子查询或 UNION 查询 ,最外层的查询被标识为 PRIMARY

    示例:

    EXPLAIN 
    SELECT * FROM tbl_user tu 
    WHERE EXISTS(
      SELECT * FROM tbl_user_login_log tull
      WHERE tull.`user_name` = tu.`user_name`
    );
    

    结果:

    +----+--------------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    | id | select_type        | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
    +----+--------------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    |  1 | PRIMARY            | tu    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   13 |   100.00 | Using where |
    |  2 | DEPENDENT SUBQUERY | tull  | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   16 |    10.00 | Using where |
    +----+--------------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    2 rows in set, 2 warnings (0.00 sec)
    

    说明:

    • 注意id为1的,它是最外层的select,所以是最慢执行的,所以id最小
    • 理解primary是最外层select即可

    UNION

    UNION:UNION 操作的第二个或之后的 SELECT,不依赖于外部查询的结果集(外部查询指的就是 PRIMARY 对应的 SELECT)

    示例:

    EXPLAIN 
    SELECT * FROM tbl_user 
    WHERE user_name = '薛沉香'
    UNION
    SELECT * FROM tbl_user WHERE user_name = '慕容兰娟';
    

    结果:

    +----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
    | id | select_type  | table      | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra           |
    +----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
    |  1 | PRIMARY      | tbl_user   | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   13 |    10.00 | Using where     |
    |  2 | UNION        | tbl_user   | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   13 |    10.00 | Using where     |
    | NULL | UNION RESULT | <union1,2> | NULL       | ALL  | NULL          | NULL | NULL    | NULL | NULL |     NULL | Using temporary |
    +----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
    3 rows in set, 1 warning (0.00 sec)
    

    说明:

    • 简单来说就是若第二个SELECT出现在UNION之后,则被标记为UNION

    DEPENDENT UNION

    DEPENDENT UNION:UNION 操作的第二个或之后的 SELECT,依赖于外部查询的结果集

    示例:

    EXPLAIN 
    SELECT * FROM tbl_user_login_log WHERE user_name IN(
      SELECT user_name FROM tbl_user WHERE id = 2
      UNION
      SELECT user_name FROM tbl_user WHERE id = 3
    );
    

    结果:

    原sql语句的结果:

    +----+--------------+----------------+--------+---------------------+
    | id | user_name    | ip             | client | create_time         |
    +----+--------------+----------------+--------+---------------------+
    |  1 | 薛沉香       | 10.53.56.78    |      2 | 2019-10-12 12:23:45 |
    |  3 | 慕容兰娟     | 10.53.56.12    |      1 | 2018-08-12 22:23:45 |
    |  9 | 薛沉香       | 220.181.38.148 |      3 | 2019-10-26 22:23:45 |
    +----+--------------+----------------+--------+---------------------+
    3 rows in set (0.00 sec)
    

    explain执行计划结果:

    +----+--------------------+--------------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
    | id | select_type        | table              | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra           |
    +----+--------------------+--------------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
    |  1 | PRIMARY            | tbl_user_login_log | NULL       | ALL   | NULL          | NULL    | NULL    | NULL  |   16 |   100.00 | Using where     |
    |  2 | DEPENDENT SUBQUERY | tbl_user           | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL            |
    |  3 | DEPENDENT UNION    | tbl_user           | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL            |
    | NULL | UNION RESULT       | <union2,3>         | NULL       | ALL   | NULL          | NULL    | NULL    | NULL  | NULL |     NULL | Using temporary |
    +----+--------------------+--------------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
    4 rows in set, 1 warning (0.00 sec)
    

    说明:

    • 很明显primary就是指最外层的select,它的优先级最后,id最小
    • id = 3 为DEPENDENT UNION,它是union中第二个查询
    • 依赖于外部查询的结果集就是说取决于外面的查询的意思

    UNION RESULT

    UNION RESULT:UNION 的结果(如果是 UNION ALL 则无此结果)

    说明:

    • UNION和DEPENDENT UNION的例子中我们都能在结果看到UNION RESULT,表示从UNION表获取结果的SELECT

    SUBQUERY

    SUBQUERY:子查询中的第一个 SELECT 查询,不依赖于外部查询的结果集

    示例:

    EXPLAIN 
    SELECT * FROM tbl_user_login_log WHERE user_name = (
      SELECT user_name FROM tbl_user WHERE id = 2
    );
    

    结果:

    +----+-------------+--------------------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
    | id | select_type | table              | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra       |
    +----+-------------+--------------------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
    |  1 | PRIMARY     | tbl_user_login_log | NULL       | ALL   | NULL          | NULL    | NULL    | NULL  |   16 |    10.00 | Using where |
    |  2 | SUBQUERY    | tbl_user           | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL        |
    +----+-------------+--------------------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
    2 rows in set, 1 warning (0.00 sec)
    

    说明:

    • 没啥好说的,看table和id就知道SUBQUERY指代的执行顺序

    DEPENDENT SUBQUERY

    DEPENDENT SUBQUERY:子查询中的第一个select查询,依赖于外部查询的结果集

    示例:

    EXPLAIN 
    SELECT * FROM tbl_user_login_log WHERE user_name IN (
      SELECT user_name FROM tbl_user WHERE id = 2
      UNION ALL
      SELECT user_name FROM tbl_user WHERE id = 3
    );
    

    结果:

    +----+--------------------+--------------------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
    | id | select_type        | table              | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra       |
    +----+--------------------+--------------------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
    |  1 | PRIMARY            | tbl_user_login_log | NULL       | ALL   | NULL          | NULL    | NULL    | NULL  |   16 |   100.00 | Using where |
    |  2 | DEPENDENT SUBQUERY | tbl_user           | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL        |
    |  3 | DEPENDENT UNION    | tbl_user           | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL        |
    +----+--------------------+--------------------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
    3 rows in set, 1 warning (0.00 sec)
    

    说明:

    • DEPENDENT UNION的例子中我们也可以见到DEPENDENT SUBQUERY
    • 就是union中第一个select,因为外部还有select,所以带有个DEPENDENT

    DERIVED

    DERIVED:派生表(临时表),常见于 FROM 子句中有子查询的情况

    示例:

    EXPLAIN 
    SELECT * FROM (
      SELECT * FROM tbl_user WHERE user_name = '薛沉香'
    ) t;
    

    结果:

    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
    | id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | tbl_user | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   13 |    10.00 | Using where |
    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    

    说明:

    • 你一定会奇怪为什么出现的是SIMPLE而不是DERIVED,这是因为MySQL5.7 中对 Derived table 做了一个新特性,该特性允许将符合条件的 Derived table 中的子表与父查询的表合并进行直接JOIN,从而简化了执行计划,同时也提高了执行效率;默认情况下,MySQL5.7 中这个特性是开启的

    • 可通过 SET SESSION optimizer_switch='derived_merge=on|off' 来开启或关闭当前 SESSION 的该特性。

      mysql> SET SESSION optimizer_switch='derived_merge=off';
      Query OK, 0 rows affected (0.00 sec)

      mysql> EXPLAIN
      -> SELECT * FROM (
      -> SELECT * FROM tbl_user WHERE user_name = '薛沉香'
      -> ) t;
      +----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
      | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
      +----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
      | 1 | PRIMARY | | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | NULL |
      | 2 | DERIVED | tbl_user | NULL | ALL | NULL | NULL | NULL | NULL | 13 | 10.00 | Using where |
      +----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
      2 rows in set, 1 warning (0.00 sec)

    table(一般都懂)

    显示该行正在访问哪个表

    partitions(不重要)

    查询进行匹配的分区,对于非分区表,该值为NULL。

    type(很重要)

    这列很重要,显示了连接使用了哪种类别,有无使用索引。下面从性能最好到性能最差排序解析:

    system

    该表只有一行(=系统表),是 const 类型的特例

    const

    确定只有一行匹配的时候,mysql 优化器会在查询前读取它并且只读取一次,速度非常快。用于 primary key 或 unique 索引中有常亮值比较的情形

    mysql> SET SESSION optimizer_switch='derived_merge=off';
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> EXPLAIN SELECT * FROM (SELECT * FROM tbl_user WHERE id = 2) a;
    +----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------+
    | id | select_type | table      | partitions | type   | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
    +----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------+
    |  1 | PRIMARY     | <derived2> | NULL       | system | NULL          | NULL    | NULL    | NULL  |    1 |   100.00 | NULL  |
    |  2 | DERIVED     | tbl_user   | NULL       | const  | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
    +----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------+
    2 rows in set, 1 warning (0.00 sec)
    

    eq_ref

    对于每个来自于前面的表的行,从该表最多只返回一条符合条件的记录。当连接使用的索引是 PRIMARY KEY 或 UNIQUE NOT NULL 索引时使用,非常高效

    mysql> ALTER TABLE tbl_user ADD UNIQUE (user_name);
    Query OK, 0 rows affected (0.02 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    
    mysql> EXPLAIN SELECT * FROM tbl_user_login_log tl
        -> LEFT JOIN tbl_user tu ON tl.user_name = tu.user_name;
    +----+-------------+-------+------------+--------+---------------+-----------+---------+-------------------+------+----------+-------+
    | id | select_type | table | partitions | type   | possible_keys | key       | key_len | ref               | rows | filtered | Extra |
    +----+-------------+-------+------------+--------+---------------+-----------+---------+-------------------+------+----------+-------+
    |  1 | SIMPLE      | tl    | NULL       | ALL    | NULL          | NULL      | NULL    | NULL              |   16 |   100.00 | NULL  |
    |  1 | SIMPLE      | tu    | NULL       | eq_ref | user_name     | user_name | 152     | test.tl.user_name |    1 |   100.00 | NULL  |
    +----+-------------+-------+------------+--------+---------------+-----------+---------+-------------------+------+----------+-------+
    2 rows in set, 1 warning (0.00 sec)
    

    ref

    索引访问,也称索引查找,它返回所有匹配某个单个值的行。此类型通常出现在多表的 JOIN 查询, 针对于非 UNIQUE 或非 PRIMARY KEY, 或者是使用了最左前缀规则索引的查询,换句话说,如果 JOIN 不能基于关键字选择单个行的话,则使用ref

    mysql> ALTER TABLE tbl_user_login_log ADD INDEX idx_user_name (user_name);
    Query OK, 0 rows affected (0.03 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    
    mysql> EXPLAIN SELECT * FROM tbl_user_login_log tl
        -> RIGHT JOIN tbl_user tu ON tl.user_name = tu.user_name;
    +----+-------------+-------+------------+------+---------------+---------------+---------+-------------------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key           | key_len | ref               | rows | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+---------------+---------+-------------------+------+----------+-------+
    |  1 | SIMPLE      | tu    | NULL       | ALL  | NULL          | NULL          | NULL    | NULL              |   13 |   100.00 | NULL  |
    |  1 | SIMPLE      | tl    | NULL       | ref  | idx_user_name | idx_user_name | 152     | test.tu.user_name |    1 |   100.00 | NULL  |
    +----+-------------+-------+------------+------+---------------+---------------+---------+-------------------+------+----------+-------+
    2 rows in set, 1 warning (0.00 sec)
    

    fulltext

    当使用全文索引时会用到,这种索引一般用不到,会用专门的搜索服务(solr、elasticsearch等)来替代

    ref_or_null

    类似ref,但是添加了可以专门搜索 NULL 的行

    ALTER TABLE tbl_user ADD COLUMN weapon VARCHAR(50) COMMENT '武器';
    UPDATE tbl_user SET weapon = '碧玉笛' WHERE user_name = '何天香';
    UPDATE tbl_user SET weapon = '天问剑' WHERE user_name = '薛沉香';
    UPDATE tbl_user SET weapon = '流星剑' WHERE user_name = '慕容兰娟';
    UPDATE tbl_user SET weapon = '相思刀' WHERE user_name = '郭疏影';
    UPDATE tbl_user SET weapon = '湘妃竹琴' WHERE user_name = '周遗梦';
    UPDATE tbl_user SET weapon = '玉琵琶' WHERE user_name = '柳含姻';
    ALTER TABLE tbl_user ADD INDEX idx_weapon (weapon);
    EXPLAIN SELECT * FROM tbl_user WHERE weapon = '湘妃竹琴' OR weapon IS NULL;
    
    +----+-------------+----------+------------+-------------+---------------+------------+---------+-------+------+----------+-----------------------+
    | id | select_type | table    | partitions | type        | possible_keys | key        | key_len | ref   | rows | filtered | Extra                 |
    +----+-------------+----------+------------+-------------+---------------+------------+---------+-------+------+----------+-----------------------+
    |  1 | SIMPLE      | tbl_user | NULL       | ref_or_null | idx_weapon    | idx_weapon | 153     | const |    8 |   100.00 | Using index condition |
    +----+-------------+----------+------------+-------------+---------------+------------+---------+-------+------+----------+-----------------------+
    1 row in set, 1 warning (0.00 sec)
    

    这个是有前提条件的,前提为 weapon 列有索引,且 weapon 列存在 NULL

    index_merge

    该访问类型使用了索引合并优化方法

    mysql> EXPLAIN SELECT * FROM tbl_user WHERE id = 1 OR weapon = '流星剑';
    +----+-------------+----------+------------+-------------+--------------------+--------------------+---------+------+------+----------+----------------------------------------------+
    | id | select_type | table    | partitions | type        | possible_keys      | key                | key_len | ref  | rows | filtered | Extra                                        |
    +----+-------------+----------+------------+-------------+--------------------+--------------------+---------+------+------+----------+----------------------------------------------+
    |  1 | SIMPLE      | tbl_user | NULL       | index_merge | PRIMARY,idx_weapon | PRIMARY,idx_weapon | 4,153   | NULL |    2 |   100.00 | Using union(PRIMARY,idx_weapon); Using where |
    +----+-------------+----------+------------+-------------+--------------------+--------------------+---------+------+------+----------+----------------------------------------------+
    1 row in set, 1 warning (0.01 sec)
    

    这个同样也是有条件的, id 列和 weapon 列都有单列索引。如果出现 index_merge,并且这类 SQL 后期使用较频繁,可以考虑把单列索引换为组合索引,这样效率更高

    unique_subquery

    类似于两表连接中被驱动表的 eq_ref 访问方式,unique_subquery 是针对在一些包含 IN 子查询的查询语句中,如果查询优化器决定将 IN 子查询转换为 EXISTS 子查询,而且子查询可以使用到主键或者唯一索引进行等值匹配时,则会使用 unique_subquery

    mysql> EXPLAIN SELECT * FROM tbl_user t1 WHERE user_name IN (
        -> SELECT user_name FROM tbl_user t2 WHERE t1.weapon = t2.weapon
        -> ) OR weapon = '相思刀';
    +----+--------------------+-------+------------+-----------------+----------------------+-----------+---------+------+------+----------+-------------+
    | id | select_type        | table | partitions | type            | possible_keys        | key       | key_len | ref  | rows | filtered | Extra       |
    +----+--------------------+-------+------------+-----------------+----------------------+-----------+---------+------+------+----------+-------------+
    |  1 | PRIMARY            | t1    | NULL       | ALL             | idx_weapon           | NULL      | NULL    | NULL |   13 |   100.00 | Using where |
    |  2 | DEPENDENT SUBQUERY | t2    | NULL       | unique_subquery | user_name,idx_weapon | user_name | 152     | func |    1 |    10.00 | Using where |
    +----+--------------------+-------+------------+-----------------+----------------------+-----------+---------+------+------+----------+-------------+
    2 rows in set, 2 warnings (0.00 sec)
    

    index_subquery

    index_subquery 与 unique_subquery类似,只不过访问子查询中的表时使用的是普通的索引

    mysql> EXPLAIN SELECT * FROM tbl_user t1 WHERE weapon IN (
        -> SELECT weapon FROM tbl_user t2 WHERE t1.weapon = '玉琵琶'
        -> ) OR weapon = '相思刀';
    +----+--------------------+-------+------------+----------------+---------------+------------+---------+------+------+----------+--------------------------+
    | id | select_type        | table | partitions | type           | possible_keys | key        | key_len | ref  | rows | filtered | Extra                    |
    +----+--------------------+-------+------------+----------------+---------------+------------+---------+------+------+----------+--------------------------+
    |  1 | PRIMARY            | t1    | NULL       | ALL            | idx_weapon    | NULL       | NULL    | NULL |   13 |   100.00 | Using where              |
    |  2 | DEPENDENT SUBQUERY | t2    | NULL       | index_subquery | idx_weapon    | idx_weapon | 153     | func |    1 |   100.00 | Using where; Using index |
    +----+--------------------+-------+------------+----------------+---------------+------------+---------+------+------+----------+--------------------------+
    2 rows in set, 2 warnings (0.00 sec)
    

    range

    使用索引来检索给定范围的行,当使用 =、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN 或者 IN 操作符,用常量比较关键字列时,则会使用 rang

    mysql> EXPLAIN SELECT * FROM tbl_user WHERE id < 6;
    +----+-------------+----------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
    | id | select_type | table    | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+----------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | tbl_user | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL |    5 |   100.00 | Using where |
    +----+-------------+----------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    

    前提是必须基于索引,也就是 id 上必须有索引

    index

    当我们可以使用索引覆盖,但需要扫描全部的索引记录时,则会使用 index;进行统计时非常常见

    mysql> EXPLAIN SELECT COUNT(*) FROM tbl_user;
    +----+-------------+----------+------------+-------+---------------+-----------+---------+------+------+----------+-------------+
    | id | select_type | table    | partitions | type  | possible_keys | key       | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+----------+------------+-------+---------------+-----------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | tbl_user | NULL       | index | NULL          | user_name | 152     | NULL |   13 |   100.00 | Using index |
    +----+-------------+----------+------------+-------+---------------+-----------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    

    all

    全表扫描

    mysql> EXPLAIN SELECT * FROM tbl_user;
    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+
    | id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+
    |  1 | SIMPLE      | tbl_user | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   13 |   100.00 | NULL  |
    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+
    1 row in set, 1 warning (0.00 sec)
    

    possible_keys(了解即可)

    展示在这个 SQL 中,可能用到的索引有哪些,但不一定在查询时使用。若为空则表示没有可以使用的索引,此时可以通过检查 WHERE 语句看是否可以引用某些列或者新建索引来提高性能

    key(重要)

    展示这个 SQL 实际使用的索引,如果没有选择索引,则此列为null,要想强制 MySQL 使用或忽视 possible_keys 列中的索引,在查询中使用 FORCE INDEX、USE INDEX 或者I GNORE INDEX

    key_len(了解即可)

    展示 MySQL 决定使用的索引长度(字节数)。如果 key 是 NULL,则长度为 NULL

    注意:

    • key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的。
    • 在不损失精确性的情况下,长度越短越好

    ref(了解即可)

    显示索引的那一列被使用了

    rows(了解即可)

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

    filtered(不重要)

    展示的是被条件过滤的行数所占需要读到的行(rows 的值)的比例(省略了百分号,显示的是百分号前的值)

    extra(很重要)

    表示不在其他列但也很重要的额外信息。取值有很多,以下是一些常见的

    using index(看见了就安心了)

    表示 SQL 使用了使用覆盖索引,而不用回表去查询数据,性能非常不错

    mysql> EXPLAIN SELECT user_name FROM tbl_user;
    +----+-------------+----------+------------+-------+---------------+-----------+---------+------+------+----------+-------------+
    | id | select_type | table    | partitions | type  | possible_keys | key       | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+----------+------------+-------+---------------+-----------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | tbl_user | NULL       | index | NULL          | user_name | 152     | NULL |   13 |   100.00 | Using index |
    +----+-------------+----------+------------+-------+---------------+-----------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    

    using where

    表示存储引擎搜到记录后进行了后过滤(POST-FILTER),如果查询未能使用索引,using where 的作用只是提醒我们 mysql 要用 where 条件过滤结果集

    mysql> EXPLAIN SELECT * FROM tbl_user WHERE sex = 0;
    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
    | id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | tbl_user | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   13 |    10.00 | Using where |
    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    

    using temporary

    表示 mysql 需要使用临时表来存储结果集,常见于排序和分组查询

    mysql> EXPLAIN SELECT sex, count(*) FROM tbl_user GROUP BY sex;
    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
    | id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                           |
    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
    |  1 | SIMPLE      | tbl_user | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   13 |   100.00 | Using temporary; Using filesort |
    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
    1 row in set, 1 warning (0.00 sec)
    

    using filesort

    表示 mysql 无法利用索引直接完成排序(排序的字段不是索引字段),此时会用到缓冲空间(内存或者磁盘)来进行排序;一般出现该值,则表示 SQL 要进行优化了,它对 CPU 的消耗是比较大的

    mysql> EXPLAIN SELECT * FROM tbl_user ORDER BY sex DESC;
    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+----------------+
    | id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra          |
    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+----------------+
    |  1 | SIMPLE      | tbl_user | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   13 |   100.00 | Using filesort |
    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+----------------+
    1 row in set, 1 warning (0.00 sec)
    

    impossible where

    查询语句的WHERE子句永远为 FALSE 时将会提示该额外信息

    mysql> EXPLAIN SELECT * FROM tbl_user WHERE id IS NULL;
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra            |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+
    |  1 | SIMPLE      | NULL  | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL |     NULL | Impossible WHERE |
    +----+-------------+-------+---------
    

    总结

    • EXPLAIN不会告诉你关于触发器、存储过程的信息或用户自定义函数对查询的影响情况
    • EXPLAIN不考虑各种Cache
    • EXPLAIN不能显示MySQL在执行查询时所作的优化工作
    • 部分统计信息是估算的,并非精确值
    • EXPALIN只能解释SELECT操作,其他操作要重写为SELECT后查看执行计划。

    好东西,explain思维导图:https://www.processon.com/mindmap/6027f4f5f346fb64f565cf90

  • 相关阅读:
    mvc control 请求两次问题
    Jquery跨域获得Json
    使用ajax跨域withCredentials的作用
    以下是关于ASP.NET中保存各种信息的对象的比较,理解这些对象的原理,对制作完善的程序来说是相当有必要的(摘至互联网,并非原创--xukunping)
    【算法】字符串近似搜索(转)
    字符串相似度算法(编辑距离算法 Levenshtein Distance)(转)
    图像相似度算法的C#实现及测评
    求时间差的sql语句。 比如如下数据
    微信公众平台开发之微信access_token如何有效长期保存
    PowerDesigner打开设计文件后提示failed to read the fileXXX的解决办法
  • 原文地址:https://www.cnblogs.com/kylinxxx/p/14403135.html
Copyright © 2020-2023  润新知