sql语句按照指定的字段进行排序是查询数据时是一个很常见的操作。当涉及到大量数据时,对于 ORDER BY 操作,可以考虑为相应的列添加索引,如果不使用索引,mysql会使用filesort来进行排序。
filesort虽然有file,但是不一定是文件排序,要分情况。下面来看下排序的具体逻辑。
filesort会将查询行数据放入sort_buffer中,然后按排序字段进行排序。sort_buffer的大小有变量sort_buffer_size来控制,默认大小256kb。
mysql> SELECT @@sort_buffer_size;
+--------------------+
| @@sort_buffer_size |
+--------------------+
| 262144 |
如果要排序的数据内容小于sort_buffer_size,排序在内存中即可完成;否则filesort会使用临时文件进行排序。数据越多生成的临时文件越多,每份文件单独排序后再归并合并成一个有序的结果。
临时文件存放再 tmpdir 变量指定的目录下,排序完成后会自动删除。
mysql> select @@tmpdir;
+----------+
| @@tmpdir |
+----------+
| /tmp |
如果使用了filesort,在explain的Extra会显示:Using filesort。是否使用了临时文件还需要根据具体的执行过程来判断。下面通过information_schema.OPTIMIZER_TRACE表来查看是否使用临时文件。
1、准备一个t_user表,首先开启optimizer_trace
mysql> SET optimizer_trace='enabled=on';
2、然后执行查看explain这里再Extra会有Using filesort。
mysql> explain select * from t_user order by username;
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | t_user | NULL | ALL | NULL | NULL | NULL | NULL | 2700 | 100.00 | Using filesort |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+----------------+
1 row in set, 1 warning (0.00 sec)
3、执行查询
select * from t_user order by username;
注意这里是执行查询,不是explain,否则下面一步看不到信息
4、查看OPTIMIZER_TRACE
SELECT * FROM information_schema.OPTIMIZER_TRACE;
OPTIMIZER_TRACE一共有4列:
QUERY:表示当前查询语句
TRACE: 包含查询优化器的trace信息,json格式
MISSING_BYTES_BEYOND_MAXMEM:丢失字节数。
在trace内容中有“filesort_summary”部分是关于filesort处理信息,如上面查询对应的filesort_summary
mysql8:
"filesort_summary": {
"memory_available": 262144,
"key_size": 481,
"row_size": 6591,
"max_rows_per_buffer": 39,
"num_rows_estimate": 2700,
"num_rows_found": 2800,
"num_initial_chunks_spilled_to_disk": 8,
"peak_memory_used": 264192,
"sort_algorithm": "std::sort",
"sort_mode": ""
}
mysql5.7:
"filesort_summary": {
"rows": 2800,
"examined_rows": 2800,
"number_of_tmp_files": 6,
"sort_buffer_size": 261784,
"sort_mode": ""
}
这里看到5.7和8差别还是有点大,但是几个主要的字段还是差不多的。这里以5.7的trace信息来看。
sort_buffer_size是sort_buffer的大小。
rows:数据行数
number_of_tmp_files:临时文件数,如果该值为0,则表示未使用临时文件,sort_buffer够用。
sort_mode:排序方式。这个指定了参与排序的数据内容不同。
sort_key, rowid:sort_buffer中加载的数据只有排序字段(sort_key)和rowid,rowid用来排序后再回表查询获取行数据。
sort_key, additional_fields:sort_buffer中加载数据包含所有的要查询的字段。
sort_key, packed_additional_fields:和上面的additional_fields差不多,只是有些可变长度会进行压缩。
那么sort_mode这两种类型一种有所有查询字段,一种只有排序字段,查询优化器是根据什么选择的呢。这里有一个参数max_length_for_sort_data,如果查询的数据行记录超过该值,则会采用sort_key, rowid模式,否则会采用sort_key, additional_fields模式。不过这个值在MySQL 8.0.20被标为过时了,建议通过调整sort_buffer_size大小来控制join_buffer大小,尽量避免使用磁盘临时文件。这两种排序模式比较:additional_fields模式不需要回表,如果查询的列比较多,可能会导致sort_buffer所能容纳的行数据变少;rowid模式每行数据很小,sort_buffer可以加载更多的行,但是最后返回数据要回表。不同数据量场景,要合理设置sort_buffer_size和max_length_for_sort_data搭配。
这里看到排序的过程,一行行拿出来进行比较,数据量大还会使用到临时文件,还是比较耗时的。那么有没有更快的方法呢?那就是使用索引。
为什么使用索引会快?因为构建后的索引就是天然有序的,不需要再经过一行行逐一对sort_key进行比较。跳过额外的filesort。
排序使用索引的情况一般在Extra中只有Using index。使用索引一般场景:
1、查询条件中有排序索引
2、索引覆盖,查询的列都在对应的索引中
3、多列索引排序,满足最左匹配
4、排序方向一致
这只是一般的规则,下面结合几个具体的例子来看看。
t_user表上有复合索引 (username,gender,department) 还有索引(phone)
例1:
SELECT uid,phone FROM t_user ORDER BY phone;
查询列都在索引上(uid是主键),Using index
例2:
SELECT * FROM t_user ORDER BY phone;
索引不包含查询访问的所有列,则仅当索引访问比其他访问方法更高效时才使用该索引.这里查询优化器选择了Using filesort。 不太理解?
例3:
SELECT uid,phone FROM t_user WHERE uid<20 ORDER BY phone;
虽然查询内容是索引覆盖,但是where条件不在索引上(不是同一个索引),Using filesort。
这个应该可以理解一个索引筛选出来的数据内容对另一个索引是无序的。
例4:
SELECT username,gender FROM t_user ORDER BY username,gender;
SELECT username,gender FROM t_user WHERE username='a' ORDER BY gender;
查询内容索引覆盖,where满足索引最左匹配部分,Using index。
例5:
SELECT username,gender FROM t_user WHERE username LIKE 'a%' ORDER BY username desc,gender ;
多列排序,第一列排序username满足使用索引条件,gender和username排序方向相反,无法使用索引,会进行依次filesort。
例6:
SELECT username,gender FROM t_user WHERE username LIKE 'a%' ORDER BY username,department ;
username排序使用filesort,department排序不满足最左匹配,中间复合索引断开(缺少gender)使用filesort。
这里只看了几个常见例子,可能有些场景比这复杂的多。想一想能使用到索引排序一个最基本的条件:当前要查询的数据范围(where筛选后)在排序列(对应的索引)是有序的。然后才考虑覆盖索引等其它需要满足的条件。