MySql优化 ——子查询优化、排序优化

目录

1、子查询优化

2、排序优化

1、概述

2、测试

3、实战

4 filesort算法: 双路排序和单路排序


1、子查询优化

MySQL从4.1版本开始支持子查询,使用子查询可以进行SELECT语句的嵌套查询,即一个SELECT查询的结果作为另个SELECT语句的条件。子查询可以一次性完成很多逻辑上需要多个步骤才能完成的SQL操作。
子查询是 MySQL 的一项重要的功能,可以帮助我们通过一个 SQL 语句实现比较复杂的查询。但是,子查询的执行效率不高。原因:


(1)执行子查询时,MySQL需要为内层查询语句的查询结果 建立一个临时表,然后外层查询语句从临时表中查询记录。查询完毕后,再 撤销这些临时表。这样会消耗过多的CPU和IO资源,产生大量的慢查询.
(2)子查询的结果集存储的临时表,不论是内存临时表还是磁盘临时表都 不会存在索引,所以查询性能会受到一定的影响。
(3)对于返回结果集比较大的子查询,其对查询性能的影响也就越大
在MySQL中,可以使用连接(JOIN)查询来替代子查询。连接查询 不需要建立临时表,其 速度比子查询要快,如果查询中使用索引的话,性能就会更好。
举例1: 查询学生表中是班长的学生信息

#创建班级表中班长的索引
CREATE INDEX idx_monitor ON class(monitor);

EXPLAIN SELECT * FROM student stu1 WHERE stu1.`stuno` IN (
SELECT monitor
FROM class c
WHERE monitor IS NOT NULL
);

 

 MATERIALIZED

当查询优化器在执行包含子查询的语句时,选择将子查询物化之后与外层查询进行连接查询时,
 该子查询对应的`select_type`属性就是`MATERIALIZED

这里注意一个问题:为什么 IS NOT NULL 没有使得索引失效?

一般情况出现

SELECT * from table where  ·index· IS NOT NULL;这个情况索引会失效

因为前面查询的范围是 *,全字段, index 如果不是主键索引,会进行回表操作,优化器判定成本会比全表扫描更高,所以,干脆就直接 type=all了,不用索引

优化方案


EXPLAIN SELECT stul.* FROM student stul JOIN class c ON stul.stuno = c. monitor WHERE c.monitor IS NOT NULL;

 改为内连接,不再生成零时物化表

再看一个例子

#查询不为班长的信息

EXPLAIN SELECT SQL_NO_CACHE a.* FROM student a WHERE a.`stuno` NOT IN (
SELECT monitor
FROM class b
WHERE monitor IS NOT NULL
);

改为多表连接

MySql优化 ——子查询优化、排序优化_第1张图片

 

 

EXPLAIN SELECT SQL_NO_CACHE a.*
FROM student a LEFT OUT JOIN class b
ON a.stuno=b.monitor
where b.monitor IS NULL;

 结论:尽量不要使用NOTIN 或者NOT EXISTS,用LEFT JOIN xxx ON xx WHERE xxIS NULL替代

2、排序优化

1、概述

问题:在WHERE 条件字段上加索,但是为什么在ORDER BY字段上还要加索引呢?
回答:
在 MySQL 中,支持两种排序方式,分别是 FileSortIndex 排序。
。Index 排序中,索引可以保证数据的有序性,不需要再进行排序,效率更高
。FileSot 排序则一般在 内存中 进行排序,占用 CPU较多。如果待排结果较大,会产生临时文件 IO 到磁盘进行排序的情况,效率较低。
优化建议: 
1.SQL中,可以在WHERE 子句和 ORDER BY子句中使用索引,目的是在 WHERE 子句中避免全表扫描,在ORDER BY 子句 避免使用 FileSort 排序。当然,某些情况下全表扫描,或者 FileSort 排序不一定比索引慢。但总的来说,我们还是要避免,以提高查询效率。
2、尽量使用Index 完成ORDER BY排序。如果 WHERE和ORDER BY后面是相同的列就使用单索引列;如果不同就使用联合索引。

(where  a 在orderBy b 之前执行,根据最左前缀原则,建立联合索引(a,b) )
3、无法使用Index时,需要对 FileSot 方式进行调优。

2、测试

#删除表中非主键索引
CALL proc_drop_index('dbtest3','student');
CALL proc_drop_index('dbtest3','class');

过程一 

EXPLAIN SELECT SQL_NO_CACHE * FROM student ORDER BY age,classid;

EXPLAIN SELECT SQL_NO_CACHE * FROM student ORDER BY age,classid limit 10;

没有索引,使用的是Using fileSort 

 过程二

#创建索引
CREATE INDEX idx_age_classid_name ON student (age,classId,NAME);

不限制,索引失效   (注意,这边查的是 * 所有字段,用索引,要回表,入股能索引覆盖,索引就能被用上)

EXPLAIN SELECT SQL_NO_CACHE * FROM student ORDER BY age,classid;

 添加LIMIT 限制,索引生效

EXPLAIN SELECT SQL_NO_CACHE * FROM student ORDER BY age,classid limit 10;

 

过程三:

CREATE INDEX idx_age_classid_name ON student (age,classId,NAME);

CREATE INDEX idx_age_classid_stuno ON student(age,classId,stuno);

order by 顺序错误时,索引失效(其实就是联合索引的 最左前缀原则)

过程四:

Order by 时规则不一致,索引失效(顺序错,不索引;方向反 不索引)



EXPLAIN SELECT * FROM student ORDER BY age DESC,classid ASC LIMIT 10;

EXPLAIN SELECT * FROM student ORDER BY classId DESC,NAME DESC LIMIT 10;

EXPLAIN SELECT * FROM student ORDER BY age ASC,classId DESC LIMIT 10;

EXPLAIN SELECT * FROM student ORDER BY age DESC,classId DESC LIMIT 10;

如果用到了索引,且是降序  EXTRA的值如下

 

 过程5

无过滤,不索引

EXPLAIN SELECT * FROM student WHERE age=45 ORDER BY classid

 key_len =5 说明 索引只用到了 age 并没有使用 classId,优化器认为18500行开销不算多,直接在where剩余内容全部遍历了

注意,此处并不是索引覆盖,或者没有使用limit 我们可以验证一下

EXPLAIN SELECT age,classid FROM student WHERE age=45 ORDER BY classid LIMIT 10;

测试发现 key_len 依然 为 5

 再看这两个情况

EXPLAIN SELECT * FROM student WHERE classId=45 ORDER BY age

 

EXPLAIN SELECT * FROM student WHERE classId=45 ORDER BY age LIMIT 10;

我们发现,下面这种情况居然用上了索引 idex_age_classId_name 

优化器在底层先执行了order by age limit 10  先根据 age排序取了10条记录,再用where classId =45 进行过滤

如果我们建立如下索引

CREATE INDEX idx_cid ON student (classId);
EXPLAIN SELECT * FROM student WHERE classId=45 ORDER BY age

 索引就能被使用上了

 其实用不用索引,具体还是查询优化器来决定

小结:

INDEX a_b_c(a,b,c)


order by 能使用索引最左前缀
- ORDER BY a
- ORDER BY a,b
- ORDER BY a,b,c
- ORDER BY a DESC,b DESC,c DESC

如果WHERE使用索引的最左前缀定义为常量,则order by 能使用索引
- WHERE a = const ORDER BY b,c
- WHERE a = const AND b = const ORDER BY c
- WHERE a = const ORDER BY b,c
- WHERE a = const AND b > const ORDER BY b,c


不能使用索引进行排序
- ORDER BY a ASC,b DESC,c DESC /* 排序不一致 */
- WHERE g = const ORDER BY b,c /*丢失a索引*/
- WHERE a = const ORDER BY c /*丢失b索引*/
- WHERE a = const ORDER BY a,d /*d不是索引的一部分*/
- WHERE a in (...) ORDER BY b,c /*对于排序来说,多个相等条件也是范围查询*/

3、实战

SELECT SQL_NO_CACHE * FROM student WHERE age=30 AND stuno<101000 ORDER BY NAME;

 

优化方案:

方案一:为了去掉fileSort 可以把索引简称

CREATE INDEX idx_age_name ON student(age,NAME);

 解释一下: age name 建立的二级索引 age和 name 都是有序的,此处 keyl_en=5 说明 索引没有用到name字段,但是由于name是有序的,回表之后,不需要在内存中排序,也就没有了filesort,不信,我们可以只建立一个 age索引测试;

 虽然用到了age 索引,但是 name字段没有索引,回表时,name依旧是无序的,需要重排序。

 

其实,并不完全是上面那些话就能解释清楚的,还有一个原因,因为 stuno 没有建立索引字段!!

stuno没有索引字段,如果进行查找,就必须回表,这样会消耗一定的系统性能,优化器在权衡之下,为了让性能得到优化,让NAME,使用了索引,也就是 为啥 idx_age_name 没有用filesort(其实此时 filesort 和索引开销相差并不大)

再看这种情况

CREATE INDEX idx_age_stuno_name ON student(age,stuno,NAME);

  (都0.001了 你还想咋地??)

 这里为什么又用到了filesort呢?? 看似无法理解

key_len =9 说明 联合索引 用到了 age 和 stuno两个字段,两者都在索引里就已经排好序找到内容了,系统性能依据够好,而且,过滤后的结果只有18行,filesort 并不比索引慢,于是数据库底层决定NAME 字段直接将其在内存中使用filesort 排序。

一切都是由于查询优化器的决策

FileSot 排序则一般在 内存中 进行排序,占用 CPU较多。如果待排结果较大,会产生临时文件 IO 到磁盘进行排序的情况,效率较低。  ## 某些情况下 filesort 并不一定比索引慢

 

原因
所有的排序都是在条件过滤之后才执行的。所以,如果条件过滤掉大部分数据的话,剩下几百几千条数据进行排序其实并不是很消耗性能,即使索引优化了排序,但实际提升性能很有限。相对的 stuno<101000 这个条件,如果没有用到索引的话,要对几万条的数据进行扫描,这是非常消耗性能的,所以索引放在这个字段上性价比最高,是最优选择。

结论:
1.两个索引同时存在,mysql自动选择最优的方案。 (对于这个例子,mysql选择idx_age_stuno_name)但是,随着数据量的变化,选择的索引也会随之变化的。
当【范围条件】和【group by 或者 order by]】的字段出现二选一时,优先观察条件字段的过滤数量如果过滤的数据足够多,而需要排序的数据并不多时,优先把索引放在范围字上。反之,亦然。

4 filesort算法: 双路排序和单路排序


排序的字段若如果不在索引列上,则filesort会有两种算法: 双路排序和单路排序

双路排序 (慢)

  • MySQL 4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,读取行指针和order by列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出。
  • 从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段

取一批数据,要对磁盘进行两次扫描,众所周知,IO是很耗时的,所以在mysql4.1之后,出现了第二种改进的算法,就是单路排序。


单路排序 (快)
从磁盘读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出,它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间,因为它把每一行都保存在内存中了。

结论及引申出的问题

  • 由于单路是后出的,总体而言好过双路
  • 但是用单路有问题

              #  在sort_buffer中,单路比多路要 多占用很多空间,因为单路是把所有字段都取出,所以有可能取出的数据的0总大小超出了 sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据,进行排序(创建tmp文件,多路合并),排完再取sort_bufer容量大小,再排......从而多次I/0。
               # 单路本来想省一次I/O操作,反而导致了大量的I/0操作 ,反而得不偿失。

优化策略(filesrot 调优)
1、尝试提高 sort_buffer_size
        不管用哪种算法,提高这个参数都会提高效率,要根据系统的能力去提高,因为这个参数是针对每个进程(connection)的1M-8M之间调整。 MySQL5.7,InnoDB存储引擎默认值是1048576字节,1MB。

 

2.尝试提高 max_length_for_sort_data

  •         提高这个参数,会增加用改进算法的概率        

  •  但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘IO活动和低的处理器使用率。如果需要返回的列的总长度大于max_length_for_sortdata,使用双路算法,否则使用单路算法.1024-8192字节之间调整

3.Order by 时select* 是一个大忌。最好只Query需要的字段。原因:
当Query的字段大小总和小于max_length_for_sort_data ,而且排序字段不是TEXT BLOB 类型时,会用改进后的算法一一单路排序,否则用老算法-一多路排序。
两种算法的数据都有可能超出sort_buffer_size的容量,超出之后,会创建tmp文件进行合并排序,导致多次IO,但是用单路排序算法的风险会更大一些,所以要 提高sort_buffer_size。

 

你可能感兴趣的:(数据库,mysql,数据库,sql)