mysql-group by 优化

group by

group by 操作过程是扫描全表(没有索引的情况),每一个分组都在内存表中创建一行记录,扫描表时会更新临时表中的记录,如果内存表占用完那么就把临时表刷到磁盘中。

测试表:
CREATE TABLE tb_a(
`id` INT(11) NOT NULL AUTO_INCREMENT,

`user_id` INT(11) NOT NULL)

1、取消group by 的默认排序,要求mysql 只使用临时表处理分组。

在user_id 上没有索引,查询计划如下:

sql:
EXPLAIN SELECT user_id,COUNT(1) FROM tb_a
GROUP BY user_id

执行计划:


查询的是全表扫描,使用到了文件排序和临时表。


group by 默认是要对结果进行排序的,可以使用group by null 不进行排序,执行计划如下:


sql:
EXPLAIN SELECT user_id,COUNT(1) FROM tb_a
GROUP BY user_id
ORDER BY NULL
执行计划:


sql 中加入了order by null 强制mysql group by 使用临时表。

2、使用索引
在user_id 上创建索引:
ALTER TABLE  tb_a ADD INDEX user_id_index (user_id);

sql:
EXPLAIN SELECT user_id,COUNT(1) FROM tb_a
GROUP BY user_id
#ORDER BY NULL
执行计划:


执行计划中,查询类型是index,并且没有使用到临时表和文件排序,因为索引是已经排序的(并且是按照组排序的),并且使用了覆盖索引查询的数据都在索引中,不会有随机I/O。
所以此sql 中加或者不加order by null 执行计划是一样的。


删除 user_id_index 索引,为下面测试使用。
ALTER TABLE tb_a DROP INDEX user_id_index;



3、如果分组没有可以用到索引或者要查询除了索引外其它的数据会产生大量的随机I/O(这种情况是没有用到覆盖索引),那么可以根据分组数据大小使用临时表或者文件排序:

处理分组时,每个分组先在临时表中创建一行记录,扫描全表时会一行一行处理,并把临时表中的行更新。如果数据量大(可以配置临时表的大小)的话就会产生磁盘I/O。这种情况直接使用文件排序会更有优势。


3.1 分组数据小,使用临时表

sql:
EXPLAIN SELECT SQL_SMALL_RESULT  user_id,COUNT(1) FROM tb_a
GROUP BY user_id

如果临时空间设置的比较小,那么不会使用文件排序的。执行计划中同样使用了临时表和文件排序,求指点。

SQL_SMALL_RESULT
官网解释如下:
can be used with GROUP BY or DISTINCT to tell the
  optimizer that the result set is small. In this case, MySQL uses
  fast temporary tables to store the resulting table instead of using
  sorting. This should not normally be needed.


3.2 分组数据大,使用文件排序
sql:
EXPLAIN SELECT SQL_BIG_RESULT  user_id,COUNT(1) FROM tb_a
GROUP BY user_id
执行计划:


sql 中使用了 SQL_BIG_RESULT hint,让msyql 选择文件排序,这样就避免先在临时表中生成数据,再把临时表数据刷到磁盘中。


4  skip-scan
上面方法都合适其它的集合函数,如果是min()/max() 则要使用skip-scan 优化了,这个优化必须每个组有大量的数据的话才适用,否则mysql 不会进行skip-can 优化的。
EXPLAIN SELECT user_id, MAX(id) FROM tb_b

GROUP BY user_id




猜你喜欢

转载自blog.csdn.net/convict_eva/article/details/79639414