# 第五节 排序分组优化
# 1、排序
# ①结论
order by 子句需要配合 limit 子句才能让索引生效。
# ②测试基本情况
索引情况:
# [1]只有 order by 的情况
分析语句:
EXPLAIN SELECT SQL_NO_CACHE * FROM emp ORDER BY age;
分析结果:
# [2]配合 limit 子句的情况
分析语句:
EXPLAIN SELECT SQL_NO_CACHE * FROM emp ORDER BY age limit 10;
# ③测试排序方向的影响
删除上例索引:
drop index idx_age on emp;
创建新索引:
create index idx_age_deptId on emp(age, deptId);
分析语句:排序方向一致的情况
EXPLAIN SELECT SQL_NO_CACHE * FROM emp ORDER BY age desc,deptId desc limit 10;
分析结果:
分析语句:排序方向不一致的情况
EXPLAIN SELECT SQL_NO_CACHE * FROM emp ORDER BY age desc,deptId asc limit 10;
分析结果:
# 2、双路排序和单路排序
# ①filesort
如果 order by 指定的字段没有建立索引,此时 MySQL 就无法在内存完成排序了,而是执行 filesort——也就是操作硬盘完成排序。
执行 filesort 又分两种不同情况:
- 双路排序:
- 特征:两批 I/O
- 对应的 MySQL 版本:4.1 之前
- 大致工作机制:
- 第一次读取硬盘:读取『行指针』以及『order by 子句指定的字段』
- 排序:对已读取的『order by 子句指定的字段』进行排序
- 第二次读取硬盘:根据『行指针』读取 select 子句中指定的其他字段
- I/O方式:随机 I/O
- 单路排序:
- 特征:一批 I/O
- 对应的 MySQL 版本:4.1 之后
- 大致工作机制:
- 读取硬盘:读取 select 子句指定的所有列
- 排序:按照 order by 列在 buffer 中对它们进行排序
- I/O方式:顺序I/O
# ②单路排序需要注意的问题
单路排序在内存的缓冲区中执行排序,所以需要更大的内存空间。我们管这个缓冲区叫:sort_buffer。此时需要注意:如果为了排序而取出的数据体积大于 sort_buffer,这就会导致每次只能取 sort_buffer 容量大小的数据。所以这种情况下,数据的加载和排序是分段完成的。在这个过程中,MySQL 会创建临时文件,最后再把每段数据合并到一起。
所以 sort_buffer 容量太小时会导致排序操作产生多次 I/O。单路本来想省一次 I/O 操作,反而导致了大量的 I/O 操作,反而得不偿失。
# ③单路排序的优化
调整下面的三个参数:
参数名称 | 参数含义 | 调整建议 |
---|---|---|
sort_buffer_size | 单路排序缓冲区的大小 | 适当增大 |
max_length_for_sort_data | select 子句要查询的所有字段的总宽度和该参数比较: 大于该参数:使用双路排序 小于等于该参数且排序字段不是TEXT、BLOB类型:使用单路排序 | 适当增大 |
select 子句中查询的字段 | 尽量减少 |
TIP
对 sort_buffer_size 的补充说明:
不管用哪种算法,提高这个参数都会提高效率,要根据系统的能力去提高,因为这个参数是针对每个进程(connection)的 1M-8M之间调整。 MySQL5.7,InnoDB 存储引擎默认值是 1048576 字节,1MB。
对 max_length_for_sort_data 的补充说明:
max_length_for_sort_data 不能设的太高,否则数据总容量超出 sort_buffer_size 的概率就增大。明显症状是高的磁盘 I/O 活动和低的处理器使用率。建议设置在 1024-8192 字节之间。
最终目标:
- 尽量能够基于索引执行排序
- 如果确实要执行 filesort,那么尽量使用单路排序,而且尽量让单路排序只做一次 I/O。
# ④select * 会影响排序
在实际业务功能开发过程中,禁止在 select 子句中使用 * 号代表全部字段。如果确实需要查询全部字段,那就把全部字段都写明。其实这个时候更要注意的是:是不是真的要查全部字段。
具体从 SQL 优化的角度来说,select * 会导致我们加载很多没有创建索引的字段到内存中,增加了数据体积超过 sort_buffer_size 的风险。有可能会导致单路排序变成双路排序,性能下降。
# 3、GROUP BY优化
group by 使用索引的原则几乎跟 order by 一致 ,唯一区别:
group by 先排序再分组,同样遵照最左原则
当无法使用索引列,增大 max_length_for_sort_data 和 sort_buffer_size 参数的设置
where 高于 having,能写在 where 限定的条件就不要写在 having 中了
举个例子帮助大家理解:
- 假设有 100W 条记录待筛选
- 有一个 X 条件能够过滤掉 90W 条
- X 条件用在 where 子句:后续 GROUP BY 操作针对 10W 条数据操作
- X 条件用在 having子句:后续 GROUP BY 操作还是针对原来的 100W 条数据操作
所以在整个 SQL 查询语句中,能够将数据过滤掉的条件在不影响查询结果的前提下都要尽早使用,尽早过滤数据,缩小要操作的数据量,让后续操作减轻负担。