文章目录
- 1.查询优化-Explain语句详解上
- 1.1前言
- 1.2执行计划输出各列详解
- 1.2.1 table
- 1.2.2 id
- 1.2.3 select_type
- 1.2.4 partitions
- 1.2.5 type
- 1.2.6 possible_keys和key
- 1.2.7 key_len
- 1.2.8 ref
- 1.2.9 rows
- 世纪晚霞
1.查询优化-Explain语句详解上
1.1前言
- 一条查询语句在经过 MySQL 查询优化器的各种基于成本和规则的优化会后生成一个所谓的 执行计划 ,这个执行计划展示了接下来具体执行查询的方式,比如可能使用的索引、实际使用的索引、以及使用的索引长度
- 可以使用EXPLAIN 语句来帮助我们查看某个查询语句的具体执行计划,例如:
mysql> EXPLAIN SELECT 1;
+----+-------------+-------+------------+------+---------------+------+---------+------+--
----+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | r
ows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+--
----+----------+----------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | N
ULL | NULL | No tables used |
+----+-------------+-------+------------+------+---------------+------+---------+------+--
----+----------+----------------+
1 row in set, 1 warning (0.01 sec)
-
EXPLAIN 语句输出的各个列的作用:
- id: 在一个大的查询语句中每个 SELECT 关键字都对应一个唯一的 id
- select_type :SELECT 关键字对应的那个查询的类型
- table 表名
- partitions 匹配的分区信息
- type :针对单表的访问方法
- possible_keys :可能用到的索引
- key :实际上使用的索引
- key_len :实际使用到的索引长度
- ref:当使用索引列等值查询时,与索引列进行等值匹配的对象信息
- rows:预估的需要读取的记录条数
- filtered:某个表经过搜索条件过滤后剩余记录条数的百分比
- Extra 一些额外的信息
-
测试表:single_table表
-
我们仍然假设有两个和 single_table 表构造一模一样的 s1 、 s2 表,而且这两个表里边儿有10000条记录,除 id列外其余的列都插入随机值
CREATE TABLE single_table (
id INT NOT NULL AUTO_INCREMENT,
key1 VARCHAR(100),
key2 INT,
key3 VARCHAR(100),
key_part1 VARCHAR(100),
key_part2 VARCHAR(100),
key_part3 VARCHAR(100),
common_field VARCHAR(100),
PRIMARY KEY (id),
KEY idx_key1 (key1),
UNIQUE KEY idx_key2 (key2),
KEY idx_key3 (key3),
KEY idx_key_part(key_part1, key_part2, key_part3)
) Engine=InnoDB CHARSET=utf8;
1.2执行计划输出各列详解
1.2.1 table
-
定EXPLAIN语句输出的每条记录都对应着某个单表的访问方法,该条记录的table列代表着该表 的表名
-
单表测试:只涉及对 s1 表的单表查询,所以 EXPLAIN 输出中只有一条记录
mysql> EXPLAIN SELECT * FROM s1; +----+-------------+-------+------------+------+---------------+------+---------+------+-- ----+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | r ows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+-- ----+----------+-------+ | 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 9 688 | 100.00 | NULL | +----+-------------+-------+------------+------+---------------+------+---------+------+-- ----+----------+-------+ 1 row in set, 1 warning (0.00 sec)
-
连接查询:到这个连接查询的执行计划中有两条记录,这两条记录的 table 列分别是 s1 和 s2 ,这两条记录用来分 别说明对 s1 表和 s2 表的访问方法是什么
mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2; +----+-------------+-------+------------+------+---------------+------+---------+------+-- ----+----------+---------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | r ows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+-- ----+----------+---------------------------------------+ | 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 9 688 | 100.00 | NULL | | 1 | SIMPLE | s2 | NULL | ALL | NULL | NULL | NULL | NULL | 9 954 | 100.00 | Using join buffer (Block Nested Loop) | +----+-------------+-------+------------+------+---------------+------+---------+------+-- ----+----------+---------------------------------------+ 2 rows in set, 1 warning (0.01 sec)
1.2.2 id
- 查询语句中每出现一个 SELECT 关键字,MySQL 就会为它分配一个唯一的 id 值
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
+----+-------------+-------+------------+------+---------------+----------+---------+-----
--+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref
| rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+-----
--+------+----------+-------+
| 1 | SIMPLE | s1 | NULL | ref | idx_key1 | idx_key1 | 303 | cons
t | 8 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+----------+---------+-----
--+------+----------+-------+
1 row in set, 1 warning (0.03 sec)
- 对于连接查询来说,一个 SELECT 关键字后边的 FROM 子句中可以跟随多个表,所以在连接查询的执行计划中,每个表都会对应一条记录,但是这些记录的id值都是相同的
mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2;
+----+-------------+-------+------------+------+---------------+------+---------+------+--
----+----------+---------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | r
ows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+--
----+----------+---------------------------------------+
| 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 9
688 | 100.00 | NULL |
| 1 | SIMPLE | s2 | NULL | ALL | NULL | NULL | NULL | NULL | 9
954 | 100.00 | Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+--
----+----------+---------------------------------------+
2 rows in set, 1 warning (0.01 sec)
- 对于包含子查询的查询语句来说,就可能涉及多个 SELECT 关键字,所以在包含子查询的查询语句的执行计划 中,每个 SELECT 关键字都会对应一个唯一的 id 值
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a';
+----+-------------+-------+------------+-------+---------------+----------+---------+----
--+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref
| rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+----------+---------+----
--+------+----------+-------------+
| 1 | PRIMARY | s1 | NULL | ALL | idx_key3 | NULL | NULL | NUL
L | 9688 | 100.00 | Using where |
| 2 | SUBQUERY | s2 | NULL | index | idx_key1 | idx_key1 | 303 | NUL
L | 9954 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+----------+---------+----
--+------+----------+-------------+
2 rows in set, 1 warning (0.02 sec)
1.2.3 select_type
-
MySQL为每一个 SELECT 关键字代表的小查询都定义了一个称之为 select_type 的属性
-
作用:只要知道了某个小查询的 select_type 属性,就知道了这个小查询在整个大查询中扮演了一个什么角色
-
取值:
-
SINPLE: 查询语句中不包含 UNION 或者子查询的查询都算作是 SIMPLE 类型
-
PRIMARY: 对于包含 UNION 、 UNION ALL 或者子查询的大查询来说,它是由几个小查询组成的,最左边的小查询即为PRIMARY
-
UNION: 对于包含 UNION 或者 UNION ALL 的大查询来说,它是由几个小查询组成的,其中除了最左边的那个小查询以 外,其余的小查询的 select_type 值就是 UNION
mysql> EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2; +----+--------------+------------+------------+------+---------------+------+------- --+------+------+----------+-----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_le n | ref | rows | filtered | Extra | +----+--------------+------------+------------+------+---------------+------+------- --+------+------+----------+-----------------+ | 1 | PRIMARY | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 9688 | 100.00 | NULL | | 2 | UNION | s2 | NULL | ALL | NULL | NULL | NULL | NULL | 9954 | 100.00 | NULL | | NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary | +----+--------------+------------+------------+------+---------------+------+------- --+------+------+----------+-----------------+ 3 rows in set, 1 warning (0.00 sec)
-
UNION RESULT:MySQL 选择使用临时表来完成 UNION 查询的去重工作,针对该临时表的查询的 select_type 就是 UNION RESULT
-
SUBQUERY: 如果包含子查询的查询语句不能够转为对应的 semi-join 的形式,并且该子查询是不相关子查询,并且查询 优化器决定采用将该子查询物化的方案来执行该子查询,该子查询的select_type就为 SUBQUERY
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a'; +----+-------------+-------+------------+-------+---------------+----------+-------- -+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+----------+-------- -+------+------+----------+-------------+ | 1 | PRIMARY | s1 | NULL | ALL | idx_key3 | NULL | NULL | NULL | 9688 | 100.00 | Using where | | 2 | SUBQUERY | s2 | NULL | index | idx_key1 | idx_key1 | 303 | NULL | 9954 | 100.00 | Using index | +----+-------------+-------+------------+-------+---------------+----------+-------- -+------+------+----------+-------------+ 2 rows in set, 1 warning (0.00 sec)
可以看到,外层查询的 select_type 就是 PRIMARY ,子查询的 select_type 就是 SUBQUERY 。需要大家注意 的是,由于select_type为SUBQUERY的子查询由于会被物化,所以只需要执行一遍
-
DEPENDENT UNION:
-
DERIVED
-
MATERIALIZED
-
1.2.4 partitions
- 即分区:先不学习
1.2.5 type
-
执行计划的一条记录就代表着 MySQL 对某个表的执行查询时的访问方法
-
其中的 type 列就表明了 这个访问方法是个啥
-
对使用 InnoDB 存储引擎的表进行单表访问的一些访问方法,完整的访问方法如下: system , const , eq_ref , ref , fulltext , ref_or_null , index_merge , unique_subquery , index_subquery , range , index , ALL
-
system: 当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的,比如MyISAM、Memory,那么对该表的 访问方法就是 system
-
const: 当我们根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法 就是 const
mysql> EXPLAIN SELECT * FROM s1 WHERE id = 5; +----+-------------+-------+------------+-------+---------------+---------+--------- +-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+---------+--------- +-------+------+----------+-------+ | 1 | SIMPLE | s1 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL | +----+-------------+-------+------------+-------+---------------+---------+--------- +-------+------+----------+-------+ 1 row in set, 1 warning (0.01 sec)
-
eq_ref: 在连接查询时,如果被驱动表是通过主键或者唯一 二级索引列等值匹配的方式进行访问的(如果该主键或者 唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较),则对该被驱动表的访问方法就是 eq_ref
mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id; +----+-------------+-------+------------+--------+---------------+---------+-------- -+-----------------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+--------+---------------+---------+-------- -+-----------------+------+----------+-------+ | 1 | SIMPLE | s1 | NULL | ALL | PRIMARY | NULL | NULL | NULL | 9688 | 100.00 | NULL | | 1 | SIMPLE | s2 | NULL | eq_ref | PRIMARY | PRIMARY | 4 | xiaohaizi.s1.id | 1 | 100.00 | NULL | +----+-------------+-------+------------+--------+---------------+---------+-------- -+-----------------+------+----------+-------+ 2 rows in set, 1 warning (0.01 sec)
-
ref:当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就可能是 ref
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a'; +----+-------------+-------+------------+------+---------------+----------+---------+----- --+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+----------+---------+----- --+------+----------+-------+ | 1 | SIMPLE | s1 | NULL | ref | idx_key1 | idx_key1 | 303 | cons t | 8 | 100.00 | NULL | +----+-------------+-------+------------+------+---------------+----------+---------+----- --+------+----------+-------+ 1 row in set, 1 warning (0.04 sec)
-
fulltext: 全文索引
-
ref_or_null: 当对普通二级索引进行等值匹配查询,该索引列的值也可以是 NULL 值时,那么对该表的访问方法就可能是 ref_or_null
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key1 IS NULL; +----+-------------+-------+------------+-------------+---------------+----------+-- -------+-------+------+----------+-----------------------+ | id | select_type | table | partitions | type | possible_keys | key | k ey_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------------+---------------+----------+-- -------+-------+------+----------+-----------------------+ | 1 | SIMPLE | s1 | NULL | ref_or_null | idx_key1 | idx_key1 | 3 03 | const | 9 | 100.00 | Using index condition | +----+-------------+-------+------------+-------------+---------------+----------+-- -------+-------+------+----------+-----------------------+ 1 row in set, 1 warning (0.01 sec)
-
index_merge: 一般情况下对于某个表的查询只能使用到一个索引,但我们唠叨单表访问方法时特意强调了在某些场景下可 以使用 Intersection 、 Union 、 Sort-Union 这三种索引合并的方式来执行查询
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key3 = 'a'; +----+-------------+-------+------------+-------------+-------------------+--------- ----------+---------+------+------+----------+---------------------------------------- -----+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------------+-------------------+--------- ----------+---------+------+------+----------+---------------------------------------- -----+ | 1 | SIMPLE | s1 | NULL | index_merge | idx_key1,idx_key3 | idx_key 1,idx_key3 | 303,303 | NULL | 14 | 100.00 | Using union(idx_key1,idx_key3); Using where | +----+-------------+-------+------------+-------------+-------------------+--------- ----------+---------+------+------+----------+---------------------------------------- -----+ 1 row in set, 1 warning (0.01 sec)
-
index: 当我们可以使用索引覆盖,但需要扫描全部的索引记录时,该表的访问方法就是 index ,比如这样:
mysql> EXPLAIN SELECT key_part2 FROM s1 WHERE key_part3 = 'a'; +----+-------------+-------+------------+-------+---------------+--------------+---- -----+------+------+----------+--------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key _len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+--------------+---- -----+------+------+----------+--------------------------+ | 1 | SIMPLE | s1 | NULL | index | NULL | idx_key_part | 909 | NULL | 9688 | 10.00 | Using where; Using index | +----+-------------+-------+------------+-------+---------------+--------------+---- -----+------+------+----------+--------------------------+ 1 row in set, 1 warning (0.00 sec)
上述查询中的搜索列表中只有 key_part2 一个列,而且搜索条件中也只有 key_part3 一个列,这两个列又恰 好包含在 idx_key_part 这个索引中,可是搜索条件 key_part3 不能直接使用该索引进行 ref 或者 range 方 式的访问,只能扫描整个 idx_key_part 索引的记录,所以查询计划的 type 列的值就是 index
-
ALL: 全表扫描,唯一一个没有用到索引,需要特别关注
1.2.6 possible_keys和key
- possible_keys 列表示在某个查询语句中,对某个表执行单表查询时可能用到的索引有哪些
- key 列表示实际用到的索引有哪些
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key3 = 'a';
+----+-------------+-------+------------+------+-------------------+----------+---------+-
------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len |
ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+-------------------+----------+---------+-
------+------+----------+-------------+
| 1 | SIMPLE | s1 | NULL | ref | idx_key1,idx_key3 | idx_key3 | 303 |
const | 6 | 2.75 | Using where |
+----+-------------+-------+------------+------+-------------------+----------+---------+-
------+------+----------+-------------+
1 row in set, 1 warning (0.01 sec)
- 上述执行计划的 possible_keys 列的值是 idx_key1,idx_key3 ,表示该查询可能使用到 idx_key1,idx_key3 两 个索引
- 然后 key 列的值是 idx_key3 ,表示经过查询优化器计算使用不同索引的成本后,最后决定使用 idx_key3 来执行查询比较划算
- 注意:possible_keys列中的值并不是越多越好,可能使用的索引越多,查询优化器计算查询成 本时就得花费更长时间,所以如果可以的话,尽量删除那些用不到的索引
1.2.7 key_len
- key_len 列表示当优化器决定使用某个索引执行查询时,该索引记录的最大长度,是由这三个部分构成的:
- 对于使用固定长度类型的索引列来说,它实际占用的存储空间的最大长度就是该固定值,对于指定字符集的 变长类型 的索引列来说,比如某个索引列的类型是 VARCHAR(100) ,使用的字符集是 utf8 ,那么该列实际占 用的最大存储空间就是 100 × 3 = 300 个字节
- 如果该索引列可以存储 NULL 值,则 key_len 比不可以存储 NULL 值时多1个字节
- 对于变长字段来说,都会有2个字节的空间来存储该变长列的实际长度。
mysql> EXPLAIN SELECT * FROM s1 WHERE id = 5;
+----+-------------+-------+------------+-------+---------------+---------+---------+-----
--+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref
| rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-----
--+------+----------+-------+
| 1 | SIMPLE | s1 | NULL | const | PRIMARY | PRIMARY | 4 | cons
t | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+-----
--+------+----------+-------+
1 row in set, 1 warning (0.01 sec)
- 由于 id 列的类型是 INT ,并且不可以存储 NULL 值,所以在使用该列的索引时 key_len 大小就是 4
mysql> EXPLAIN SELECT * FROM s1 WHERE key2 = 5;
+----+-------------+-------+------------+-------+---------------+----------+---------+----
---+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref
| rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+----------+---------+----
---+------+----------+-------+
| 1 | SIMPLE | s1 | NULL | const | idx_key2 | idx_key2 | 5 | con
st | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+----------+---------+----
---+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
- 可以看到 key_len 列就变成了 5 ,比使用 id 列的索引时多了 1
- 对于可变长度的索引列来说,比如下边这个查询:
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
+----+-------------+-------+------------+------+---------------+----------+---------+-----
--+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref
| rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+-----
--+------+----------+-------+
| 1 | SIMPLE | s1 | NULL | ref | idx_key1 | idx_key1 | 303 | cons
t | 8 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+----------+---------+-----
--+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
- 由于 key1 列的类型是 VARCHAR(100) ,所以该列实际最多占用的存储空间就是 300 字节
- 又因为该列允许存储 NULL 值,所以 key_len 需要加 1
- 又因为该列是可变长度列,所以 key_len 需要加 2
- 所以最后 ken_len 的 值就是 303
1.2.8 ref
- 当使用索引列等值匹配的条件去执行查询时
- 也就是在访问方法是 const 、 eq_ref 、 ref 、 ref_or_null 、 unique_subquery 、 index_subquery 其中之一时, ref 列展示的就是与索引列作等值匹配的东东是个啥
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
+----+-------------+-------+------------+------+---------------+----------+---------+-----
--+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref
| rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+-----
--+------+----------+-------+
| 1 | SIMPLE | s1 | NULL | ref | idx_key1 | idx_key1 | 303 | cons
t | 8 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+----------+---------+-----
--+------+----------+-------+
1 row in set, 1 warning (0.01 sec)
1.2.9 rows
- 如果查询优化器决定使用全表扫描的方式对某个表执行查询时,执行计划的 rows 列就代表预计需要扫描的行数
- 如果使用索引来执行查询时,执行计划的 rows 列就代表预计扫描的索引记录行数
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 > 'z';
+----+-------------+-------+------------+-------+---------------+----------+---------+----
--+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref
| rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+----------+---------+----
--+------+----------+-----------------------+
| 1 | SIMPLE | s1 | NULL | range | idx_key1 | idx_key1 | 303 | NUL
L | 266 | 100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+----------+---------+----
--+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
世纪晚霞
嘿嘿,末尾放上这几天广州的世纪晚霞,和大家一起分享下嘿嘿,祝好运连连
谢谢各位看到末尾的好朋友,谢谢你们 这么好看 还有眼光能够发现我吖,加油加油,冲!