SQL对于大多数程序员来说都不陌生,甚至可以与我们使用的具体编程语言(JAVA、C、C++等)相提并论。我们无法总是写出完美的代码,同样我们也无法总是写出完美的SQL。这时候就需要mysql优化器登场了。mysql的优化器会根据表、列、索引的详细信息以及where字句中的条件,对用户下发的SQL进行优化,使其可以更高效地执行。而优化器执行的最有效的一组查询操作就叫做查询执行计划。通过查看执行计划我们不仅可以了解SQL执行的详情,还可以发现一些低效的操作,并以此为依据来改进SQL语句,实现高效查询。当然了,执行计划不仅可以用来查看SELECT语句,同样也可以用来查看DELETE、INSERT、REPLACE和UPDATE语句。由于平时我们使用SELECT最多,而且对于SELECT语句,执行计划可以提供很多关于执行的信息,所以我们主要讨论有关SELECT的执行计划。
环境准备
CREATE TABLE `student` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(20) NOT NULL DEFAULT '',
`age` TINYINT UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `name_index` (`name`)
)ENGINE = InnoDB DEFAULT CHARSET = utf8;
INSERT INTO student (name, age) VALUES ('a', 99);
INSERT INTO student (name, age) VALUES ('b', 21);
INSERT INTO student (name, age) VALUES ('c', 23);
INSERT INTO student (name, age) VALUES ('d', 50);
INSERT INTO student (name, age) VALUES ('e', 15);
CREATE TABLE `sc` (
`sid` INT NOT NULL,
`course` VARBINARY(10) NOT NULL DEFAULT '',
`score` DECIMAL(4, 1) NOT NULL DEFAULT 0,
KEY (sid, course)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
INSERT INTO sc (sid, course, score) values('1', '语文', 34);
INSERT INTO sc (sid, course, score) values ('1', '数学', 78);
INSERT INTO sc (sid, course, score) values('2', '语文', 34);
INSERT INTO sc (sid, course, score) values ('2', '英语', 56);
INSERT INTO sc (sid, course, score) values('3', '数学', 34);
INSERT INTO sc (sid, course, score) values ('4', '英语', 78);
执行计划详解
id
select语句得标识符,每个select都会自动分配一个唯一的标识符
select_type
select查询类型
- SIMPLE:简单的查询,不包含UNION和子查询
- PRIMARY:最外层的查询
- UNION:union语句中第二个或者后面的查询
- DEPENDENT UNION:union语句中第二个或者后面的查询,取决于外部的查询
- UNION RESULT:union的结果
- SUBQUERY:子查询中的第一个查询
- DEPENDENT SUBQUERY:子查询中的第一个查询,取决于外部查询
table
查询涉及到的表
partitions
查询记录匹配的分区,对于非分区表值为NULL
type
连接类型
- system:该表的查询结果只有一条,是const的特例
- const:该表最多一个匹配的行,该行在查询开始的时候读取
因为只有一行,优化器会将该行中列的值视为常量。因为只读取一次,所以非常快 - eq_ref
当使用=运算符进行索引列的比较时使用。比较值可以是常量,也可以是之前读取的表中列的表达式。 - ref
对于先前表中的每个行,将从该表中读取所有匹配的行。如果联接仅使用最左前缀、键不是主键、键是非唯一键(换句话说,联接无法根据键值选择单个行) - range
使用索引进行范围查询,通常出现在=,<>,>,>=,<,<=,is null,<=>,between,in()操作中。当type为range时,ref为NULL,并且key_len字段是此次查询使用到的索引最长的那个 - index
连接类型和ALL相同,除了扫描索引树。一般来说index比all快,因为索引的数据量一般小于数据 - all
进行全表扫描,通常要禁止这种连接。查询效率极低
possible_keys
查询中可能用到的索引,也就是说不一定会使用到这些索引
key
查询实际用到的索引,可能是一个possibley_keys中不存在的索引
key_len
实际使用索引的长度。对于符合索引可以通过此值看出实际使用到了索引的哪些字段。计算规则如下:
- 字符串:
char(n):utf8编码为3n字节,utf8mb4编码为4n字节
varchar(n):utf8编码为3n+2字节,utf8mb4编码为4n+2字节 - 数值类型
tinyint:1字节
smallint:2字节
mediumint:3字节
int:4字节
bigint:8字节 - 时间类型
date:3字节
datetime:8字节
timestamp:4字节
字段属性:NULL属性占用一个字节,如果一个字段是NOT NULL的,则没有此属性
ref
显示将哪些列或常量与索引进行比较
rows
要查找符合条件的结果需要扫面的行数,此值是估计值。值越小,效率越高
filtered
被过滤的行数的百分表如果是100,表示没有被过滤。例如:如果行数为1000,过滤条件为50.00(50%),则与下表连接的行数为1000×50%= 500。
Extra
- Using index:覆盖索引扫描,需要的数据在索引中就可以查到,不需要查找数据行,也说明了性能不错
- Using index condition:索引下推,如果不使用索引下推,则存储引擎将遍历索引以在基表中定位行,并将其返回给mysql服务器,后者将评估where行的条件。启用索引下推后,mysql服务器会将where条件下降到存储引擎,然后存储引擎只读取满足条件的行。索引下推可以减少存储引擎访问基表的次数以及mysql服务器访问存储引擎的次数
- Using filesort:仅通过索引无法排序,mysql需要额外遍历一次,以找出如何按照排序顺序检索行
- Using temporary:查询用到了临时表。例如在使用group by和order by的情况下需要列出不同的列
更多信息介绍参考mysql优化之查询执行计划