sql_mode之only_full_group_by
今天上班的时候,业务方问了我这样一个问题:能不能把线上的sql_mode值改为和测试环境一致?因为我们在测试环境上写的sql在线上可能会出错,原因是线上的环境设置了sql_mode=only_full_group_by。
听到这个需求的时候,我还是很震惊的,这个线上的环境竟然还有人想修改,而且是往更加宽松的程度上去修改,这我肯定不能同意啊。不过也可以理解他们,他们可能没有意识到这个参数的作用,只是从自己的角度出发,想着这样能够解决他们的问题。后面我了解了一下情况,大概的情况就是他们写了一个sql,使用了group by,然后group by里面的字段只有一个,但是select语句中查询了多个字段,导致MySQL报了类似下面的错误:
ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause
and contains nonaggregated column 'yeyztest.test.id' which is not
functionally dependent on columns in GROUP BY clause; this is incompatible
with sql_mode=only_full_group_by
大概就是说这个select语句中的字段了group by中的字段不一致,这在MySQL中是不被允许的。
了解了需求之后,我大概举了一个例子,对这种情况进行了一个测试,且看如下例子:
mysql--dba_admin@127.0.0.1:yeyztest 22:15:04>>select * from test;
+----+------+-------+
| id | age | score |
+----+------+-------+
| 1 | 1 | 5 |
| 2 | 2 | 10 |
| 5 | 5 | 25 |
| 8 | 8 | 40 |
| 9 | 2 | 45 |
| 10 | 5 | 50 |
| 11 | 8 | 55 |
+----+------+-------+
7 rows in set (0.00 sec)
mysql--dba_admin@127.0.0.1:yeyztest 22:15:13>>select @@sql_mode;
+-------------------------------------------------------------------------------------------------------------------------------------------+
| @@sql_mode |
+-------------------------------------------------------------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION |
+-------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
这个例子中创建了一个test的表,其中id是主键,其他两个字段是普通的字段,然后sql_mode包含了only_full_group_by,然后我们开始写SQL来查看是否可以通过:
mysql--dba_admin@127.0.0.1:yeyztest 22:15:21>>select * from test group by age;
ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column
'yeyztest.test.id' which is not functionally dependent on columns in
GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
可以看到,这个问题复现了,我们使用了select *的语法,然后最后group by一个age字段,这样的话,id和score字段没有用到,提示我们的是不可以执行,因为前后字段数量不一致。那么如果我们使用三个字段做group by呢?
mysql--dba_admin@127.0.0.1:yeyztest 22:16:10>>select * from test group by age,score,id;
+----+------+-------+
| id | age | score |
+----+------+-------+
| 1 | 1 | 5 |
| 2 | 2 | 10 |
| 9 | 2 | 45 |
| 5 | 5 | 25 |
| 10 | 5 | 50 |
| 8 | 8 | 40 |
| 11 | 8 | 55 |
+----+------+-------+
7 rows in set (0.00 sec)
可以看到,如果使用了group by三个字段,那么这个结果是可靠的,没有报错误,而且是按照group by最近的一个字段age来进行的分组,再来看单独使用id的情况以及使用id和score的情况:
mysql--dba_admin@127.0.0.1:yeyztest 22:16:25>>select * from test group by id;
+----+------+-------+
| id | age | score |
+----+------+-------+
| 1 | 1 | 5 |
| 2 | 2 | 10 |
| 5 | 5 | 25 |
| 8 | 8 | 40 |
| 9 | 2 | 45 |
| 10 | 5 | 50 |
| 11 | 8 | 55 |
+----+------+-------+
7 rows in set (0.00 sec)
mysql--dba_admin@127.0.0.1:yeyztest 22:17:05>>select * from test group by score,id;
+----+------+-------+
| id | age | score |
+----+------+-------+
| 1 | 1 | 5 |
| 2 | 2 | 10 |
| 5 | 5 | 25 |
| 8 | 8 | 40 |
| 9 | 2 | 45 |
| 10 | 5 | 50 |
| 11 | 8 | 55 |
+----+------+-------+
7 rows in set (0.00 sec)
我们发现,都可以执行成功。虽然前面使用了select *后面的group by只使用了部分字段,但是依旧可以成功,没有报错,这里我就有一些疑问了,为什么这个也是部分字段但是却没有报错,那么如果我不使用id,使用age和score的组合呢?
mysql--dba_admin@127.0.0.1:yeyztest 22:17:08>>select * from test group by score,age;
ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column
'yeyztest.test.id' which is not functionally dependent on columns in
GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
我们可以发现,单独使用这两个字段age和score进行group by,那么结果一定是不可靠的,因为会报错。问题到底出在那里呢?把select * 改成age和score试试:
mysql--dba_admin@127.0.0.1:yeyztest 22:19:02>>select age,score from test group by age,score ;
+------+-------+
| age | score |
+------+-------+
| 1 | 5 |
| 2 | 10 |
| 2 | 45 |
| 5 | 25 |
| 5 | 50 |
| 8 | 40 |
| 8 | 55 |
+------+-------+
7 rows in set (0.00 sec)
发现这样是可以的,当然,这样符合要求,就是group by后面的字段和select 的字段一致。
以上都是有这个参数的情况,再来看看没有only_full_group_by的情况,先切换到这种非严格模式:
mysql--dba_admin@127.0.0.1:yeyztest 22:17:35>>set global sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';
Query OK, 0 rows affected (0.00 sec)
mysql--dba_admin@127.0.0.1:yeyztest 22:17:54>>select * from test group by score,age;
ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'yeyztest.test.id' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
mysql--dba_admin@127.0.0.1:yeyztest 22:17:58>>set session sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';
Query OK, 0 rows affected (0.00 sec)
mysql--dba_admin@127.0.0.1:yeyztest 22:18:20>>select * from test group by score,age;
+----+------+-------+
| id | age | score |
+----+------+-------+
| 1 | 1 | 5 |
| 2 | 2 | 10 |
| 5 | 5 | 25 |
| 8 | 8 | 40 |
| 9 | 2 | 45 |
| 10 | 5 | 50 |
| 11 | 8 | 55 |
+----+------+-------+
7 rows in set (0.00 sec)
上面两个测试,仅仅是set global和set session的区别,可以看到,当我们set global的时候,这个sql_mode相当于还是以前的,要使他生效,需要重新连接进来,也就是使用新会话,否则不会生效,而我们使用了set session之后,当前回话的sql_mode就直接被改掉了。也就支持select和group by的前后字段不一致了。
经过我们上面这么多实验,可以得到下面的结论:
1、当我们的sql_mode使用了only_full_group_by的时候,如果要想实现select的字段和group by的字段数量不相同而查询语句不出错,那么我们必须在group by后面添加主键字段,否则一定会出错。
2、当我们去掉sql_mode的only_full_group_by的时候,之所以前后字段数量不一致还能查询成功,实际上是mysql为我们进行了补齐。这种情况下我们不必保持前后字段数量一致,但是需要知道,这是一种不好的习惯,在我们日常写sql的时候,还是尽量要在严格模式下面写,这样mysql会为我们进行校验,如果一旦不合适,就会抛出错误,有助于提升你sql语句的健壮性。