Mysql复习计划(三)- 索引的设计原则和Mysql性能分析

发布时间 2023-12-08 17:08:34作者: 三角形代表重生

一. 索引的设计原则

索引的分类:

  • 功能逻辑角度,分为4种:普通索引、唯一索引、主键索引、全文索引。
  • 物理实现方式角度,分为2种:聚簇索引、非聚簇索引。
  • 作用字段个数角度,分为2种:单列索引、联合索引。
    普通索引:可以创建在任何数据类型上,无任何限制。
    唯一性索引:使用UNIQUE参数进行设置,值必须是唯一,允许有空值。 可以有多个唯一索引。
    主键索引:一种特殊的唯一性索引。在其基础上增加了不为空的约束。一张表最多一个主键索引。
    单列索引:在表的单个字段上创建索引。
    联合索引:在表的多个字段上创建索引。只有查询条件中使用了这些字段的第一个字段才会被使用。遵循最左前缀原则。
    全文索引:通过FULLTEXT设置索引为全文索引,允许在这种索引列中插入重复值和控制。该类型的索引只能作用于CHAR、VARCHAR、TEXT及系列类型的字段上。 适用于大型数据集。

1.1 Mysql8.0索引新特性

1.1.1 降序索引

Mysql在4版本的时候就支持降序索引的语法,但是DESC的定义是被忽略的,在Mysql8.0版本才开始真正地支持降序索引,但是仅限于InnoDB引擎。
例如一个查询,需要对多个列进行排序,但是顺序要求并不一致,那么使用降序索引会避免数据库使用额外的文件排序操作,从而提高性能。案例如下:
在Mysql5.7和8.0版本分别创建数据库表(a字段默认升序,b降序)

CREATE TABLE test(a int, b int, index idx_a_b(a,b desc));

创建好后,分别使用命令查看创建的表格式:

show create table test\G;

Mysql5.7版本:

Mysql8.0版本:

可以发现Mysql8.0创建的索引已经是降序了,接下来用案例来测试降序索引的效率。分别在两个版本的数据库中插入1000条数据:

DELIMITER // 
CREATE PROCEDURE insert1000()
BEGIN
	DECLARE i INT DEFAULT 1;
	WHILE i <= 1000
	DO
			INSERT INTO test SELECT rand()*80000,rand()*80000;
			SET i = i + 1;
	END WHILE;
	COMMIT;
END // 
DELIMITER ;
CALL insert1000() ;

结果如下:

执行查询语句,并且使用Explain 关键词:

EXPLAIN SELECT * from  test ORDER BY a,b DESC LIMIT 5;

Mysql5.7版本:

Mysql8.0版本:

这两者的区别是很大的。Mysql5.7版本的查询结果中的Extra字段中显示了Using filesort值。说明使用了文件内排序的操作,这种操作是非常耗时的。同时检索了1000条记录。而Mysql8.0当中,使用了降序索引,仅仅检索了5条数据。

1.1.2 隐藏索引

Mysql8.0开始支持隐藏索引,只需要将待删除的索引设置为隐藏索引,那么查询优化器就不会再使用这个索引, 这种通过先将索引设置为隐藏索引,再删除索引的方式叫做软删除。

1.2 索引的设计原则

1.2.1 数据准备

1.测试表准备:

# 创建学生表和课程表
CREATE TABLE student_info (
	id INT(11) NOT NULL AUTO_INCREMENT,
	student_id INT NOT NULL,
	name VARCHAR(20) DEFAULT NULL,
	course_id INT NOT NULL,
	class_id INT(11) DEFAULT	NULL,
	create_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
	PRIMARY KEY(id)
)ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;
CREATE TABLE course (
	id INT(11) NOT NULL AUTO_INCREMENT,
	course_id INT NOT NULL,
	course_name VARCHAR(40) DEFAULT NULL,
	PRIMARY KEY(id)
)ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;
16

2.函数准备:

# 1.允许创建函数设置
set global log_bin_trust_function_creators=1;
#函数1:创建随机产生字符串函数
DELIMITER //
CREATE FUNCTION rand_string ( n INT ) RETURNS VARCHAR ( 255 ) #该函数会返回一个字符串 
BEGIN
DECLARE
	chars_str VARCHAR ( 100 ) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
DECLARE
	return_str VARCHAR ( 255 ) DEFAULT '';
DECLARE
	i INT DEFAULT 0;
WHILE
		i < n DO
		SET return_str = CONCAT(return_str,SUBSTRING( chars_str, FLOOR( 1+RAND ()* 52 ), 1 ));
		SET i = i + 1;
END WHILE;
RETURN return_str;
END // 
DELIMITER;
#函数2:创建随机数函数
DELIMITER //
CREATE FUNCTION rand_num ( from_num INT, to_num INT ) RETURNS INT ( 11 ) BEGIN
	DECLARE
		i INT DEFAULT 0;
	SET i = FLOOR(
		from_num + RAND()*(to_num - from_num + 1));
	RETURN i;
END // 
DELIMITER;
16

3.存储过程准备:

# 存储过程1:创建插入课程表存储过程
DELIMITER //
CREATE PROCEDURE insert_course( max_num INT ) BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0; #设置手动提交事务
REPEAT #循环
SET i=i+1; #赋值
INSERT INTO course (course_id, course_name ) VALUES
(rand_num(10000,10100),rand_string(6));
 UNTIL i = max_num
END REPEAT;
COMMIT; #提交事务 
END //
DELIMITER ;
# 存储过程2:创建插入学生信息表存储过程
DELIMITER //
CREATE PROCEDURE insert_stu( max_num INT ) BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0; #设置手动提交事务
REPEAT #循环
SET i=i+1; #赋值
INSERT INTO student_info (course_id, class_id ,student_id ,NAME ) VALUES
(rand_num(10000,10100),rand_num(10000,10200),rand_num(1,200000),rand_string(6)); UNTIL i = max_num
END REPEAT;
COMMIT; #提交事务
END //
DELIMITER ;
16

4.数据插入:

CALL insert_course(100);
CALL insert_stu(1000000);

1.2.2 适合创建索引的情形

1.字段的数值具备唯一性的特性,此时即使它是组合字段,也必须建立唯一索引。

2.频繁作为where查询条件的字段。
根据student_id去查询:

SELECT course_id ,class_id,name,create_time ,student_id from student_info where student_id = 123110;

此时的查询时间:0.515s。

此时我们为student_id这个字段添加个索引后再进行查询:

ALTER TABLE student_info add INDEX idx_sid(student_id);

此时相同的查询条件下,在加了索引之后,查询的效率明显提升:0.012s

我们可以通过命令来查看学生表中拥有的索引:

show INDEX from student_info;

3.经常group by和order by的列
案例1:根据student_id进行group by。

# 添加了索引之后 0.01s
SELECT student_id,count(*) as num from student_info GROUP BY student_id LIMIT 100;
# 删除索引
DROP INDEX idx_sid on student_info;
# 添加了索引之前 1.565s
SELECT student_id,count(*) as num from student_info GROUP BY student_id LIMIT 100;

案例2:同时使用group by和order by

# 去除限制,以下面为例,ORDER BY create_time,GROUP BY student_id ,此时Mysql要求GROUP BY 中必须包含ORDER BY中的字段create_time
SELECT @@sql_mode;
# 将查询出来的结果的第一个枚举去掉即可 ONLY_FULL_GROUP_BY
set @@sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
# 添加联合索引之前:此时student_id具有单独的索引,时间大概在16s左右
SELECT student_id, count(*) AS num
FROM student_info
GROUP BY student_id
ORDER BY create_time desc LIMIT 10;
# 添加联合索引
ALTER table student_info add INDEX idx_sid_cre_time(student_id,create_time desc);
# 添加联合索引之后再查询,时间缩短到0.5s
SELECT student_id, count(*) AS num
FROM student_info
GROUP BY student_id
ORDER BY create_time desc LIMIT 10;
16

注意:联合索引中,根据最左匹配原则,要将已经具有索引的student_id放到前面。

4.Update、Delete操作中,where条件中的列,也可以添加索引。
以Update为例:

# 时间3.36s
UPDATE student_info set student_id = 10002 where name = 'hssMHY';
# 添加索引
ALTER table student_info add INDEX idx_name(name);
# 时间0.02s
UPDATE student_info set student_id = 10003 where name = 'hssMHY';

如果进行更新的时候,更新的字段是非索引字段,那么此时提升的效率会更明显,因为非索引字段更新的时候不需要对索引进行维护。

5.Distinct字段需要创建索引。

6.多表Join连接操作的时候创建索引注意事项。

select xxx from A,B
on A.id = B.id
where A.name = 'xxx';

注意点:

  1. 连接表的数量尽量不要超过3张,因为每增加一张表,相当于增加一次嵌套循环,数量级增长快。
  2. 对where条件创建索引。因为where语句才是对数据条件进行过滤。
  3. 最后。对于连接的字段创建索引。同时该字段在多张表中的类型必须一致。

7.使用列的类型小的创建索引。
类型大小指的是该类型表示的数据范围大小。 以整数类型为例,有tinyint,mediumint,int,bigint。它们占用的存储空间依次递增。此时我们应该从小的类型开始去创建索引。
原因:

  1. 数据类型越小,在查询时进行的比较操作越快。
  2. 数据类型越小,索引占用的存储空间就越少,在一个数据页中就可以存储更多的记录,从而减少磁盘IO带来的性能损耗。

8.使用字符串前缀创建索引。
背景:表中某个列的字符串长度很长。
这种情况下带来的问题:

  1. B+树索引中的记录,也就是叶子节点上会保存该数据的完整信息,这个保存的过程耗费的时间长。同时字符串越长,占据的存储空间越大。
  2. 字符串越长,做字符串比较的时候会占用更多的时间。
    为了解决这种情况 ,最好通过截取字符串的方式,将截取部分作为索引,也就是建立前缀索引。 不仅能节约空间,还能减少字符串的比较时间。
    同时Alibaba开发手册中建议:
  • 在varchar字段上建立索引的时候,必须指定索引的长度,没必要对全字段建立索引。
    例如:
create table shop(address varchar(120) not null);
# 取address字段的前12位
alter table shop add index(address(12));

一般长度为20的索引,区分度就高达90%以上了。
区分度计算公式:

count(distinct left(列名, 索引长度))/count(*)

9.区分度搞的列适合作为索引。

10.使用最频繁的列放在联合索引的左侧。增加联合索引的使用率。

11.多个字段都要创建索引的情况下,联合索引由于多个单个索引的创建。

当然,索引虽然能够提升查询的效率,但也不是说数量越多越好,对索引有限制:单表索引数量最好不超过6个。 原因如下:

  1. 每个索引都需要占用磁盘空间,索引数量越多,占据的磁盘空间越大。
  2. 索引会造成Insert、Delete、Update等语句的性能。(涉及到维护B+树的消耗)
  3. 优化器在选择如何优化查询的时候,会对每一个可能用到的索引进行评估,因此索引数量越多,会增加Mysql优化器生成执行计划的时间,降低查询性能。

二. Mysql性能分析

当出现执行SQL的时候,存在高延迟的情况,就可以采用分析工具来定位有问题的SQL了,一般分为三个步骤:慢查询、Explain、show Profiling。

2.1 查看系统性能参数

语法结构如下:

SHOW [GLOBAL|SESSION] STATUS LIKE '参数';

常用的几个性能参数如下:

  • Connections:连接Mysql服务器的次数。
  • UpTime:Mysql服务器的上线时间。
  • Slow_queries:慢查询的次数。
  • Innodb_rows_read:Select查询返回的行数。
  • Innodb_rows_inserted:执行Insert操作插入的行数。
  • Innodb_rows_updated:执行Update操作更新的行数。
  • Innodb_rows_deleted:执行Delete操作删除的行数。
  • Com_select:查询操作的次数。
  • Com_insert:插入操作的次数。(批量插入的insert操作,只会算一次)
  • Com_update:更新操作的次数。
  • Com_delete:删除操作的次数。

2.2 慢查询

Mysql有个慢查询日志,一般用来记录哪些响应时间超过阈值(默认10s)的语句。不过默认是关闭的,若非调优需要,一般不建议启动该参数。因为开启慢查询日志多少会带来一定的性能影响。
临时开启慢查询:

# 临时设置(全局有效)
set global slow_query_log='ON';

查看:

show VARIABLES like 'slow_query_log%'


同时为了方便,我们可以将慢查询默认的阈值10s改成0.1s:

set global long_query_time = 0.1;

测试:

SELECT * from student_info where `name` = 'bchEBT'


此时用命令查看下,慢查询语句的次数有几条:

show status like 'slow_queries';

结果如下:

2.2.1 慢查询日志分析工具

在开启慢查询功能后,Mysql就会将相关的慢查询日志写入对应目录的文件下:

先看下mysqldumpslow的用法:

mysqldumpslow --help

结果如下:

  • -a: 不将数字抽象成N,字符串抽象成S。
  • -s: 是表示按照何种方式排序。
    c: 访问次数
    l: 锁定时间
    r: 返回记录
    t: 查询时间
    al:平均锁定时间
    ar:平均返回记录数
    at:平均查询时间 (默认方式)
    ac:平均查询次数
  • -t: 即为返回前面多少条的数据。
  • -g: 后边搭配一个正则匹配模式,大小写不敏感。
    小插曲:mysqldumpslow命令没有。我的Mysql是通过Docker来装的。当你出现以下错误:
bash: mysqldumpslow: command not found

这时候是因为mysqldumpslow不在/usr/bin下面,而系统默认会查找/usr/bin下的命令。因此需要找到mysqldumpslow,并将其软连接到/user/bin下。
1.通过find命令查找mysqldumpslow到底在哪:

find  ./ -name *mysqldumpslow*

好家伙,我这里的地址非常奇怪(你们的应该会正常点):

2.复制对应的路径,然后建立软连接:ln -s [原地址] [目标地址]

ln -s /var/lib/docker/overlay2/e99c397fbbed993eece52ff597970fd763ccfd5320ddeb46b4a801c2cc648f3e/diff/usr/bin/mysqldumpslow /usr/bin/

3.此时在执行命令即可,输入命令:

mysqldumpslow -s t -t 5 /mydata/mysql/data/f634e0d26724-slow.log

结果如下:

2.3 分析查询语句Explain

假如我们开启了慢查询,然后发现了哪几个语句特别慢的,咱们就可以用Explain进行分析了。
Explain为我们提供了查看某个语句的具体执行计划的功能,例如:

  1. 表的读取顺序。
  2. 数据库读取操作的类型。
  3. 哪些索引可以被使用。
  4. 哪些索引被实际使用。
  5. 表之间的引用。
  6. 每张表有多少行数据被优化器查询到。
    重点:Explain并不会真正执行后面的语句。
    | 字段 | 描述 |
    | --- | --- |
    | id | 在一个大的查询语句中每个SELECT关键字都对应一个唯一的id |
    | select_type | Select关键字对应的查询类型 |
    | table | 表名 |
    | type | 针对单表的访问方法 |
    | possible_keys | 可能用到的索引 |
    | key | 实际上使用的索引 |
    | key_len | 实际上使用到的索引长度 |
    | ref | 当使用索引列等值查询时,与索引列进行等值匹配的对象信息 |
    | rows | 预估的需要读取的记录条数 |
    | filtered | 某个表经过搜索条件过滤后剩余记录条数的百分比 |
    | Extra | 额外信息 |

2.3.1 数据准备

1.建表:

CREATE TABLE s1 (
    id INT 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),
    INDEX idx_key1 (key1),
    UNIQUE INDEX idx_key2 (key2),
    INDEX idx_key3 (key3),
    INDEX idx_key_part(key_part1, key_part2, key_part3)
) ENGINE=INNODB CHARSET=utf8;
CREATE TABLE s2 (
    id INT 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),
    INDEX idx_key1 (key1),
    UNIQUE INDEX idx_key2 (key2),
		INDEX idx_key3 (key3),
    INDEX idx_key_part(key_part1, key_part2, key_part3)
) ENGINE=INNODB CHARSET=utf8;
16

2.存储过程:

set global log_bin_trust_function_creators=1; # 不加global只是当前窗口有效。
DELIMITER //
CREATE FUNCTION rand_string1(n INT)
RETURNS VARCHAR(255) #该函数会返回一个字符串
BEGIN
    DECLARE chars_str VARCHAR(100) DEFAULT
'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
    DECLARE return_str VARCHAR(255) DEFAULT '';
    DECLARE i INT DEFAULT 0;
    WHILE i < n DO
        SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
        SET i = i + 1;
    END WHILE;
    RETURN return_str;
END //
DELIMITER ;
DELIMITER //
CREATE PROCEDURE insert_s1 (IN min_num INT (10),IN max_num INT (10))
BEGIN
    DECLARE i INT DEFAULT 0;
    SET autocommit = 0;
    REPEAT
    SET i = i + 1;
    INSERT INTO s1 VALUES(
    (min_num + i),
    rand_string1(6),
    (min_num + 30 * i + 5),
    rand_string1(6),
    rand_string1(10),
    rand_string1(5),
    rand_string1(10),
    rand_string1(10));
    UNTIL i = max_num
END REPEAT;
    COMMIT;
END //
DELIMITER ;
DELIMITER //
CREATE PROCEDURE insert_s2 (IN min_num INT (10),IN max_num INT (10))
BEGIN
DECLARE i INT DEFAULT 0;
    SET autocommit = 0;
    REPEAT
    SET i = i + 1;
    INSERT INTO s2 VALUES(
        (min_num + i),
        rand_string1(6),
        (min_num + 30 * i + 5),
        rand_string1(6),
        rand_string1(10),
        rand_string1(5),
        rand_string1(10),
        rand_string1(10));
    UNTIL i = max_num
    END REPEAT;
    COMMIT;
END //
DELIMITER ;
16
32
48

3.数据创建:

CALL insert_s1(10001,10000);
CALL insert_s2(10001,10000);

2.3.2 id 和 table 字段

案例1:

EXPLAIN SELECT * FROM s1 INNER JOIN s2;

EXPLAIN中,第一行的表叫做驱动表,也就是s1。后面的表叫做被驱动表

这里的table字段也非常好理解,就是实际对应的是哪一张表。因为上述查询语句涉及到两张表,因此对于的EXPLAIN结果,也会出现两条结果。

案例2:

EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a';

结果如下:

因为上述语句包含了子查询,而且子查询肯定是优先执行的。此时id并不再是案例1中的id一致的情况了。

案例3:

EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key2 FROM s2 WHERE common_field = 'a');

但是这种子查询的情况,利用Explain查出来的结果,其id号竟然是一样的。这是因为查询优化器可能对涉及到的子查询进行重写,转变为多表查询的操作。

得出以下结论

  1. id如果相同,可以认为是一组,从上往下顺序执行。
  2. 所有组中,id值越大,优先级越高,越先执行。
  3. 每一个id号代表一个独立的查询,一个sql的查询次数越少越好。

2.3.3 select_type 字段

select_type字段表述这个查询的一个类型,有这么几种:

名称 描述
SIMPLE 不使用UNION的简单查询
PRIMARY /UNION /UNION RESULT Mysql中使用临时表来完成UNION查询的工作,针对该临时表的查询,对应的select_type是UNION_RESULT。
SUBQUERY和 DEPENDENT SUBQUERY
DEPENDENT UNION
DERIVED 派生表,也就是将某个查询结果固定成某个字段
MATERIALIZED 当查询优化器在执行包含子查询的语句的时候,选择将子查询物化之后,再与外层查询进行连接查询。物化表:比如只包含key1字段的表,相当于一个集合常量。

2.3.4 type 字段(重要)

type代表执行查询时的一个访问方法,访问方法如下:system、const、eq_ref、ref、fulltext、ref_or_null、index_merge、unique_subquery、index_subquery、range、index、ALL。按顺序从前往后,性能越来越差。
1.system:当表中 只有一条记录 并且该表使用的存储引擎的统计数据是精确的,例如MyISAM。那么此时对该表的访问是system。

CREATE TABLE t(i int) Engine=MyISAM;
INSERT INTO t VALUES(1);
EXPLAIN SELECT * FROM t;

结果如下:

其他的类型:
结合表s1的结构来看:

类型 描述和案例
const 当我们根据主键或者唯一二级索引列与常数进行等值匹配的时候,单表访问就是const
eq_ref 在连接查询时,若被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的,那么对该被驱动表的访问是eq_ref
ref 当通过普通的二级索引列和常量进行匹配时,那么对该表的访问是ref
ref_or_null 通过普通二级索引进行等值匹配时,该列的值可以为null的时候,此时为ref_or_null
index_merge 单表访问的时候,某些场景可能涉及到索引合并的方式来查询,比如where语句中有两个条件,每个列都有自己单独的索引。
unique_subquery 针对一些包含In子查询的语句,若查询优化器决定将In子查询转化为Exists子查询,并且子查询可以使用主键进行等值匹配时,那么此时子查询执行计划的type是unique_subquery
range 使用索引获取某些范围区间的记录。
index 当我们可以使用索引覆盖,但是需要扫描全部的索引记录的时候,就是index方式
ALL 全表扫描
几个注意点哈:
  • eq_ref针对的是被驱动表。
  • ref_or_null等于在ref的基础上允许有null值罢了。
  • ref访问,不包括主键索引。const包括主键。
  • index_merge目前一共三种索引合并:Intersection、Union、Sort-Union。上述案例通俗点来说。就是where 索引1=xxx or 索引2=xxx的这种情况。
  • SQL性能优化的角度来看:至少到达range级别,要求是ref级别,最好是consts级别。

2.3.5 possible_keys、key 字段

EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key3 = 'a';

结果:

意思是,该查询当中,优化器检查的是否发现可能用到的索引有idx_key1和idx_key3,但是实际上用到的索引是idx_key3。
其中key_len的大小是303,这里的大小指的是字节大小。同时值越大越好,主要针对于联合索引。

2.3.6 key_len 字段 (重要)

例如:根据主键查询

EXPLAIN SELECT * FROM s1 WHERE id = 10005;

那么此时结果:因为id是int类型,占4个字节。

根据key2查询:key2在id的基础上,具有唯一性索引,因此是非空,而非空占1个字节,因此对应的索引长度是5个字节。

EXPLAIN SELECT * FROM s1 WHERE key2 = 10126;


key_len的计算公式如下:

varchar(10)变长字段且允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL)+2(变长字段)
varchar(10)变长字段且不允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+2(变长字段)
char(10)固定字段且允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL)
char(10)固定字段且不允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)

2.3.7 ref 字段

ref字段是当使用索引列进行等值查询的时候,与索引列进行等职匹配的对象信息。 比如对象是一个常数或者是某个列。

2.3.8 rows 和 filtered 字段(重要)

rows字段代表预估的需要读取的记录条数,值越小越好,查询所消耗的时间也就越小。
filtered字段代表某个表经过搜索条件过滤后,剩余记录条数所占的百分比。

EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND common_field = 'a';

这里就是说查询出来的结果,占总数据条数的10%。

2.3.9 Extra 字段(重要)

Extra这个字段非常重要,我们可以通过额外的信息来更准确的理解Mysql如何执行给定的查询语句的。
下面给出几个比较重要和常见的额外信息:

额外信息 描述和案例
No tables used 查询语句没有from子句的时候提示。
Impossible WHERE 当查询语句where子句永远为false的时候
Using where 当使用全局扫描来执行查询,但是where子句中有针对性该表的搜索条件的时候
No matching min/max row 当查询列表处有Min或者Max聚合函数,但是并不符合where子句中的搜索条件的时候出现
Using index 查询列表以及搜索条件中只包含属于某个索引的列的情况出现(比如不需要回表查询)
Using index condition 搜索条件中虽然出现了索引列,但是却不能使用索引,比如使用模糊查询导致索引失效
Using join buffer (Block Nested Loop) 连接查询过程中,当被驱动表不能有效地利用索引来加快访问速度的时候,Mysql就会分配一块名为join buffer的内存块来加快查询速度。 也就是基于块的嵌套循环算法。 例如下面的common_field列不包含索引
Not exists 当使用左连接时,若where子句包含要求被驱动表的某个列等于null值的搜索条件,但是那个列又不允许为null, 此时出现提示。
Using intersect(...) 、 Using union(...) 和 Using sort_union(...) 准备使用索引合并的方式执行查询
Zero limit 当limit子句的参数为0,表示此时读不出任何记录,此时提示。
Using filesort 很多情况排序操作无法使用到索引,只能在内存或者磁盘中进行排序,Mysql将这种在内存上或者磁盘上进行排序的方式叫做文件排序。(效率低)
Using temporary 当执行语句包含Distinct、Group By、Union等子句查询的时候,若不能有效利用索引来完成查询,此时Mysql会借助临时表完成功能, 此时会提示Using temporary