相关文章:
使用 explain 可以模拟优化器执行 SQL 语句,从而知道 MySQL 是如何处理 SQL 语句、如何分析查询语句、如何分析表结构的性能瓶颈
-
表的读取顺序
-
数据读取操作的操作类型
-
哪些索引可以使用
-
哪些索引被实际使用
-
表之间的引用
-
每张表有多少行数据被优化器查询
此外,除了 select 语句,其他如:insert、update、delete、replace 等语句也可以使用 explain 来查询执行计划
一、创建测试数据
-
生成思路
-
利用 MySQL 内存表插入快的特点,可以先利用函数和存储过程在内存表中生成数据,然后再从内存表插入普通表中
-
这里我们来生成一张具有 1000 条数据的学生信息表
-
-
创建普通表和内存表
-
普通表
DROP TABLE IF EXISTS tbl_student; CREATE TABLE tbl_student ( id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, uuid VARCHAR(40) NOT NULL COMMENT '学生唯一标识', name VARCHAR(20) NOT NULL COMMENT '学生姓名', age INT(3) UNSIGNED NOT NULL COMMENT '学生年龄', sex TINYINT(1) UNSIGNED NOT NULL COMMENT '学生性别: 1=男,0=女', PRIMARY KEY (id), UNIQUE KEY uk_uuid (uuid) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='学生信息表';
-
内存表
DROP TABLE IF EXISTS tbl_student_memory; CREATE TABLE tbl_student_memory ( id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, uuid VARCHAR(40) NOT NULL COMMENT '学生唯一标识', name VARCHAR(20) NOT NULL COMMENT '学生姓名', age INT(3) UNSIGNED NOT NULL COMMENT '学生年龄', sex TINYINT(1) UNSIGNED NOT NULL COMMENT '学生性别: 1=男,0=女', PRIMARY KEY (id), UNIQUE KEY uk_uuid (uuid) ) ENGINE=Memory DEFAULT CHARSET=utf8 COMMENT='学生信息表';
-
-
创建随机函数
-
生成随机姓名
DROP FUNCTION IF EXISTS generate_student_name; CREATE FUNCTION generate_student_name(n INT) RETURNS VARCHAR(20) BEGIN DECLARE source_name VARCHAR(60) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; DECLARE return_name VARCHAR(20) DEFAULT '' ; DECLARE i INT DEFAULT 0; WHILE i < n DO SET return_name = CONCAT(return_name, SUBSTRING(source_name , FLOOR(1 + RAND() * 62 ), 1)); SET i = i +1; END WHILE; RETURN return_name; END;
-
生成随机年龄
DROP FUNCTION IF EXISTS generate_student_age; CREATE FUNCTION generate_student_age() RETURNS INT(2) BEGIN DECLARE return_age INT(2) DEFAULT 20; SET return_age = return_age + RAND() * 10; RETURN return_age; END;
-
生成随机性别
DROP FUNCTION IF EXISTS generate_student_sex; CREATE FUNCTION generate_student_sex() RETURNS TINYINT(1) BEGIN DECLARE return_sex TINYINT(1) DEFAULT 1 ; SET return_sex = (CASE WHEN RAND() >= 0.5 THEN 1 ELSE 0 END); RETURN return_sex; END;
-
-
创建存储过程
DROP PROCEDURE IF EXISTS add_student_record; CREATE PROCEDURE add_student_record(IN n int) BEGIN DECLARE i INT DEFAULT 1; WHILE (i <= n) DO INSERT INTO tbl_student_memory (uuid, name, age, sex ) VALUES (UUID(), generate_student_name(20), generate_student_age(), generate_student_sex()); SET i = i + 1; END WHILE; END;
-
调用存储过程
CALL add_student_record(1000);
-
插入普通表中
INSERT INTO tbl_student SELECT * FROM tbl_student_memory;
-
统计学生表
mysql> select count(*) from tbl_student; +----------+ | count(*) | +----------+ | 1000 | +----------+ 1 row in set (0.00 sec)
-
PS:如果要生成大量数据,需要提前在 MySQL 配置文件中设置两个参数,然后重启 MySQL
tmp_table_size=1024M max_heap_table_size=1024M
-
tmp_table_size
-
内存临时表的最大值,超过该限制后会记录到磁盘当中,存储在指定的
tmpdir
目录下mysql> show variables like 'tmpdir'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | tmpdir | /tmp | +---------------+-------+ 1 row in set (0.00 sec)
-
-
max_heap_table_size
- 用户可以创建的内存表大小,该值用于计算内存表的最大行数值
-
二、调用 explain
mysql> explain select name from tbl_student t order by name desc;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | t | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
1 row in set, 1 warning (0.00 sec)
三、参数说明
-
id
-
查询序号,表示执行 select 语句的执行顺序
-
当 id 相同时,从上到下顺序执行;当 id 不同时,id 越大的越先执行
mysql> explain select t1.uuid, t2.name from tbl_student t1, tbl_student t2 where t2.name = t1.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 | 1000 | 100.00 | NULL | | 1 | SIMPLE | t2 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 10.00 | Using where; Using join buffer (Block Nested Loop) | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+ 2 rows in set, 1 warning (0.00 sec)
mysql> explain select name from tbl_student t1 union select name from tbl_student t2; +----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+ | 1 | PRIMARY | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | NULL | | 2 | UNION | t2 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 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)
-
-
select_type
-
查询类型,主要用于区别普通查询、联合查询、子查询等复杂查询,此处只展示了常见的分类
-
SIMPLE
-
最简单的 select 语句,查询中不包括子查询和 union
mysql> explain select name from tbl_student t; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+ | 1 | SIMPLE | t | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | NULL | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+ 1 row in set, 1 warning (0.00 sec)
-
-
PRIMARY
-
最外层的 select 语句,在使用 union 的查询语句中是最前面的 select 语句
mysql> explain select name from tbl_student t1 where id = (select id from tbl_student t2 where id = 1); +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+ | 1 | PRIMARY | t1 | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | NULL | | 2 | SUBQUERY | t2 | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | Using index | +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+ 2 rows in set, 1 warning (0.00 sec)
mysql> explain select name from tbl_student t1 union select name from tbl_student t2; +----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+ | 1 | PRIMARY | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | NULL | | 2 | UNION | t2 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 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
-
union 后的第二个或之后的 select 语句
mysql> explain select name from tbl_student t1 union select name from tbl_student t2; +----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+ | 1 | PRIMARY | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | NULL | | 2 | UNION | t2 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 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)
-
-
DEPENDENT UNION
-
union 后的第二个或之后的 select 语句,同时依赖于外部查询
mysql> explain select name from tbl_student t where id in (select id from tbl_student t1 union select id from tbl_student t2); +----+--------------------+------------+------------+--------+---------------+---------+---------+------+------+----------+-----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+--------------------+------------+------------+--------+---------------+---------+---------+------+------+----------+-----------------+ | 1 | PRIMARY | t | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | Using where | | 2 | DEPENDENT SUBQUERY | t1 | NULL | eq_ref | PRIMARY | PRIMARY | 8 | func | 1 | 100.00 | Using index | | 3 | DEPENDENT UNION | t2 | NULL | eq_ref | PRIMARY | PRIMARY | 8 | func | 1 | 100.00 | Using index | | NULL | UNION RESULT | <union2,3> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary | +----+--------------------+------------+------------+--------+---------------+---------+---------+------+------+----------+-----------------+ 4 rows in set, 1 warning (0.00 sec)
-
-
UNION RESULT
-
union 查询的结果
mysql> explain select name from tbl_student t1 union select name from tbl_student t2; +----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+ | 1 | PRIMARY | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | NULL | | 2 | UNION | t2 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 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)
-
-
SUBQUERY
-
子查询中的第一个 select 语句
mysql> explain select name from tbl_student t1 where id = (select id from tbl_student t2 where id = 1); +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+ | 1 | PRIMARY | t1 | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | NULL | | 2 | SUBQUERY | t2 | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | Using index | +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+ 2 rows in set, 1 warning (0.00 sec)
-
-
DEPENDENT SUBQUERY
-
子查询中的第一个 select 语句,同时依赖于外部查询
mysql> explain select name from tbl_student t1 where id = (select id from tbl_student t2 where id = t1.id); +----+--------------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+--------------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+ | 1 | PRIMARY | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | Using where | | 2 | DEPENDENT SUBQUERY | t2 | NULL | eq_ref | PRIMARY | PRIMARY | 8 | test.t1.id | 1 | 100.00 | Using index | +----+--------------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+ 2 rows in set, 2 warnings (0.00 sec)
-
-
DERIVED
-
派生表,from 子句中的 select 语句
mysql> explain select name from (select name from tbl_student t1 union select name from tbl_student t2) t3; +----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+ | 1 | PRIMARY | <derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 2000 | 100.00 | NULL | | 2 | DERIVED | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | NULL | | 3 | UNION | t2 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 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)
-
-
-
-
table
-
输出行所引用的表,也有可能是以下值之一
-
<unionM,N>
-
该行引用的表为 id 是 M 和 N 行的并集
mysql> explain select name from tbl_student t1 union select name from tbl_student t2; +----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+ | 1 | PRIMARY | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | NULL | | 2 | UNION | t2 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 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)
-
-
<derivedN>
-
该行引用的表为 id 是 N 行的派生表结果 (派生表可能来源于 from 子句中的 select 语句)
mysql> explain select name from (select name from tbl_student t1 union select name from tbl_student t2) t3; +----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+ | 1 | PRIMARY | <derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 2000 | 100.00 | NULL | | 2 | DERIVED | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | NULL | | 3 | UNION | t2 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 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)
-
-
<subqueryN>
- 该行引用的表为 id 是 N 行的实例化子查询结果
-
-
-
partitions
- 查询输出行对应的分区,对于未分区的表而言,该值为 null
-
type
-
连接类型,用于说明表与表之间是如何进行关联操作的,此处只展示了常见的连接类型
-
system
- 表示仅有一行记录,是 const 类型的特例
-
const
-
表示通过索引查找一次就能找到指定的数据,常用于常量
-
const 相比较于 primary key 或者 unique 索引,因此只匹配一行数据,所以很快
mysql> explain select name from tbl_student where id = 1; +----+-------------+-------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+ | 1 | SIMPLE | tbl_student | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | NULL | +----+-------------+-------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+ 1 row in set, 1 warning (0.00 sec)
-
-
eq_ref
-
唯一性索引扫描,对于每个索引键,表中只有一行数据与之匹配,常用于主键或唯一键
mysql> explain select t1.name from tbl_student t1, tbl_student t2 where t2.id = t1.id; +----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+ | 1 | SIMPLE | t1 | NULL | ALL | PRIMARY | NULL | NULL | NULL | 1000 | 100.00 | NULL | | 1 | SIMPLE | t2 | NULL | eq_ref | PRIMARY | PRIMARY | 8 | test.t1.id | 1 | 100.00 | Using index | +----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+ 2 rows in set, 1 warning (0.00 sec)
-
-
ref
-
非唯一性索引扫描,对于每个索引键,表中有一行或多行数据与之匹配
mysql> alter table tbl_student add index idx_name (name); Query OK, 0 rows affected (0.04 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> explain select name from tbl_student where name = 'cLJqIcFALVbZfGVo'; +----+-------------+-------------+------------+------+---------------+----------+---------+-------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+------+---------------+----------+---------+-------+------+----------+-------------+ | 1 | SIMPLE | tbl_student | NULL | ref | idx_name | idx_name | 62 | const | 1 | 100.00 | Using index | +----+-------------+-------------+------------+------+---------------+----------+---------+-------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec) mysql> alter table tbl_student drop index idx_name; Query OK, 0 rows affected (0.02 sec) Records: 0 Duplicates: 0 Warnings: 0
-
-
fulltext
-
表示使用全文索引来查找数据
mysql> alter table tbl_student add fulltext index ft_name (name); Query OK, 0 rows affected (0.19 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> explain select name from tbl_student where match (name) against ('cLJqIcFALVbZfGVo'); +----+-------------+-------------+------------+----------+---------------+---------+---------+-------+------+----------+-------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+----------+---------------+---------+---------+-------+------+----------+-------------------------------+ | 1 | SIMPLE | tbl_student | NULL | fulltext | ft_name | ft_name | 0 | const | 1 | 100.00 | Using where; Ft_hints: sorted | +----+-------------+-------------+------------+----------+---------------+---------+---------+-------+------+----------+-------------------------------+ 1 row in set, 1 warning (0.00 sec) mysql> alter table tbl_student drop index ft_name; Query OK, 0 rows affected (0.04 sec) Records: 0 Duplicates: 0 Warnings: 0
-
-
ref_or_null
-
ref_or_null 与 ref 类似,不同之处在于使用该连接类型 MySQL 会额外搜索包含 null 值的行
mysql> alter table tbl_student add index idx_name (name); Query OK, 0 rows affected (0.04 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> alter table tbl_student modify name varchar(20) default null comment '学生姓名'; Query OK, 0 rows affected (0.22 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> update tbl_student set name = null where id = 1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> explain select name from tbl_student where name = 'cLJqIcFALVbZfGVo' or name is null; +----+-------------+-------------+------------+-------------+---------------+----------+---------+-------+------+----------+--------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+-------------+---------------+----------+---------+-------+------+----------+--------------------------+ | 1 | SIMPLE | tbl_student | NULL | ref_or_null | idx_name | idx_name | 63 | const | 2 | 100.00 | Using where; Using index | +----+-------------+-------------+------------+-------------+---------------+----------+---------+-------+------+----------+--------------------------+ 1 row in set, 1 warning (0.00 sec) mysql> update tbl_student set name = 'cLJqIcFALVbZfGVo' where id = 1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> alter table tbl_student modify name varchar(20) not null comment '学生姓名'; Query OK, 0 rows affected (0.20 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> alter table tbl_student drop index idx_name; Query OK, 0 rows affected (0.01 sec) Records: 0 Duplicates: 0 Warnings: 0
-
-
index_merge
-
表示使用索引合并优化
-
在这种情况下,key 输出行中包含使用的索引列表,key_len 输出行中包含使用的索引的最大字节长度列表
-
该连接类型仅适用于单个表的索引扫描,而不适合于多个表的索引扫描
mysql> alter table tbl_student add index idx_name (name); Query OK, 0 rows affected (0.07 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> explain select name from tbl_student where uuid = '53bd65ec-c76b-11ea-84aa-00163e0d9873' or name = 'RluhKMuVaAgnZFDxF'; +----+-------------+-------------+------------+-------------+------------------+------------------+---------+------+------+----------+--------------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+-------------+------------------+------------------+---------+------+------+----------+--------------------------------------------+ | 1 | SIMPLE | tbl_student | NULL | index_merge | uk_uuid,idx_name | uk_uuid,idx_name | 122,62 | NULL | 2 | 100.00 | Using union(uk_uuid,idx_name); Using where | +----+-------------+-------------+------------+-------------+------------------+------------------+---------+------+------+----------+--------------------------------------------+ 1 row in set, 1 warning (0.00 sec) mysql> alter table tbl_student drop index idx_name; Query OK, 0 rows affected (0.01 sec) Records: 0 Duplicates: 0 Warnings: 0
-
-
range
-
只检索指定范围的行,常用于使用 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, LIKE, IN 等操作且比较值为常量的情况
mysql> explain select name from tbl_student where id = 1 or id = 2; +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | 1 | SIMPLE | tbl_student | NULL | range | PRIMARY | PRIMARY | 8 | NULL | 2 | 100.00 | Using where | +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec)
mysql> explain select name from tbl_student where id > 1; +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | 1 | SIMPLE | tbl_student | NULL | range | PRIMARY | PRIMARY | 8 | NULL | 999 | 100.00 | Using where | +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec)
-
-
index
-
index 与 ALL 类似,不同之处在于使用该连接类型只会去扫描索引树,通常要比 ALL 块,因为索引文件通常要比数据文件小
-
虽然仍然为全表扫描,但优点在于索引是有序的,不需要借助外部索引来进行排序
mysql> explain select uuid from tbl_student; +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | 1 | SIMPLE | tbl_student | NULL | index | NULL | uk_uuid | 122 | NULL | 1000 | 100.00 | Using index | +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec)
-
-
ALL
-
全表扫描,是效率最低的查找方式
mysql> explain select * from tbl_student; +----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------+ | 1 | SIMPLE | tbl_student | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | NULL | +----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------+ 1 row in set, 1 warning (0.00 sec)
-
-
-
-
possible_keys
- MySQL 在查询时可能使用的索引,查询涉及到的字段若存在索引, 则该索引会被列出,但不一定被查询使用
-
key
-
MySQL 实际使用的索引
-
如果没有使用索引,则 key 为 null;如果是使用了覆盖索引 (指 select 查询的数据只需要从索引中就能取得,而不必读取数据行,换句话说就是,查询列要被所建的索引覆盖),则 key 为该索引
mysql> explain select uuid from tbl_student; +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | 1 | SIMPLE | tbl_student | NULL | index | NULL | uk_uuid | 122 | NULL | 1000 | 100.00 | Using index | +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec)
-
-
key_len
-
MySQL 实际使用的索引的字节长度,可通过 key_len 来计算查询中使用的索引长度,在不损失精度的情况下,长度越短越好
-
key_len 显示的是索引字节的最大可能长度,而非实际使用长度,即 key_len 是根据表定义计算得到的,而不是通过表内检索得到的
-
举例说明
-
VARCHAR (变长字段)
mysql> explain select uuid from tbl_student; +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | 1 | SIMPLE | tbl_student | NULL | index | NULL | uk_uuid | 122 | NULL | 1000 | 100.00 | Using index | +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec)
-
如上所示,查询 uuid 字段,key_len 为 122
-
在创建表时,uuid 字段类型为 VARCHAR,最大长度为 40 (
uuid VARCHAR(40) NOT NULL COMMENT '学生唯一标识'
),字符集为 utf-8,也就是说一个字符占 3 个字节,40 * 3 = 120 个字节,剩余还有 2 个字节是由于该字段为变长字段 (VARCHAR) 而预留的,若将其该为定长字段 (CHAR),就不需要额外的字节了
-
-
CHAR (定长字段)
mysql> alter table tbl_student modify column uuid CHAR(40) NOT NULL COMMENT '学生唯一标识'; Query OK, 1000 rows affected (0.14 sec) Records: 1000 Duplicates: 0 Warnings: 0 mysql> explain select uuid from tbl_student; +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | 1 | SIMPLE | tbl_student | NULL | index | NULL | uk_uuid | 120 | NULL | 757 | 100.00 | Using index | +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec)
- 如上所示,我们将 uuid 字段类型改为 CHAR,然后再调用 explain,此时显示 key_len 为 120,即 40 * 3 = 120 个字节,符合预期
-
最后我们再把 uuid 字段类型改回去
mysql> alter table tbl_student modify column uuid VARCHAR(40) NOT NULL COMMENT '学生唯一标识'; Query OK, 1000 rows affected (0.13 sec) Records: 1000 Duplicates: 0 Warnings: 0
-
-
-
ref
-
显示索引的哪一列被使用了,可能为列或者常量
mysql> explain select name from tbl_student where id = 1; +----+-------------+-------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+ | 1 | SIMPLE | tbl_student | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | NULL | +----+-------------+-------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+ 1 row in set, 1 warning (0.00 sec)
mysql> explain select t1.uuid, t2.name from tbl_student t1, tbl_student t2 where t2.id = t1.id; +----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+ | 1 | SIMPLE | t1 | NULL | index | PRIMARY | uk_uuid | 122 | NULL | 1000 | 100.00 | Using index | | 1 | SIMPLE | t2 | NULL | eq_ref | PRIMARY | PRIMARY | 8 | test.t1.id | 1 | 100.00 | NULL | +----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+ 2 rows in set, 1 warning (0.00 sec)
-
-
rows
- 根据表统计信息以及索引索引的选用情况,大致估算出找到指定记录所需要读取的行数
-
filtered
-
通过过滤的行数对比总行数的百分比
-
最大值为 100,表示没有进行过滤;值从 100 开始减小,表示过滤量增加
-
rows 为读取的估计行数,rows * filtered 的结果表示与下表连接的行数,如:rows = 1000,filtered = 50.00 (50%),则与下表连接的行数为 1000 * 50% = 500
-
-
Extra
-
不适合在其他列中显示,但十分重要的额外信息,此处只展示了常见的额外信息
-
Using filesort
-
表示 MySQL 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取,MySQL 中无法利用索引完成的排序操作称为 “文件排序”
mysql> explain select name from tbl_student order by uuid desc; +----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+----------------+ | 1 | SIMPLE | tbl_student | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | Using filesort | +----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+----------------+ 1 row in set, 1 warning (0.00 sec)
-
-
Using temporary
-
表示使用了临时表来保存中间结果,MySQL 在对查询结果排序时使用了临时表,常用于排序 order by 和分组查询 group by
mysql> explain select name from tbl_student group by name; +----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+ | 1 | SIMPLE | tbl_student | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | Using temporary; Using filesort | +----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+ 1 row in set, 1 warning (0.00 sec)
-
-
Using index
-
表示使用了覆盖索引,避免了访问表的行数据
-
如果同时出现 Using where,则表示索引被用于执行索引键值的查找
mysql> alter table tbl_student add index idx_uuid_name (uuid, name); Query OK, 0 rows affected (0.08 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> explain select uuid from tbl_student where name = 'cLJqIcFALVbZfGVo'; +----+-------------+-------------+------------+-------+---------------+---------------+---------+------+------+----------+--------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+-------+---------------+---------------+---------+------+------+----------+--------------------------+ | 1 | SIMPLE | tbl_student | NULL | index | NULL | idx_uuid_name | 184 | NULL | 1000 | 10.00 | Using where; Using index | +----+-------------+-------------+------------+-------+---------------+---------------+---------+------+------+----------+--------------------------+ 1 row in set, 1 warning (0.01 sec) mysql> alter table tbl_student drop index idx_uuid_name; Query OK, 0 rows affected (0.02 sec) Records: 0 Duplicates: 0 Warnings: 0
-
如果没有出现 Using where, 则表示索引被用于读取数据而非执行查找操作
mysql> explain select uuid from tbl_student; +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | 1 | SIMPLE | tbl_student | NULL | index | NULL | uk_uuid | 122 | NULL | 1000 | 100.00 | Using index | +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec)
-
-
Using where
-
表示使用了 where 条件过滤
mysql> explain select name from tbl_student where name = 'cLJqIcFALVbZfGVo'; +----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | tbl_student | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 10.00 | Using where | +----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec)
-
-
Using join buffer
-
表示使用了连接缓存
mysql> explain select t1.uuid, t2.name from tbl_student t1 inner join tbl_student t2 on t2.name = t1.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 | 1000 | 100.00 | NULL | | 1 | SIMPLE | t2 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 10.00 | Using where; Using join buffer (Block Nested Loop) | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+ 2 rows in set, 1 warning (0.00 sec)
-
-
Impossible WHERE
-
表示使用了 where 条件过滤,但 where 语句的值总为 false,不能用来获取任何数据
mysql> explain select name from tbl_student where id = 1 and id = 2; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+ | 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 | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+ 1 row in set, 1 warning (0.00 sec)
-
-
Select tables optimized away
-
在没有 group by 子句的情况下,基于索引来优化 max()、min() 操作
mysql> explain select max(uuid) from tbl_student; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+ | 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 | Select tables optimized away | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+ 1 row in set, 1 warning (0.00 sec)
mysql> explain select min(uuid) from tbl_student; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+ | 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 | Select tables optimized away | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+ 1 row in set, 1 warning (0.00 sec)
-
对于 MyISAM 存储引擎来优化 count(*) 操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化
-
-
Distinct
- 表示 MySQL 正在查找不同的值,在找到第一个匹配的数据后将停止查找相同数据的操作
-
-
四、相关思考
-
让我们来思考一个问题,当我们使用以下 SQL 来统计表中数据时,会走哪个索引?
explain select count(id) from tbl_student;
-
MySQL 使用的是 B+ 树,它的叶子节点中存在链表,我们统计的时候就会直接走链表,方便我们的统计;那么在到达叶子节点前,MySQL 走的是聚集索引 (主键),还是非聚集索引 (唯一键) 呢?
-
正常情况下,我们肯定会说会走聚集索引 (主键),因为聚集索引可以直接在叶子节点中查找到对应的行数据,那么这条统计 SQL 真的走的是聚集索引 (主键) 吗?让我们来一起运行一下
mysql> explain select count(id) from tbl_student; +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | 1 | SIMPLE | tbl_student | NULL | index | NULL | uk_uuid | 122 | NULL | 1000 | 100.00 | Using index | +----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec)
-
如上所示,出乎意料,这条统计 SQL 并没有走聚集索引 (主键),而是走的非聚集索引 (唯一键),那么为什么不走聚集索引呢?
-
MySQL 走哪个索引是由 MySQL 查询优化器来决定的
-
MySQL 查询优化器最重要的目标是尽可能的使用索引,并且使用最严格的索引来消除尽可能多的数据行,最终目标是提交给 select 语句来查找数据行,MySQL 查询优化器会根据自己的分析和判断的标准来决定使用哪一个索引
-
这里统计 SQL 没有走聚集索引 (主键),大致是因为聚集索引 (主键) 的叶子节点还存放了主键对应的行数据,当数据都放在一起后,查询效率会比非聚集索引 (唯一键) 要低;而非聚集索引 (唯一键) 只存储关键字以及主键的值,这样我们在内存里就可以加载更多的关键字以及主键的值,来做 count 查询操作,从而提高了性能
-
-
为了验证上面的说话,我们把表 tbl_student 的数据增加到 100w 条 (调用步骤一中的操作 (
CALL add_student_record(1000000);
))-
使用非聚集索引 (唯一键)
mysql> select count(id) from tbl_student; +-----------+ | count(id) | +-----------+ | 1000000 | +-----------+ 1 row in set (0.19 sec)
-
使用聚集索引 (主键)
mysql> select count(id) from tbl_student force index (primary); +-----------+ | count(id) | +-----------+ | 1000000 | +-----------+ 1 row in set (0.33 sec)
-
如上所示,当使用非聚集索引 (唯一键) 时,效率确实要比使用聚集索引 (主键) 高
-