• (七)group by 的优化


    ---
    title: 不懂SQL优化?那你就OUT了(七)

    MySQL如何优化--group by

    date: 2018-12-15

    categories: 数据库优化
    ---

    上一篇我们主要讨论了 order by 的优化,这一篇我们继续讨论 group by 的优化。

    group by子句在进行分组时一般的方法(如果没有合适的索引可用)是先扫描整个表并创建一个新的临时表,然后按照group by 指定的列进行排序, 在创建的临时表中每个组的所有行应为连续的,然后使用该临时表来找到组并执行聚合函数(如果有)。

    例如:(与上一篇 测试 order by 数据一样)

    CREATE TABLE t_testGroupBy(

    userid INT PRIMARY KEY AUTO_INCREMENT, -- 用户编号

    username VARCHAR(25), -- 用户姓名

    userAge INT, -- 用户年龄

    usergender CHAR(3), -- 用户性别

    provice VARCHAR(25), -- 所在省份

    city VARCHAR(25), -- 所在城市

    address VARCHAR(200) -- 详细地址
    );

    测试数据:


    INSERT INTO t_testGroupBy VALUES(NULL,'张三',18,'男','四川省','成都市','xxxx路222号');
    INSERT INTO t_testGroupBy VALUES(NULL,'李四',20,'女','云南省','昆明市','xxx北路12号');
    INSERT INTO t_testGroupBy VALUES(NULL,'王五',24,'男','贵州省','遵义市','xxxxx路18号');
    INSERT INTO t_testGroupBy VALUES(NULL,'赵六',19,'女','四川省','绵阳市','xx路234号');
    INSERT INTO t_testGroupBy VALUES(NULL,'孙琦',28,'男','云南省','玉溪市','xxxx路324号');
    INSERT INTO t_testGroupBy VALUES(NULL,'王晓琪',21,'女','云南省','玉溪市','xxxx路123号');


    建立索引:


    CREATE INDEX idx_name ON t_testgroupby(usergender);

    CREATE INDEX idx_age ON t_testgroupby(userAge);

    CREATE INDEX idx_provice_city_address ON t_testgroupby(provice,city,address);


    例如:

    查询 男学生和女学生的总人数


    可以从上图中看到在进行group by 操作时,创建了临时表和使用了文件排序,这样的sql语句执行效率是很低的,需要优化。

    使用group by查询结果集时速度慢的原因可能有以下几种:

    1. 分组字段不在同一张表中

    2. 分组字段没有建索引

    3. 分组字段导致索引没有起作用(如何让索引起作用才是关键)

    4. 分组字段中使用聚合函数导致索引不起作用


    在某些情况中,mysql能够通过使用索引访问而不用创建临时表。

    group by 与 order by 相比,group by主要只是多了排序之后的分组操作,如果在分组的时候还使用了其他的一些聚合函数,那么还需要执行一些聚合函数的计算。所以,在 group by 的实现过程中,与order by 一样也可以利用到索引。

    <font style="color:coral">group by 使用索引的最重要的前提条件是所有group by列引用同一索引,并且索引按顺序保存其关键字(例如,这是B+树索引(有序的),而不是HASH索引(没有顺序的))</font>。

    group by 是否使用索引访问来代替临时表的使用还取决于在查询中使用了哪部分索引、为该部分指定的条件,以及选择的聚合函数。

    有两种方法通过索引访问执行group by的查询:

    在第1个方法(<font style='color:coral'>松散索引扫描</font>)中,分组操作和范围预测(如果有的话)一起执行完成的.

    第2个方法(<font style='color:coral'>紧凑索引扫描</font>))首先执行范围扫描,然后组合结果元组(数据)。


    ###松散索引扫描(Loose Index Scan)


    官网给出的定义:


    > The most efficient way to process GROUP BY is when an index is used to directly retrieve the grouping columns. With this access method, MySQL uses the property of some index types that the keys are ordered (for example, BTREE). This property enables use of lookup groups in an index without having to consider all keys in the index that satisfy all WHERE conditions. This access method considers only a fraction of the keys in an index, so it is called a Loose Index Scan.

    > When there is no WHERE clause, a Loose Index Scan reads as many keys as the number of groups, which may be a much smaller number than that of all keys. If the WHERE clause contains range predicates (see the discussion of the range join type in Section 8.8.1, “Optimizing Queries with EXPLAIN”), a Loose Index Scan looks up the first key of each group that satisfies the range conditions, and again reads the smallest possible number of keys

    翻译:

    处理分组最有效方法是使用的索引直接跟分组列有关。使用这种访问方法,MySQL使用一些索引类型的属性(关键字),这些索引的类型键是有序的(理解的:这些关键字已经排好序)(例如,BTREE),该属性允许在分组时使用,而不必考虑满足所有WHERE条件的索引中的所有键(理解的:这使得索引中用于分组的字段不必完全涵盖WHERE条件中索引对应的key)。这种访问方法只考虑索引中键的一部分,因此称为松散的索引扫描。


    当没有WHERE子句时,松散的索引扫描读取的键与组的数量相同,组的数量可能比所有键的数量小得多.
    如果WHERE子句包含范围判断(请参阅8.8.1节“使用EXPLAIN优化查询”中关于范围连接类型的讨论),那么松散的索引扫描将查找满足范围条件的每个组的第一个键,并再次读取可能的最小键数。

    #####使用松散索引扫描需要满足以下条件:

    1. 查询针对一个单表。

    2. group by 包括索引的第1个连续部分(使用联合索引时,必须满足最左前缀原则)(如果对于GROUP BY,查询有一个DISTINCT子句,则所有显式属性指向索引开头)。

    3. 在使用group by 的同时,如果有聚合函数,只能使用 MAX 和 MIN 这两个聚合函数,并且它们均指向相同的列。

    4.如果引用(where条件中)到了该索引中GROUP BY 条件之外的字段条件的时候,必须以常量形式存在,但MIN()或MAX() 函数的参数例外。

    补充:如果sql中有where子句,且select中引用了该索引中group by 条件之外的字段条件的时候,where中这些字段要以常量形式存在。如果查询中有where条件,则条件必须为索引,不能包含非索引的字段。

    此类查询的EXPLAIN输出显示Extra列的Using indexforgroup-by。

    以下是官网给出的案列:

    下面的查询提供该类的几个例子,假定表t1(c1,c2,c3,c4)有一个索引idx(c1,c2,c3):

    1. SELECT c1, c2 FROM t1 GROUP BY c1, c2;

    2. SELECT DISTINCT c1, c2 FROM t1;



    3.SELECT c1, MIN(c2) FROM t1 GROUP BY c1;

    4.SELECT c1, c2 FROM t1 WHERE c1 < const GROUP BY c1, c2;

    5.SELECT MAX(c3), MIN(c3), c1, c2 FROM t1 WHERE c2 > const GROUP BY c1, c2;

    6.SELECT c2 FROM t1 WHERE c1 < const GROUP BY c1, c2;

    7.SELECT c1, c2 FROM t1 WHERE c3 = const GROUP BY c1, c2;


    不能使用松散索引扫描执行下面的查询:

    1. 除了MIN()或MAX(),还有其它聚合函数,

    例如:SELECT c1, SUM(c2) FROM t1 GROUP BY c1;



    2. GROUP BY子句中的域不引用索引开头(未使用最左前缀),如下所示:

    例如:SELECT c1,c2 FROM t1 GROUP BY c2, c3;



    3. 查询引用了GROUP BY部分后面的关键字的一部分,并且没有等于常量的等式,例如:

    例如:SELECT c1,c3 FROM t1 GROUP BY c1, c2;(在其他数据库中 group by中没有的列不能出现在select 中)

    ###紧凑索引扫描(Tight Index Scan)

    > A Tight Index Scan may be either a full index scan or a range index scan, depending on the query conditions.

    When the conditions for a Loose Index Scan are not met, it still may be possible to avoid creation of temporary tables for GROUP BY queries. If there are range conditions in the WHERE clause, this method reads only the keys that satisfy these conditions. Otherwise, it performs an index scan. Because this method reads all keys in each range defined by the WHERE clause, or scans the whole index if there are no range conditions, it is called a Tight Index Scan. With a Tight Index Scan, the grouping operation is performed only after all keys that satisfy the range conditions have been found.

    翻译:

    紧凑索引扫描可以为索引扫描或一个范围索引扫描,取决于查询条件。

    当松散索引扫描条件没有满足的时候,group by仍然有可能避免创建临时表。如果where条件有范围扫描,那么紧凑索引扫描仅读取满足这些条件的keys(索引键),否则执行全索引扫描。这种方式读取所有where条件定义的范围内的索引键,或者扫描整个索引,因而称作紧凑索引扫描。请注意对于紧凑式索引扫描,只有找到了满足范围条件的所有关键字后才进行组合操作。


    在 mysql 中,mysql query Optimizer 首先会选择尝试通过松散索引扫描来实现 group by 操作,当发现某些情况无法满足松散索引扫描实现 group by 的要求之后,才会尝试通过紧凑索引扫描来实现。


    以下查询不适用于之前描述的松散索引扫描访问方法,但仍然可以使用紧凑索引扫描访问方法。

    1.GROUP BY有一个间隔,但它被覆盖的条件为c2 = ‘a’

    例如:SELECT c1,c2,c3 FROM t1 WHERE c2 = 'a' GROUP BY c1,c3;



    2.GROUP BY不以关键字的第1个元素开始(没有遵循最左前缀原则),但是有一个条件提供该元素的常量:

    例如:SELECT c1,c2,c3 FROM t1 WHERE c1 = 'a' GROUP BY c2,c3;


    当 group by 条件字段并不连续或者不是索引前缀部分的时候,mysql查询优化器无法使用松散索引扫描,所以无法直接通过索引完成group by操作,因为缺失的索引键信息无法得到。但是,如果查询语句中存在一个常量值来引用缺失的索引键,则可以使用紧凑索引扫描完成分组操作,因为常量填充了搜索关键字中的“间隔”,可以形成完整的索引前缀,这些索引前缀可以用于索引查找。

  • 相关阅读:
    序列化与反序列化
    JAVA常用设计模式(一、抽象工厂模式)
    JAVA基础部分复习(七、JAVA枚举类型使用)
    JAVA常用设计模式(一、单例模式、工厂模式)
    JAVA高级篇(二、JVM内存模型、内存管理之第一篇)
    JAVA高级篇(一、JVM基本概念)
    linux常用命令
    JAVA基础部分复习(六、常用关键字说明)
    JAVA基础部分复习(五、JAVA反射)
    JAVA基础部分复习(三、泛型)
  • 原文地址:https://www.cnblogs.com/ysviewvicn/p/10288327.html
Copyright © 2020-2023  润新知