GROUP BY优化

本文翻译自MySQL 8.0 Reference Manual 8.2.1.17 GROUP BY Optimization

执行group by语句最一般的方式就是扫描整张表,并且创建一张临时表来保存所有的分组字段值,然后通过这张临时表整理分组并执行聚集函数。在某些情况下MySQL可以通过使用索引访问避免创建临时表,依次提高查询性能。

通过索引来执行group by查询的一个最重要的前提条件是group by子句中所有的列都来自同一个索引,并且该索引按顺序保存列值(比如BTREE索引,HASH索引则不满足此约束)。是否使用索引访问而不是临时表也取决于使用索引中的哪些列,使用的条件类型以及使用的聚集函数等。

MySQL支持两种访问方式来通过索引执行group by语句,下面的小结将进行详细介绍。第一种方法在所有的范围条件上执行分组操作,第二种方法首先进行范围扫描,然后对获得的元组进行分组操作。

  • Loose Index Scan(松散的索引扫描)
  • Tight Index Scan(紧凑的索引扫描)

下面将详细介绍着两种索引扫描方法。

  1. Loose Index Scan(松散的索引扫描)
    处理group by最高效的方式就是使用索引直接获取分组的列。当使用索引访问方法时,MySQL利用索引按照顺序组织列值的特性(比如BTREE索引)。这种特性可以被用来查找索引中满足条件的分组而不需要访问索引中的所有值。这种访问方法仅仅考虑索引中的部分值,所以称为松散的索引扫描(Loose Index Scan)。当查询语句中没有任何条件子句时,松散的索引扫描仅仅读取索引中分组数量个索引值,而不需要读取索引中的所有值,这大大地减少了读取的索引值数据。如果WHERE子句中包含范围条件子句(jion type为range),松散的索引扫描会读取满足条件的所有分组中的第一个key值,然后尽可能少的读取其他的key值。

要使用松散的所有扫描,必须满足如下条件:

  • 查询仅仅在一张表上进行。

  • group by使用的列必须来自一个索引的最左前缀列,并且没有其他列(除了group by,对于有DISTINCT子句的查询,当所有的distinct列都来自一个索引的最左前缀列时,也满足条件)。例如,如果表t1(c1, c2, c3)上定义了索引,可以对GROUP BY c1, c2使用松散的索引扫描,但是对于GROUP BY c2, c3却不可以使用松散的所有扫描,因为group by子句中的列不是索引的最左前缀列,语句GROUP BY c1, c2, c4也不能使用松散的所有扫描,因为c4不是索引中的列。

  • 查询中只能使用MIN()或者MAX()聚集函数,并且查询中使用的多个聚集函数只能指定相同的列。聚集函数中使用的列必须是使用的索引中的列,并且是GROUP BY子句中出现的第一个列。(译者注:这条约束可以从BTREE索引的实现原理上理解)。

  • 索引中其他没有在GROUP BY子句中指定的列必须是常数(即必须和常数相等),除非是用于聚集函数MIN()或者MAX()的列。

  • 对于索引中的列,这些列必须是完整索引,而不是仅仅对列的部分值进行索引。例如:对于列c1 VARCHAR(20), INDEX(c1(10)),这个索引仅仅使用了c1列的前面10个字符进行索引,因此不能使用松散的索引扫描进行优化。

如果可用使用松散的索引扫描处理查询语句,那么EXPLAINExtra会显示Using index for group-by

假设在表t1(c1, c2, c3, c4)有索引idx(c1, c2, c3),下面的查询语句可以使用松散的索引扫描:

SELECT c1, c2 FROM t1 GROUP BY c1, c2;
SELECT DISTINCT c1, c2 FROM t1;
SELECT c1, MIN(C2) FROM t1 GROUP BY c1;
SELECT c1, c2 FROM t1 WHERE t1 < const GROUP BY c1, c2;
SELECT MAX(C3), MIN(C3), c1, c2 FROM t1 WHERE c2 > const GROUP BY c1, c2;
SELECT c2 FROM t1 WHERE c1 < const GROUP BY c1, c2;
SELECT c1, c2 FROM t1 WHERE c3 = const GROUP BY c1, c2;

下面的语句则不能使用松散的索引扫描:

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

上述语句使用了SUM()函数,而松散的索引扫描只支持MIN()MAX()函数。

SELECT c1, c2 FROM t1 GROUP BY c2, c3;

上述语句GROUP BY子句中的列不是索引的最左前缀列。

SELECT c1, c3 FROM t1 GROUP BY c1, c2;

上述语句中使用了同一索引中没有用于GROUP BY子句中的列,但是该列没有和常数相等的条件。如果查询有WHERE c3 = const的条件子句,则可以使用松散的索引扫描。

某些情况下,除了MIN()MAX()函数,松散的索引扫描也可以使用其他的聚集函数:

  • AVG(DISTINCT)SUM(DISTINCT)以及COUNT(DISTINCT)也是可以支持的,其中AVG(DISTINCT)SUM(DISTINCT)只支持单个参数,而COUNT(DISTINCT)则支持多个列作为参数。
  • 查询语句中不能有GROUP BYDISTINCT子句。

假设在表t1(c1, c2, c3, c4)有索引idx(c1, c2, c3),下面的查询语句可以使用松散的索引扫描:

SELECT COUNT(DISTINCT c1), SUM(DISTINCT c1) FROM t1;

SELECT COUNT(DISTINCT c1, c2), COUNT(DISTINCT c2, c1) FROM t1;
  1. Tight Index Scan(紧凑的索引扫描)

紧凑的索引扫描可以是全索引扫描或者是范围索引扫描,这取决于查询的条件。

当松散索引扫描的条件没有满足时,仍然有避免创建临时表处理GROUP BY查询的可能。如果WHERE子句中有范围条件,这种访问方法仅仅读取那些满足条件的keys,否则会进行一次索引扫描。因为这种访问方法读取满足WHERE子句条件的所有keys或者当没有WHERE子句时则读取所有的keys,因此称其为紧凑的索引扫描。在使用紧凑的索引扫描时,仅当所有满足条件的keys都被读取之后才会进行分组操作(译者注:松散的索引扫描在扫描(或者读取)的过程中就进行了分组)。

为了使用紧凑的索引扫描,在GROUP BY子句之前或之间的所有列都具有等于常数的条件表达式即可,即需要等于常数的条件来填充所有不满足最左前缀列的约束即可。这些被填充、没有间隙的索引可以被用来进行数据查找。如果GROUP BY需要排序,则MySQL也可以通过此方法避免额外的排序操作。

假设在表t1(c1, c2, c3, c4)有索引idx(c1, c2, c3)下面的查询不能使用松散的索引扫描,但是依然可以使用紧凑的索引扫描:

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

上述语句中在使用索引时具有“间隙”,但是可以被条件c2 = 'a'填充这个间隙。

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

上述语句没有满足最左前缀索引列的条件,但是条件c1 = 'a'使得使用的索引的最左列被覆盖。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容