本文同时发表在https://github.com/zhangyachen/zhangyachen.github.io/issues/103

考虑如下表和sql:

CREATE TABLE `iknow_team_info` (
`teamId` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`userNum` int(11) unsigned NOT NULL DEFAULT '0'',
PRIMARY KEY (`teamId`),
) ENGINE=InnoDB DEFAULT CHARSET=gbk' mysql> select teamId,userNum from iknow_team_info limit 10;
+--------+---------+
| teamId | userNum |
+--------+---------+
| 1 | 73 |
| 4 | 100 |
| 8 | 112 |
| 9 | 136 |
| 10 | 58 |
| 12 | 84 |
| 16 | 141 |
| 17 | 560 |
| 18 | 114 |
| 19 | 8 |
+--------+---------+
10 rows in set (0.01 sec) mysql> select teamId,max(userNum) from iknow_team_info;
+--------+--------------+
| teamId | max(userNum) |
+--------+--------------+
| 1 | 1000 |
+--------+--------------+
1 row in set (0.02 sec)

关于最后一个sql:查找人数(userNum)最多的行对应的teamId,为什么会返回1呢?很显然人数最多的行对应的teamId不是1。

在这里userNum列没有索引,mysql肯定会全表扫描:

mysql> explain select teamId,max(userNum) from iknow_team_info;
+----+-------------+-----------------+------+---------------+------+---------+------+-------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------------+------+---------------+------+---------+------+-------+-------+
| 1 | SIMPLE | iknow_team_info | ALL | NULL | NULL | NULL | NULL | 12191 | |
+----+-------------+-----------------+------+---------------+------+---------+------+-------+-------+
1 row in set (0.00 sec)

我猜测的sql执行过程是这样的: 全表扫描,扫描的过程中记录下扫描过得最大的userNum以及对应的teamId,最后将结果返回。这个过程应该很清晰明了,为什么mysql没有返回正确结果呢?

最后在官方手册中寻找到了答案:

原来MAX()也是聚集函数的一种,所有聚集函数如下表:

关于MAX()函数的一点思考-LMLPHP

当我们使用了上面表中的聚集函数但是却没有包含group by时,mysql会默认在所有满足条件的行上做聚集。

所以我们可以大胆的假设上面的sql等同于:

mysql> select teamId,max(userNum) from iknow_team_info group by null;
+--------+--------------+
| teamId | max(userNum) |
+--------+--------------+
| 1 | 1000 |
+--------+--------------+
1 row in set (0.02 sec)

mysql跟标准sql的一点不同是:mysql接受出现在select列表中但是没有出现在group by列表中的列。所以,当teamId不在group by的列表中时,mysql会在每一个分组中随机挑选出一个teamId,所以最后出现的teamId是1,不是正确的。

要想出现正确的结果,我们可以按照下面的方式书写sql:

mysql> select teamId,userNum from iknow_team_info order by userNum desc limit 1;
+--------+---------+
| teamId | userNum |
+--------+---------+
| 88010 | 1000 |
+--------+---------+
1 row in set (0.01 sec)

或者我们可以让teamId出现在group by的列表中,从而取出正确的teamId(即列出每个teamId组内的max(userNum)),再对所有的max(userNum)进行排序。

mysql> select teamId,max(userNum) maxNum from iknow_team_info group by teamId order by maxNum desc limit 1;
+--------+--------+
| teamId | maxNum |
+--------+--------+
| 88041 | 1000 |
+--------+--------+
1 row in set (0.02 sec)

参考资料:

How does SQL MAX() works?

Aggregate (GROUP BY) Function Descriptions

05-11 15:20