Group by隐式排序,一个优美的BUG

  你在使用MySQL的Group by分组时,是否发现分组后的数据都是有序的?其实,在MySQL8.0版本前,优化器在分组查询时都会进行隐式排序。那既然隐式排序为何还要保留Order by?隐式排序的目的又是什么呢?让咱们一块儿来看看。mysql

 

1、背景小故事

  年前咱们换了领导。俗话说,新官上任干总爱干傻事儿,这不,领导要拥抱新事物,要求咱们更新项目MySQL版本,从MySQL5.7更新到MySQL8.0。不知是MySQL5.7不香了,仍是领导你眼光高了?算法

  我把这个任务交给同事小王,小王不觉得然,说换就换。迁完库,在代码基本不改的状况下自信上线。上线后却发现本来一些有序的列表变无序了,最后临时回退了版本。sql

在这里插入图片描述

  核对代码时咱们发现,老版本Select语句中只是用到了Group by分组,也没有用到order by排序,有点蒙,为啥没用order by却排了序?查资料后得知,在MySQL8.0版本前是存在Group by隐式排序的! 就是说在咱们使用分组(Group by)时,如:select * from T group by appName; 会默认按照appName正序排序,至关于select * from T group by appName order by appName;,倒排同理:select * from T group by appName desc; 可见,MySQL在8.0版本前的分组查询中,偷偷加上了排序操做。数据库

  纳尼?MySQL还有这种操做?快找一下官方文档对Group by隐式排序的介绍:app

官方文档

官方文档MySQL 5.7 Reference Manual中的“2.1.14 ORDER BY Optimization”章节有以下介绍:ide

GROUP BY implicitly sorts by default (that is, in the absence of ASC or DESC designators for GROUP BY columns). However, relying on implicit GROUP BY sorting (that is, sorting in the absence of ASC or DESC designators) or explicit sorting for GROUP BY (that is, by using explicit ASC or DESC designators for GROUP BY columns) is deprecated. To produce a given sort order, provide an ORDER BY clause.测试

google翻译:默认状况下GROUP BY隐式排序(即,缺乏GROUP BY列的ASC或DESC指示符)。可是,不推荐依赖于隐式GROUP BY排序(即,在没有ASC或DESC指示符的状况下排序)或GROUP BY的显式排序(即,经过对GROUP BY列使用显式ASC或DESC指示符)。要生成给定的排序 ORDER,请提供ORDER BY子句。优化

  从MySQL 8.0开始,GROUP BY字段再也不支持隐式排序. 官方文档MySQL 8.0 Reference Manual中“8.2.1.16 ORDER BY Optimization”章节有以下介绍:google

Previously (MySQL 5.7 and lower), GROUP BY sorted implicitly under certain conditions. In MySQL 8.0, that no longer occurs, so specifying ORDER BY NULL at the end to suppress implicit sorting (as was done previously) is no longer necessary. However, query results may differ from previous MySQL versions. To produce a given sort order, provide an ORDER BY clause.spa

google翻译:之前(MySQL 5.7及更低版本),GROUP BY在某些条件下隐式排序。 在MySQL 8.0中,再也不发生这种状况,所以再也不须要在末尾指定ORDER BY NULL来抑制隐式排序(如前所述)。 可是,查询结果可能与之前的MySQL版本不一样。 要产生给定的排序顺序,请提供ORDER BY子句

陈哈哈:“哦,这么看来开发老版本的同事是没用Order by,直接用了隐式排序。年轻人,不讲武德啊!!” 小王(小声):“哈哥,这模块以前好像是你负责的。” 陈哈哈(老脸一红):??? 陈哈哈:“咳咳,这MySQL8.0团队不讲武德,给我挖坑!”

在这里插入图片描述

好了,接下来咱们用测试数据演示一下

数据测试

下面是表T测试数据,无序

mysql> SELECT pid,appName from T;    
+--------+-------------------------+
| pid    | appName                 |
+--------+-------------------------+
|      1 |  Dock Sound Redirector  |
|      2 |  Blues Music station    |
|      3 |  usb tether TRIAL       |
|      4 |  Il vero test del QI    |
|      5 |  FlightTime Calculator  |
|      6 |  ZX Spectrum Emulator   |
|      7 |  The City Dress Up      |
+--------+-------------------------+
7 rows in set (0.00 sec)

实验1:(MySQL版本:5.7.24)

-- 隐式排序
mysql> SELECT pid,appName from T group by appName;    
+--------+-------------------------+
| pid    | appName                 |
+--------+-------------------------+
|      2 |  Blues Music station    |
|      1 |  Dock Sound Redirector  |
|      5 |  FlightTime Calculator  |
|      4 |  Il vero test del QI    |
|      7 |  The City Dress Up      |
|      3 |  usb tether TRIAL       |
|      6 |  ZX Spectrum Emulator   |
+--------+-------------------------+
7 rows in set (0.00 sec)

-- 如上述隐式排序,至关于SELECT pid,appName from T group by appName asc 或 SELECT pid,appName from T group by appName order by appName asc;

-- 显式排序,至关于SELECT pid,appName from T group by appName order by appName desc;
mysql> SELECT pid,appName from T group by appName desc;    
+--------+-------------------------+
| pid    | appName                 |
+--------+-------------------------+
|      6 |  ZX Spectrum Emulator   |
|      3 |  usb tether TRIAL       |
|      7 |  The City Dress Up      |
|      4 |  Il vero test del QI    |
|      5 |  FlightTime Calculator  |
|      1 |  Dock Sound Redirector  |
|      2 |  Blues Music station    |
+--------+-------------------------+
7 rows in set (0.00 sec)

实验2:(MySQL版本:8.0.16)

mysql> SELECT pid,appName from T group by appName;    
+--------+-------------------------+
| pid    | appName                 |
+--------+-------------------------+
|      1 |  Dock Sound Redirector  |
|      2 |  Blues Music station    |
|      3 |  usb tether TRIAL       |
|      4 |  Il vero test del QI    |
|      5 |  FlightTime Calculator  |
|      6 |  ZX Spectrum Emulator   |
|      7 |  The City Dress Up      |
+--------+-------------------------+
7 rows in set (0.00 sec)

mysql> SELECT pid,appName from T group by appName DESC;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DESC' at line 1

  如上所示,在MySQL 8.0中,GROUP BY隐式排序不支持了,上面测试例子是无序的。GROUP BY显示排序则直接报错。因此若是有数据库从MySQL 5.7或以前的版本,迁移升级到MySQL 8的话,就须要特别留意这个问题了。

2、隐式排序 - 起源(一个优美的BUG)

  最初为何要用隐式排序呢?咱们知道,要对一组数据进行分组,MySQL优化器会选择不一样的方法。其中最有效的一种是分组以前对数据排序,下降数据复杂度,使得连续分组变得很容易。另外,若是能够Group by 一个索引字段来用于获取排序的数据,那么使用它的成本就很是低了(由于BTree索引是自然有序的)。而在实际操做中,Group by用到索引的频率很高。这么看,这确实是个很棒的主意!也能够说是留了一个优美的BUG

  以下查询语句,用到了appName_idx索引,所以group by查询不须要排序,直接分组,高效。

-- 有索引:appName_idx
mysql> EXPLAIN SELECT appName from 0122_csj_demo GROUP BY appName \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: 0122_csj_demo
   partitions: NULL
         type: index
possible_keys: appName_idx
          key: appName_idx
      key_len: 515
          ref: NULL
         rows: 28
     filtered: 100.00
        Extra: Using index
1 row in set, 1 warning (0.00 sec)

  若是没有索引,MySQL优化器仍然能够决定在分组以前用外部临时表进行filesort排序,从效率上讲,和无序分组差很少。当用户指定Order by时,是MySQL最但愿看到的,这样就不会让排序工做白费,这也是让MySQL团队始终默认隐式排序存在的缘由之一。

mysql> EXPLAIN SELECT appName from 0122_csj_demo GROUP BY appName \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: 0122_csj_demo
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 28
     filtered: 100.00
        Extra: Using temporary; Using filesort
1 row in set, 1 warning (0.00 sec)

  另外,用户能够显式指定ORDER BY NULL就能让MySQL知道GROUP BY不须要排序。所以须要一个非标准(ORDER BY NULL)语法来抵消另外一个非标准扩展(GROUP BY 排序)的影响。

mysql> EXPLAIN SELECT appName from 0122_csj_demo GROUP BY appName ORDER BY null \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: 0122_csj_demo
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 28
     filtered: 100.00
        Extra: Using temporary
1 row in set, 1 warning (0.00 sec)

3、隐式排序 - 宿命

  为了解决这个优美的BUG,MySQL团队在8.0版本引入了倒排索引。正负向索引排序的优化思路,给隐式排序体面的落下帷幕。自此Group by隐式排序功能被删除,分组排序必须用order by来进行,分组的算法依然能够基于正负向索引延续以前分组的高效性。

  好了,本文到此基本结束,隐式排序算是MySQL角落里较冷门的知识点,对我来讲倒是一位结识四年的旧友了。北漂四年,时光匆匆,从初识MySQL的寸步难行,到深刻理解各知识点的实现思路,也算顺道吃了杯隐排的践行酒。

  莫泊桑说:"生活可能不像你想象的那么好,可是,也不会你想象的那么糟"。人的脆弱和坚强都超乎了本身的想,有时候可能脆弱的一句话,就泪流满面。有时候你会发现,本身咬着牙走过很长的一段路。 在外漂泊打工人不易,为了家人父母过上好日子,加油!

在这里插入图片描述

相关文章
相关标签/搜索