暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MySQL - EXPLAIN 执行计划

平平无奇一码农 2021-06-21
492

MySQL 提供 EXPLAIN 命令分析 SQL 执行计划,用法很简单,加载 SQL 前面即可。MySQL 5.6 版本之前,EXPLAIN 只支持 SELECT 查询,自 5.6 版本开始支持 DML 语句,即 UPDATE、DELETE、INSERT。

构造示例数据:

    CREATE TABLE `user` (
    `uid` int(11) NOT NULL AUTO_INCREMENT,
    `uname` varchar(20) NOT NULL,
    `role_id` int(11) NOT NULL,
    PRIMARY KEY (`uid`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


    CREATE TABLE `role` (
    `rid` int(11) NOT NULL AUTO_INCREMENT,
    `rname` varchar(20) NOT NULL,
    PRIMARY KEY (`rid`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


    INSERT INTO role VALUES(1,'管理员');
    INSERT INTO role VALUES(2,'老师');
    INSERT INTO role VALUES(3,'学生');

    INSERT INTO user VALUES (1,'Admin',1);
    INSERT INTO user VALUES (2,'饕餮',2);
    INSERT INTO user VALUES (3,'混沌',2);
    INSERT INTO user VALUES (4,'梼杌',2);
    INSERT INTO user VALUES (5,'穷奇',2);
    INSERT INTO user VALUES (6,'青龙',3);
    INSERT INTO user VALUES (7,'白虎',3);
    INSERT INTO user VALUES (8,'朱雀',3);
    INSERT INTO user VALUES (9,'玄武',3);
    复制

    EXPLAIN 使用示例:

      EXPLAIN
      SELECT
      b.rname,
      COUNT(*)
      FROM
      user a,
      role b
      WHERE
      a.role_id = b.rid
      GROUP BY
      b.rname;
      复制

      创建索引后 EXPLAIN 使用示例:

        CREATE INDEX index1 ON role(rid,rname);
        CREATE INDEX index2 ON role(rname,rid);




        CREATE INDEX index1 ON user(role_id,uid);
        CREATE INDEX index2 ON user(uid,role_id);


        EXPLAIN
        SELECT
        b.rname,
        COUNT(*)
        FROM
        user a,
        role b
        WHERE
        a.role_id = b.rid
        GROUP BY
        b.rname;
        复制


        索引添加前后执行计划有明显变化,EXPLAIN 命令输出一共有 12 个字段,如下:


        id

        该语句的唯一标识。如果 EXPLAIN 的结果包括多个 id 值,则数字越大越先执行;而对于相同 id 的行,则表示从上往下依次执行。

          EXPLAIN 
          SELECT uname FROM user WHERE role_id = (SELECT rid FROM role WHERE rname = '管理员')
          UNION
          SELECT uname FROM user WHERE role_id = (SELECT rid FROM role WHERE rname = '学生')
          复制

          select_type

          table

          该列显示对应行正在访问的表(有别名就显示别名),它也可能是以下显示的值:

          • <unionM,N>
            : 这一行代表着 id 为 M 和 N 的查询的关联结果。

          • <derivedN>
            : 该派生表取值于 id 为 N 的的查询结果。例如,派生表可能来自于 FROM 子句的子查询。

          • <subqueryN>
            : 这一行来自于 id 为 N 的查询的物化子查询的查询结果。

          partitions

          该列显示分区表命中的分区情况,非分区表该字段为空(NULL)。

          type

          该列称为关联类型或者访问类型,它指明了MySQL决定如何查找表中符合条件的行,同时是我们判断查询是否高效的重要依据。

          • ALL
            :全表扫描,这个类型是性能最差的查询之一。通常来说,我们的查询不应该出现 ALL 类型,因为这样的查询,在数据量最大的情况下,对数据库的性能是巨大的灾难。如果在查询里使用了 LIMIT N,即使 type 依然是 ALL,但是只需要扫描到符合条件的前 N 行数据,就会停止继续扫描。

          • index
            :全索引扫描,和 ALL 类型类似,只不过 ALL 类型是全表扫描,而 index 类型是扫描全部的索引,主要优点是避免了排序,但是开销仍然非常大。如果在 Extra 列看到 Using index,说明正在使用覆盖索引,只扫描索引的数据,它比按索引次序全表扫描的开销要少很多。

          • range
            :范围扫描,就是一个有限制的索引扫描,它开始于索引里的某一点,返回匹配这个值域的行,range 比全索引扫描更高效,因为它不需要遍历全部索引。这个类型通常出现在 =、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN、IN() 的操作中,key 列显示使用了哪个索引,当 type 为该值时,则输出的 ref 列为 NULL,并且 key_len 列是此次查询中使用到的索引最长的那个。

          • ref
            :一种索引访问,也称索引查找,它返回所有匹配某个单个值的行。此类型通常出现在多表的 JOIN 查询, 针对于非唯一或非主键索引, 或者是使用了最左前缀规则索引的查询。

          • ref_or_null
            ref_or_null  ref 类似,但是 MySQL 必须对包含 NULL 值的行进行行额外搜索。

          • eq_ref
            :使用这种索引查找,最多只返回一条符合条件的记录。在使用唯一性索引或主键查找时会出现该值,非常高效。

          • index_subquery
            :index_subquery 替换了以下形式的子查询中的 eq_ref 访问类型,其中 key_column 是非唯一索引。

            value IN (SELECT key_column FROM table)
            复制
            • unique_subquery
              :unique_subquery 跟 index_subquery 类似,它替换了以下形式的子查询中的 eq_ref 访问类型,其中 primary_key 可以是主键索引或唯一索引。

              value IN (SELECT primary_key FROM table)
              复制
              • index_merge
                :表示出现了索引合并优化,通常是将多个索引字段的范围扫描合并为一个。包括单表中多个索引的交集,并集以及交集之间的并集,但不包括跨多张表和全文索引。

              • const
                :MySQL 知道查询最多只能匹配到一条符合条件的记录。因为只有一行,所以优化器可以将这一行中的列中的值视为常量。const 表查询非常快,因为它们只读取一次数据行。通常使用主键或唯一索引进行等值条件查询时会用 const。

              • system
                :该表只有一行(系统表),是 const 关联类型的特例。

              • NULL
                :在执行阶段不需要访问表。

              • fulltext
                :命中全文索引时 type 为 fulltext。

              possible_keys

              该列显示查询可能使用哪些索引来查找。

              key

              该列显示实际使用的索引,如果没有选择索引,值为 NULL。

              key_len

              该列显示实际使用的索引的字节长度,观察 key_len 可以让你知道 MySQL 实际上使用了一个联合索引的多少个字段。

              ref

              该列显示哪些字段或者常量被用来和 key 配合从表中查询记录出来。

              rows

              该列显示估计要找到所需的行而要读取的行数,这是个估计值,原则上值越小越好。

              filtered

              该列显示存储引擎返回的数据在 Server 层过滤后,剩下多少满足查询的记录数量的比例,注意是百分比,不是具体记录数。用 rows × filtered 可获得和下一张表连接的行数。例如 rows = 1000,filtered = 50%,则和下一张表连接的行数是 500。

              Extra

              该列显示执行查询时的额外信息,常见的取值如下:

              • Using index
                :使用覆盖索引,表示查询索引就可查到所需数据,不用扫描表数据文件,往往说明性能不错。

              • Using Where
                :在存储引擎检索行后再进行过滤,使用了 WHERE 从句来限制哪些行将与下一张表匹配或者是返回给用户。

              • Using temporary
                :在查询结果排序时会使用一个临时表,一般出现于排序、分组和多表 JOIN 的情况,查询效率不高,建议优化。

              • Using filesort
                :对结果使用一个外部索引排序,而不是按索引次序从表里读取行,一般有出现该值,都建议优化去掉,因为这样的查询 CPU 资源消耗大。

              • ...... 还有不少,不一一说明,如果想查询尽可能快,多关注 Using filesort 和 Using temporary。

              SHOW WARNINGS

              在 EXPLAIN 语句后紧跟 SHOW WARNINGS
               命令可以看到MySQL优化器优化查询语句的结果。

                EXPLAIN 
                SELECT uname FROM user WHERE role_id = (SELECT rid FROM role WHERE rname = '管理员')
                UNION
                SELECT uname FROM user WHERE role_id = (SELECT rid FROM role WHERE rname = '学生');
                SHOW WARNINGS;


                Message:
                /* select#1 */ select `DB`.`user`.`uname` AS `uname` from `DB`.`user`
                where (`DB`.`user`.`role_id` = (/* select#2 */ select `DB`.`role`.`rid` from `DB`.`role` where (`DB`.`role`.`rname` = '管理员')))
                union
                /* select#3 */ select `DB`.`user`.`uname` AS `uname` from `DB`.`user`
                where (`DB`.`user`.`role_id` = (/* select#4 */ select `DB`.`role`.`rid` from `DB`.`role` where (`DB`.`role`.`rname` = '学生')))
                复制



                公众号文章同步github。

                本文地址github.com/lazecoding/Note/blob/main/note/articles/mysql/EXPLAIN.md

                文章转载自平平无奇一码农,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                评论