文章目录
- MySQL——子查询、排序、分组、分页优化
- 1、子查询优化
- 2、ORDER BY排序优化
- 3、filesort算法:双路排序和单路排序
- 4、GROUP BY 分组优化
- 5、分页查询优化
MySQL——子查询、排序、分组、分页优化
1、子查询优化
子查询
当一个查询是另一个查询的子部分是,,称之为子查询(查询语句中嵌套含有查询语句)。子查询也是使用频率比较高的一种查询类型。因此,优化子查询,对于整个系统的性能也有直接的影响。
子查询的位置
在 SELECT 语句中,子查询可以被嵌套在 SELECT 语句的列、表和查询条件中,即 SELECT 子句,FROM 子句、WHERE 子句、GROUP BY 子句和 HAVING 子句。
子查询效率不高的原因
1、执行子查询时,MySQL 需要为内层查询语句的查询结果建立一个临时表,然后外层查询语句从临时表中查询记录(临时表无法建立索引)。查询完毕后,再撤销这些临时表。这样会消耗过多的CPU和IO资源,产生大量的慢查询。
2、子查询的结果集存储的临时表,不论是内存临时表还是磁盘临时表都不会存在索引,所以查询性能会受到一定的影响。
3、对于返回结果集比较大的子查询,其对查询性能的影响也就越大。
优化方向
在MySQL中,可以把子查询改为连接查询,连接查询不用建立临时表,其速度比子查询要快。
例如子查询为:
select s.*
from student s
where s.stuno not in (select monitor from class c where monitor is not null)
修改后的连接查询:
select a.*
from student s left join class c
on s.stuno = c.monitor
where c.monitor is null;
2、ORDER BY排序优化
MySQL支持两种排序方式,分别是 FileSort 和 Index 排序
- Index 排序中,索引可以保证数据的有序性,不需要再进行排序,效率更高
- FileSort 排序一般在内存中进行,占用CPU较多。如果待排结果较大,会产生临时文件I/O到磁盘进行排序的情况,效率较低。
优化建议
1、SQL中,可以在WHERE子句和ORDER BY子句中使用索引,目的是在WHERE子句中避免全表扫描,在ORDER BY子句使用索引避免使用FileSort排序。当然,某些情况下全表扫描,或者FileSort排序不一定比索引慢。但总的来说,我们还是要避免,以提高查询效率。
2、尽量使用Index索引完成ORDER BY排序。如果WHERE和ORDER BY后面是相同的列就使用单索引列;如果不同就使用联合索引。
3、无法使用Index索引时,需要对 FileSort 方式进行调优
实例
创建索引:
create index idx_stuno_name_age on student(stuno,name,age);
只使用 ORDER BY:索引失效,原因:进行回表操作(二级索引中查完需要到聚簇索引中再查一遍)时要排序的数据量太大,效率低,查询优化器会选择在内存中排序的方式
mysql> explain select * from student order by stuno,name;
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+----------------+
| 1 | SIMPLE | student | NULL | ALL | NULL | NULL | NULL | NULL | 499086 | 100.00 | Using filesort |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+----------------+
1 row in set, 1 warning (0.00 sec)
使用 ORDER BY 时加上LIMIT:索引没有失效,原因:对前10条数据使用索引排序再进行回表操作
mysql> explain select * from student order by stuno,name limit 10;
+----+-------------+---------+------------+-------+---------------+--------------------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+--------------------+---------+------+------+----------+-------+
| 1 | SIMPLE | student | NULL | index | NULL | idx_stuno_name_age | 72 | NULL | 10 | 100.00 | NULL |
+----+-------------+---------+------------+-------+---------------+--------------------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
当索引列排序相反时,索引失效
mysql> explain select * from student order by stuno desc ,name asc limit 10;
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+----------------+
| 1 | SIMPLE | student | NULL | ALL | NULL | NULL | NULL | NULL | 499086 | 100.00 | Using filesort |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+----------------+
1 row in set, 1 warning (0.00 sec)
按照索引长度为4可知只使用了stuno列上的索引,因为根据stuno条件过滤筛选出来的数据量不大,可以直接进行回表操作,而不需要使用其他索引
mysql> explain select * from student where stuno = 10 and age < 20 order by name;
+----+-------------+---------+------------+------+--------------------+--------------------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+--------------------+--------------------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | student | NULL | ref | idx_stuno_name_age | idx_stuno_name_age | 4 | const | 1 | 33.33 | Using index condition |
+----+-------------+---------+------------+------+--------------------+--------------------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
当【范围条件】和【group by或者order by】的字段出现创建索引二选一时,优先观察条件字段的过滤数量,如果过滤的数据足够多,而需要排序的数据并不多时,优先把索引放在范围字段上,如:
select * from student where stuno = 10 and age < 20 order by name;
反之,当过滤的数据不足,而需要排序的数据很多时,优先把索引放在排序字段上。
3、filesort算法:双路排序和单路排序
双路排序(又叫回表排序模式,慢)
先根据相应的条件取出相应的排序字段和可以直接定位行数据的行 ID,然后在 sort buffer 中进行排序,排序完后需要再次从磁盘取回其它需要的字段;
取一批数据,要对磁盘进行两次扫描,众所周知,IO是很耗时的,所以在mysql4.1之后,出现了第二种改进的算法,就是单路排序。
单路排序(快)
是一次性从磁盘读取查询满足条件行的所有字段,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出,它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间,因为它把每一行都保存在内存中了。
对比:
其实对比两个排序模式,单路排序会把所有需要查询的字段都放到 sort_buffer中,而双路排序只会把主键 和需要排序的字段放到 sort buffer 中进行排序,然后再通过主键回到原表查询需要的字段。
单路存在的问题
在sort_buffer中,单路比多路要多占用很多空间,因为单路是把所有字段都取出,所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据,进行排序(创建tmp文件,多路合并),排完再取sort_buffer容量大小,再排…从而多次I/O。
单路本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失。
单路优化方式一:提高sort_buffer_size
InnoDB存储引擎默认值是1MB:
mysql> show variables like '%sort_buffer_size%';
+-------------------------+---------+
| Variable_name | Value |
+-------------------------+---------+
| innodb_sort_buffer_size | 1048576 |
| myisam_sort_buffer_size | 8388608 |
| sort_buffer_size | 262144 |
+-------------------------+---------+
3 rows in set (0.00 sec)
单路优化方式二:提高max_length_for_sort_data
max_length_for_sort_data默认值1024字节
mysql> show variables like '%max_length_for_sort_data%';
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| max_length_for_sort_data | 1024 |
+--------------------------+-------+
1 row in set (0.00 sec)
如果需要返回的列的总长度大于max_length_for_sort_data,使用双路排序算法,否则使用单路排序算法。
order by时select *是一个大忌。最好只Query需要的字段。原因:
当Query的字段大小总和小于max_length_for_sort_data,而且排序字段不是TEXT|BLOB类型时,会用改进后的算法——单路排序,否则用老算法――多路排序。
两种算法的数据都有可能超出sort_buffer_size的容量,超出之后,会创建tmp文件进行合并排序,导致多次IO,但是用单路排序算法的风险会更大一些,所以要提高sort_buffer_size。
4、GROUP BY 分组优化
group by使用索引的原则几乎跟order by一致,group by即使where过滤条件没有用到索引,也可以直接使用索引,但是实际上是否使用索引还是基于查询优化器的选择
group by先排序再分组,遵照索引建的最佳左前缀法则
当无法使用索引列,增大max_length_for_sort_data和sort_buffer_size参数的设置
where效率高于having,能写在where限定的条件就不要写在having中了
减少使用order by,和业务沟通能不排序就不排序,或将排序放到程序端去做。Order by、group by、distinct这些语句较为耗费CPU,数据库的CPU资源是极其宝贵的。
包含了order by、group by、distinct这些查询的语句,where条件过滤出来的结果集请保持在1000行以内,否则SQL会很慢。
5、分页查询优化
一般分页查询时,通过创建覆盖索引能够比较好地提高性能。
一个常见又非常头疼的问题就是limit 2000000,10,此时需要MysQL排序前2000010记录,仅仅返回2000000-2000010的记录,其他记录丢弃,查询排序的代价非常大
select * from student limit 2000000,10;
优化思路一
在索引上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容
select * from student t ,(select id from studnet order by id limit 2000000,10) a
where t.id = a.id;
优化思路二:
该方案适用于主键自增的表,可以把Limit查询转换成某个位置的查询。
select * from student where id > 2000000 limit 10;