• 数据库系统原理与应用教程(038)—— MySQL 的索引(四):使用 EXPLAIN 命令分析索引


    数据库系统原理与应用教程(038)—— MySQL 的索引(四):使用 EXPLAIN 命令分析索引

    在查询语句中使用 explain 关键字,可以查看索引是否被使用,有没有做全表扫描,并且显示索引的相关信息。

    EXPLAIN 命令的语法格式如下:

    explain select 语句;
    
    • 1

    一、准备数据

    (1)创建两张表

    /*
    create table dept(
        dept_id int primary key,
        dept_name char(20)
    );
    
    insert into dept 
    values(11,'销售部'),(12,'财务部'),(13,'生产部'),(14,'人事部'),(15,'技术部');
    
    create table emp(
        e_id int primary key auto_increment,
        e_name char(20),
        salary int,
        dept_id int,
        foreign key(dept_id) references dept(dept_id)
    );
    */
    mysql> create table dept(
        ->     dept_id int primary key,
        ->     dept_name char(20)
        -> );
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> insert into dept 
        -> values(11,'销售部'),(12,'财务部'),(13,'生产部'),(14,'人事部'),(15,'技术部');
    Query OK, 5 rows affected (0.00 sec)
    Records: 5  Duplicates: 0  Warnings: 0
    
    mysql> create table emp(
        ->     e_id int primary key auto_increment,
        ->     e_name char(20),
        ->     salary int,
        ->     dept_id int,
        ->     foreign key(dept_id) references dept(dept_id)
        -> );
    Query OK, 0 rows affected (0.01 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    (2)在 emp 表中生成大量数据

    /*
    insert into emp(e_name, salary, dept_id) values('张',5200,null);
    insert into emp(e_name, salary, dept_id) values('王',5800,null);
    insert into emp(e_name, salary, dept_id) values('李',6600,null);
    insert into emp(e_name, salary, dept_id) values('赵',8900,null);
    insert into emp(e_name, salary, dept_id) values('刘',7100,null);
    */
    
    mysql> select * from emp;
    +------+--------+--------+---------+
    | e_id | e_name | salary | dept_id |
    +------+--------+--------+---------+
    |    1 | 张     |   5200 |    NULL |
    |    2 | 王     |   5800 |    NULL |
    |    3 | 李     |   6600 |    NULL |
    |    4 | 赵     |   8900 |    NULL |
    |    5 | 刘     |   7100 |    NULL |
    +------+--------+--------+---------+
    5 rows in set (0.00 sec)
    
    /*
    insert into emp(e_name, salary, dept_id) 
    select concat(left(e_name,2),round(rand()*1000000,0)),
    round(4000+rand()*16000,0),dept_id
    from emp;
    */
    -- 反复执行以上命令,生成 500 万条记录,结果如下:
    mysql> select count(e_id) from emp;
    +-------------+
    | count(e_id) |
    +-------------+
    |     5242880 |
    +-------------+
    1 row in set (0.35 sec)
    
    -- 更新 emp 表中的 dept_id 列
    update emp set dept_id = 11 where e_id <= 10;
    update emp set dept_id = 12 where e_id <= 20 and e_id > 10;
    update emp set dept_id = 13 where e_id <= 30 and e_id > 20;
    update emp set dept_id = 14 where e_id <= 40 and e_id > 30;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    二、EXPLAIN 命令各输出项的含义

    执行 EXPLAIN 命令如下:

    mysql> EXPLAIN select e_name from emp where e_id = 1452355\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    以上输出结果中的各输出项含义为:

    /*
    (1) id:SELECT 标识符
    (2) select_type:SELECT 查询的类型
    (3) table:数据表的名字
    (4) partitions:匹配的分区
    (5) type:访问表的方式
    (6) possible_keys:查询时可能使用的索引
    (7) key:实际使用的索引
    (8) key_len:索引字段的长度
    (9) ref:连接查询时,用于显示关联的字段
    (10) rows:需要扫描的行数(估算的行数)
    (11) filtered:按条件过滤后查询到的记录的百分比
    (12) Extra:执行情况的描述和说明
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    下面对各个输出项进行详细介绍。

    1、id

    SELECT 标识符,是SELECT 查询的序列号。如果一个 select 语句包含子查询或者包含 union 运算等会有多个 ID。此时,id 用于表示查询中执行 select 子句或操作表的顺序,id 相同,执行顺序从上到下,id 不同,id 值越大则执行的优先级越高。例如:

    (1)查询涉及到多张表

    /*
    explain
    select e_id, e_name, salary, dept_name 
    from emp e, dept d
    where e.dept_id = d.dept_id and e_id = 1\G
    */
    mysql> explain
        -> select e_id, e_name, salary, dept_name 
        -> from emp e, dept d
        -> where e.dept_id = d.dept_id and e_id = 1\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: e
      ....
    *************************** 2. row ***************************
               id: 1
      select_type: SIMPLE
            table: d
      
      ....
    2 rows in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    (2)使用 union 连接两个查询

    /*
    explain
    select * from emp where e_id = 1  union
    select * from emp where e_id = 2\G
    */
    mysql> explain
        -> select * from emp where e_id = 1  union
        -> select * from emp where e_id = 2\G
    *************************** 1. row ***************************
               id: 1
      select_type: PRIMARY
            table: emp
      ....
    *************************** 2. row ***************************
               id: 2
      select_type: UNION
            table: emp
      ....
    *************************** 3. row ***************************
               id: NULL
      select_type: UNION RESULT
            table: 
      ....
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    (3)使用子查询

    /*
    explain
    select * from emp 
    where dept_id = (
        select dept_id from dept where dept_name = '技术部'
    )\G
    */
    mysql> explain
        -> select * from emp 
        -> where dept_id = (
        ->     select dept_id from dept where dept_name = '技术部'
        -> )\G
    *************************** 1. row ***************************
               id: 1
      select_type: PRIMARY
            table: emp
      ....
    *************************** 2. row ***************************
               id: 2
      select_type: SUBQUERY
            table: dept
      ....
    2 rows in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    2、select_type

    表示查询中每个 select 子句的类型。有以下几种类型:

    (1)SIMPLE(简单的 select 查询,不包含子查询或 union 查询)

    mysql> explain select * from dept where dept_id=11\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: dept
      ....
    1 row in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (2)PRIMARY(子查询中最外层查询)

    /*
    explain
    select * from emp 
    where dept_id = (
        select dept_id from dept where dept_name = '技术部'
    )\G
    */
    mysql> explain
        -> select * from emp 
        -> where dept_id = (
        ->     select dept_id from dept where dept_name = '技术部'
        -> )\G
    *************************** 1. row ***************************
               id: 1
      select_type: PRIMARY
            table: emp
      .....
    *************************** 2. row ***************************
               id: 2
      select_type: SUBQUERY
            table: dept
      ....
    2 rows in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

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

    mysql> explain
        -> select * from emp 
        -> where dept_id = (
        ->     select dept_id from dept where dept_name = '技术部'
        -> )\G
    *************************** 1. row ***************************
               id: 1
      select_type: PRIMARY
            table: emp
      .....
    *************************** 2. row ***************************
               id: 2
      select_type: SUBQUERY
            table: dept
      ....
    2 rows in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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

    /*
    explain
    select * from dept 
    where exists (
        select * from emp where emp.dept_id = dept.dept_id
    )\G
    */
    mysql> explain
        -> select * from dept 
        -> where exists (
        ->     select * from emp where emp.dept_id = dept.dept_id
        -> )\G
    *************************** 1. row ***************************
               id: 1
      select_type: PRIMARY
            table: dept
      ....
    *************************** 2. row ***************************
               id: 2
      select_type: DEPENDENT SUBQUERY
            table: emp
      ....
    2 rows in set, 2 warnings (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

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

    /*
    explain
    select * from emp where e_id = 1  union
    select * from emp where e_id = 2\G
    */
    mysql> explain
        -> select * from emp where e_id = 1  union
        -> select * from emp where e_id = 2\G
    *************************** 1. row ***************************
               id: 1
      select_type: PRIMARY
            table: emp
      ....
    *************************** 2. row ***************************
               id: 2
      select_type: UNION
            table: emp
      ....
    *************************** 3. row ***************************
               id: NULL
      select_type: UNION RESULT
            table: 
      ....
    3 rows in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

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

    /*
    explain
    select * from emp where e_id = 1  union
    select * from emp where e_id = 2\G
    */
    mysql> explain
        -> select * from emp where e_id = 1  union
        -> select * from emp where e_id = 2\G
    *************************** 1. row ***************************
               id: 1
      select_type: PRIMARY
            table: emp
      ....
    *************************** 2. row ***************************
               id: 2
      select_type: UNION
            table: emp
      ....
    *************************** 3. row ***************************
               id: NULL
      select_type: UNION RESULT
            table: 
      ....
    3 rows in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    3、table

    查询所用的表名称,可能是别名。

    /*
    explain
    select * from dept 
    where exists (
        select * from emp where emp.dept_id = dept.dept_id
    )\G
    */
    mysql> explain
        -> select * from dept 
        -> where exists (
        ->     select * from emp where emp.dept_id = dept.dept_id
        -> )\G
    *************************** 1. row ***************************
               id: 1
      select_type: PRIMARY
            table: dept
      ....
    *************************** 2. row ***************************
               id: 2
      select_type: DEPENDENT SUBQUERY
            table: emp
      ....
    2 rows in set, 2 warnings (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    4、type

    访问表的方式,表示 MySQL 在表中找到所需行的方式。常用的类型有: ALL、index、range、 ref、eq_ref、const、system、NULL(从左到右,性能从差到好)。

    emp 表的索引情况如下:

    mysql> show index from emp\G
    *************************** 1. row ***************************
            Table: emp
       Non_unique: 0
         Key_name: PRIMARY
     Seq_in_index: 1
      Column_name: e_id
        Collation: A
      Cardinality: 5226312
         Sub_part: NULL
           Packed: NULL
             Null: 
       Index_type: BTREE
          Comment: 
    Index_comment: 
    *************************** 2. row ***************************
            Table: emp
       Non_unique: 1
         Key_name: dept_id
     Seq_in_index: 1
      Column_name: dept_id
        Collation: A
      Cardinality: 1045262
         Sub_part: NULL
           Packed: NULL
             Null: YES
       Index_type: BTREE
          Comment: 
    Index_comment: 
    2 rows in set (0.01 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    type 各选项的含义如下:

    (1)ALL:Full Table Scan,如果查询没有使用索引,MySQL 将遍历全表以找到匹配的行。

    -- salary 列没有创建索引,查询 salary 列时采用全表扫描,该查询用时 0.8 秒
    mysql> explain select * from emp where salary=16587\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: ALL  
    1 row in set, 1 warning (0.00 sec)
    mysql> select * from emp where salary=16587;
    +---------+------------+--------+---------+
    | e_id    | e_name     | salary | dept_id |
    +---------+------------+--------+---------+
    |    1505 | 李7623821  |  16587 |    NULL |
    |    8475 | 李1966649  |  16587 |    NULL |
    .......
    | 5527773 | 刘8556683  |  16587 |    NULL |
    | 5533268 | 刘8556663  |  16587 |    NULL |
    +---------+------------+--------+---------+
    258 rows in set (0.80 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    (2)index:全索引扫描,和 ALL 相比,index 只遍历索引树,通常比 ALL 快,因为索引文件通常比数据文件小。

    -- 该查询需要把 e_id 列中所有数据全部取出进行计数,因此需要遍历整个索引树
    mysql> explain select count(e_id) from emp\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: index
      ....
    1 row in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    (3)range:检索给定范围的行,一般出现在 where 语句的条件中,如使用 between、>、<、in 等查询。要查询的列必须创建索引,否则是全表扫描(ALL)。

    mysql> explain select * from emp where e_id between 16587 and 16588\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: range
      .....
    1 row in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (4)ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,返回匹配某条件的多行值。

    mysql> explain select * from emp where dept_id=11\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: ref
      ....
    1 row in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (5)eq_ref:用于连接查询,对于前表的每一行,后表只有一行被扫描。

    mysql> explain select e_name from emp,dept where emp.dept_id=dept.dept_id and e_name='王2246852'\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: ref
    possible_keys: dept_id,idx_name
              key: idx_name
          key_len: 61
              ref: const
             rows: 1
         filtered: 100.00
            Extra: Using where
    *************************** 2. row ***************************
               id: 1
      select_type: SIMPLE
            table: dept
       partitions: NULL
             type: eq_ref
    possible_keys: PRIMARY
              key: PRIMARY
          key_len: 4
              ref: mydb.emp.dept_id
             rows: 1
         filtered: 100.00
            Extra: Using index
    2 rows in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    (6)const:表示通过一次索引就找到了结果,常出现于 primary key 或 unique 索引。因为只匹配一行数据,所以查询非常快。如将主键置于 where 条件中,MySQL 就能将查询转换为一个常量。

    mysql> explain select * from emp where e_id=3\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: const
      .....
    1 row in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (7)NULL:执行时不用访问表或索引。

    mysql> explain select now() from dual\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: NULL
       partitions: NULL
             type: NULL
       ....
            Extra: No tables used
    1 row in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    5、possible_keys

    显示可能应用在表中的索引,可能一个或多个。查询涉及到的列若存在索引,则该索引将被列出,但不一定被查询实际使用。

    例如:

    mysql> explain select e_name from emp,dept where emp.dept_id=dept.dept_id and e_name='王2246852'\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: ref
    possible_keys: dept_id,idx_name
              key: idx_name
          key_len: 61
              ref: const
             rows: 1
         filtered: 100.00
            Extra: Using where
    *************************** 2. row ***************************
               id: 1
      select_type: SIMPLE
            table: dept
       partitions: NULL
             type: eq_ref
    possible_keys: PRIMARY
              key: PRIMARY
          key_len: 4
              ref: mydb.emp.dept_id
             rows: 1
         filtered: 100.00
            Extra: Using index
    2 rows in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    6、key

    实际使用的索引,如为NULL,则表示未使用索引。若查询中使用了覆盖索引,则该索引和查询的 select 字段重叠。

    例如:

    -- 姓名的第一个字符重复率太高,查询时没有使用索引
    mysql> explain select * from emp where e_name like '王%'\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: ALL
    possible_keys: idx_name
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 5058821
         filtered: 41.72
            Extra: Using where
    1 row in set, 1 warning (0.00 sec)
    
    -- 查询姓名时使用了索引
    mysql> explain select * from emp where e_name like '王2547%'\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: range
    possible_keys: idx_name
              key: idx_name
          key_len: 61
              ref: NULL
             rows: 643
         filtered: 100.00
            Extra: Using index condition
    1 row in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    7、key_len

    表示索引所使用的字节数,可通过该列计算查询使用的索引长度。在不损失精确性的情况下,长度越短越好。

    例如:

    -- 索引 idx_name 的长度是61个字节
    mysql> explain select * from emp where e_name like '王2547%'\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: range
    possible_keys: idx_name
              key: idx_name
          key_len: 61
              ref: NULL
             rows: 643
         filtered: 100.00
            Extra: Using index condition
    1 row in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    8、ref

    显示关联的字段,如果是非连接查询,则显示 const,如果是连接查询,则会显示关联的字段。

    例如:

    mysql> explain select e_name from emp,dept where emp.dept_id=dept.dept_id\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: dept
       partitions: NULL
             type: index
    possible_keys: PRIMARY
              key: idx_name
          key_len: 61
              ref: NULL
             rows: 5
         filtered: 100.00
            Extra: Using index
    *************************** 2. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: ref
    possible_keys: dept_id
              key: dept_id
          key_len: 5
              ref: mydb.dept.dept_id
             rows: 1264705
         filtered: 100.00
            Extra: NULL
    2 rows in set, 1 warning (0.00 sec)
    
    -- 非连接查询,ref 显示为 null
    mysql> explain select * from emp where e_name like '王2547%'\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: range
    possible_keys: idx_name
              key: idx_name
          key_len: 61
              ref: NULL
             rows: 643
         filtered: 100.00
            Extra: Using index condition
    1 row in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    9、rows

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

    例如:

    mysql> select count(*) from emp;
    +----------+
    | count(*) |
    +----------+
    |  5242880 |
    +----------+
    1 row in set (1.10 sec)
    
    mysql> select count(*) from emp where e_name like '王%';
    +----------+
    | count(*) |
    +----------+
    |  1048576 |
    +----------+
    1 row in set (0.25 sec)
    
    -- 该查询执行的是全表扫描,需要读取表中的所有记录。
    mysql> explain select * from emp where e_name like '王%'\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: ALL
    possible_keys: idx_name
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 5058821
         filtered: 41.72
            Extra: Using where
    1 row in set, 1 warning (0.00 sec)
    
    -- 该查询的结果为 643 行
    mysql> select count(*) from emp where e_name like '王2547%';
    +----------+
    | count(*) |
    +----------+
    |      643 |
    +----------+
    1 row in set (0.00 sec)
    
    -- 该查询使用了索引,一共读取 643 行
    mysql> explain select * from emp where e_name like '王2547%'\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: range
    possible_keys: idx_name
              key: idx_name
          key_len: 61
              ref: NULL
             rows: 643
         filtered: 100.00
            Extra: Using index condition
    1 row in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    10、filtered

    表示选取的行和读取的行的百分比,100表示选取了100%,80表示读取了80%。例如:

    mysql> select count(*) from emp;
    +----------+
    | count(*) |
    +----------+
    |  5242880 |
    +----------+
    1 row in set (1.10 sec)
    
    mysql> select count(*) from emp where e_name like '王%';
    +----------+
    | count(*) |
    +----------+
    |  1048576 |
    +----------+
    1 row in set (0.25 sec)
    
    -- 该查询执行的是全表扫描,表中一共 5242880 条记录,查询到 1048576 条记录。所以读取了 41.72%。
    mysql> explain select * from emp where e_name like '王%'\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: ALL
    possible_keys: idx_name
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 5058821
         filtered: 41.72
            Extra: Using where
    1 row in set, 1 warning (0.00 sec)
    
    -- 该查询的结果为 643 行
    mysql> select count(*) from emp where e_name like '王2547%';
    +----------+
    | count(*) |
    +----------+
    |      643 |
    +----------+
    1 row in set (0.00 sec)
    
    -- 该查询一共读取 643 行,查询结果也是 643 行,读取率 100%
    mysql> explain select * from emp where e_name like '王2547%'\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: range
    possible_keys: idx_name
              key: idx_name
          key_len: 61
              ref: NULL
             rows: 643
         filtered: 100.00
            Extra: Using index condition
    1 row in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    11、extra

    显示一些重要的额外信息。一般有以下几项:

    (1)Using filesort:对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。当查询包含 order by 操作,而且无法利用索引完成的排序操作称为“文件排序”。

    例如:

    -- salary 列没有创建索引
    mysql> explain select * from emp order by salary\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 5058821
         filtered: 100.00
            Extra: Using filesort
    1 row in set, 1 warning (0.02 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    (2)Using temporary:表示 MySQL 需要使用临时表来存储结果集,常见于排序和分组查询。

    例如:

    mysql> explain select count(*) from emp group by left(e_name,1)\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: index
    possible_keys: idx_name
              key: idx_name
          key_len: 61
              ref: NULL
             rows: 5058821
         filtered: 100.00
            Extra: Using index; Using temporary; Using filesort
    1 row in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    (3)Using index:查询使用了覆盖索引(数据列只从索引中就能取得数据,不必读取数据行)。

    mysql> explain select e_name from emp where e_name like '王2587%'\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: range
    possible_keys: idx_name
              key: idx_name
          key_len: 61
              ref: NULL
             rows: 526
         filtered: 100.00
            Extra: Using where; Using index
    1 row in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    (4)Using join buffer:表明在获取连接条件时没有使用索引,并且需要连接缓冲区来存储中间结果。如果出现了这个值,应注意根据查询的具体情况可能需要添加索引来改进能。

    例如:

    -- 删除 emp 表中的外键
    mysql> alter table emp drop foreign key emp_ibfk_1;
    Query OK, 0 rows affected (0.03 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    
    -- 删除 emp 外键对应的索引
    mysql> drop index dept_id on emp;
    Query OK, 0 rows affected (0.02 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    
    mysql> explain select * from emp join dept on emp.dept_id=dept.dept_id\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: dept
       partitions: NULL
             type: index
    possible_keys: PRIMARY
              key: idx_name
          key_len: 61
              ref: NULL
             rows: 5
         filtered: 100.00
            Extra: Using index
    *************************** 2. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 5058821
         filtered: 10.00
            Extra: Using where; Using join buffer (Block Nested Loop)
    2 rows in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    三、使用 explain 命令分析查询

    1、没有创建索引

    (1)执行如下查询,查询用时 1.26 秒

    mysql> select * from emp where salary=15872;
    +---------+------------+--------+---------+
    | e_id    | e_name     | salary | dept_id |
    +---------+------------+--------+---------+
    |   10930 | 李711638   |  15872 |      11 |
    |   12580 | 李7507372  |  15872 |      11 |
    .....
    | 5497149 | 张4265793  |  15872 |      14 |
    | 5514615 | 王1433169  |  15872 |      14 |
    +---------+------------+--------+---------+
    304 rows in set (1.26 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    (2)使用 explain 命令分析查询使用索引的情况

    分析可知:该查询没有使用索引。

    mysql> explain select * from emp where salary=15872\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 5058821
         filtered: 10.00
            Extra: Using where
    1 row in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    (3)查看 emp 表的索引情况

    可以看出,emp 表并没有针对 salary 列创建索引。

    mysql> show index from emp\G
    *************************** 1. row ***************************
            Table: emp
       Non_unique: 0
         Key_name: PRIMARY
     Seq_in_index: 1
      Column_name: e_id
        Collation: A
      Cardinality: 5058821
         Sub_part: NULL
           Packed: NULL
             Null: 
       Index_type: BTREE
          Comment: 
    Index_comment: 
    *************************** 2. row ***************************
            Table: emp
       Non_unique: 1
         Key_name: idx_name
     Seq_in_index: 1
      Column_name: e_name
        Collation: A
      Cardinality: 3540442
         Sub_part: NULL
           Packed: NULL
             Null: YES
       Index_type: BTREE
          Comment: 
    Index_comment: 
    2 rows in set (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    (4)根据 salary 列创建索引

    mysql> create index idx_salary on emp(salary);
    Query OK, 0 rows affected (25.61 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    
    • 1
    • 2
    • 3

    (5)重新执行查询

    查询用时 0.07 秒,和上次查询相比,查询时间明显缩短。

    mysql> select * from emp where salary=15872;
    +---------+------------+--------+---------+
    | e_id    | e_name     | salary | dept_id |
    +---------+------------+--------+---------+
    |   10930 | 李711638   |  15872 |      11 |
    |   12580 | 李7507372  |  15872 |      11 |
    .........
    | 5497149 | 张4265793  |  15872 |      14 |
    | 5514615 | 王1433169  |  15872 |      14 |
    +---------+------------+--------+---------+
    304 rows in set (0.07 sec)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    (6)使用 explain 分析查询

    分析可知,本次查询使用了索引 idx_salary。

    mysql> explain select * from emp where salary=15872\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: ref
    possible_keys: idx_salary
              key: idx_salary
          key_len: 5
              ref: const
             rows: 304
         filtered: 100.00
            Extra: NULL
    1 row in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2、创建了索引但没有使用

    (1)执行如下查询

    发现该查询没有使用索引,执行全表扫描而且 filtered 为 41.72%,说明在查询结果占总数据的比例过高时,MySQL 自动选择不使用索引,全表扫描可能更快。

    mysql> explain select * from emp where e_name like '王%'\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: ALL
    possible_keys: idx_name
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 5058821
         filtered: 41.72
            Extra: Using where
    1 row in set, 1 warning (0.01 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    (2)修改查询条件,减少查询结果

    此时,查询使用了索引。

    mysql> explain select * from emp where e_name like '王584678'\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: range
    possible_keys: idx_name
              key: idx_name
          key_len: 61
              ref: NULL
             rows: 1
         filtered: 100.00
            Extra: Using index condition
    1 row in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3、条件设置错误导致索引无法使用

    (1)执行如下查询

    发现并没有使用索引,原因是 where 条件设置错误,通配符不能放在最左边。

    mysql> explain select * from emp where e_name like '%王25024'\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 5058821
         filtered: 11.11
            Extra: Using where
    1 row in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    (2)修改查询条件

    此时,查询使用了索引 idx_name。

    mysql> explain select * from emp where e_name like '王25024%'\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: emp
       partitions: NULL
             type: range
    possible_keys: idx_name
              key: idx_name
          key_len: 61
              ref: NULL
             rows: 70
         filtered: 100.00
            Extra: Using index condition
    1 row in set, 1 warning (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    C语言基础Day8-共用体
    持续集成,持续交付和持续部署的概念,以及GitLab CI / CD的介绍
    N点标定-坐标系变换
    [qiankun]微前端实现方案
    教程图文详解 - 下一代互联网(第七章)
    中国功率半导体产业发展状况及“十四五”投资规划建议报告2022-2028年新版
    HarmonyOS--状态管理--装饰器
    javaWeb项目-邮票鉴赏系统功能介绍
    【毕业季·进击的技术er】努力只能及格,拼命才能优秀!
    Python语义分割与街景识别(1):理论学习
  • 原文地址:https://blog.csdn.net/weixin_44377973/article/details/125889914