MySQL从5.7版本开始默认启用了ONLY_FULL_GROUP_BY模式,这让不少原本在5.6上运行正常的SQL语句升级后直接报错。例如一条常见的订单统计语句:
SELECT customer_id, customer_name, COUNT(*) AS order_count FROM orders GROUP BY customer_id;
在旧版本中,这条语句可以执行,customer_name虽然不在GROUP BY子句中,MySQL会从每组中任意取一条记录的值返回。但升级后,如果sql_mode包含ONLY_FULL_GROUP_BY,执行会报错,提示customer_name不在GROUP BY子句中。这个报错并不是缺陷,而是更接近SQL标准的严格检查。

一、ONLY_FULL_GROUP_BY模式到底限制了什么
要理解这个模式,需要先分清SQL标准与MySQL扩展之间的差异。SQL标准规定,在包含GROUP BY的查询中,SELECT列表中的每一项要么是被分组的列,要么必须包含在聚合函数中。如果出现既没有参与分组、也没有被聚合的列,数据库无法确定应该返回组内的哪一个值,因此标准要求直接报错。
ONLY_FULL_GROUP_BY就是用来强制执行这条规则的。开启后,MySQL会对GROUP BY语句做逐列检查,凡是出现在SELECT列表、HAVING条件或ORDER BY中的列,只要不是聚合函数参数,就必须同样出现在GROUP BY子句中。否则查询会被拒绝,返回类似Expression #2 of SELECT list is not in GROUP BY clause的错误。
这条限制看起来严格,实际上可以避免很多隐蔽的数据问题。举例来说,如果表orders中同一客户有多条不同姓名的记录,当按客户分组时,不同行的customer_name可能不同。旧模式随机取值会让统计结果变得不可预期,同一个查询前后执行结果也可能不一致。开启严格模式后,开发者必须明确自己要取的是哪一个值,例如使用MAX(customer_name)或ANY_VALUE(customer_name),查询逻辑反而更清晰。
二、调整sql_mode的几种方式及其区别
遇到因为ONLY_FULL_GROUP_BY导致的报错,最简单的临时办法是修改当前会话的sql_mode。先查看当前模式:
SELECT @@SESSION.sql_mode;
可以看到ONLY_FULL_GROUP_BY被包含在返回结果中。如果需要立即让当前连接恢复旧版宽松行为,可以执行:
SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
这条命令只会影响当前会话,不会改变其他连接,数据库重启后设置也会失效。如果希望所有新连接都取消严格分组检查,可以在配置文件中修改。Linux下通常编辑/etc/mysql/my.cnf或/etc/my.cnf,Windows下编辑my.ini,在[mysqld]段落添加:
[mysqld] sql_mode = STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
保存后重启MySQL服务,配置才会生效。需要注意,不同版本默认的sql_mode值会有差异,不建议直接照搬网上找到的配置字符串,最好先查看自己的完整模式,再去除ONLY_FULL_GROUP_BY,保留其他选项。也可以通过SET GLOBAL sql_mode = ...修改全局值,但它对已存在的连接不生效,只影响新建立的连接。
从运维角度看,直接修改配置虽然能快速恢复业务,但也会让整个实例失去严格模式的保护。如果团队中有人习惯写不规范的GROUP BY语句,关闭后可能重新引入不确定的查询结果,而且问题更隐蔽,排查成本更高。
三、直接关闭模式的隐患与更稳妥的改写方案
关闭ONLY_FULL_GROUP_BY后,MySQL会沿用旧版行为,对未分组列采用任意取值策略。这种任意性并不意味着随机,而是由存储引擎和优化器决定,通常取决于扫描顺序。换句话说,同一查询在不同时间、不同执行计划下可能返回不同的customer_name,统计报表会出现让运营困惑的异常。
更稳妥的做法是保留严格模式,同时调整SQL写法。对于确实不需要关心具体取哪个值的列,可以显式使用ANY_VALUE()函数,让MySQL知道开发者已经了解并接受这种不确定性。上面的错误示例可以改写为:
SELECT customer_id, ANY_VALUE(customer_name) AS customer_name, COUNT(*) AS order_count FROM orders GROUP BY customer_id;
这样既符合ONLY_FULL_GROUP_BY的检查,又明确表达了自己的意图。如果业务上需要显示准确的客户姓名,更好的方案是把姓名放在用户表中,通过JOIN关联,而不是在订单表中依赖不分组取值。例如:
SELECT o.customer_id, u.customer_name, COUNT(*) AS order_count FROM orders o JOIN users u ON u.customer_id = o.customer_id GROUP BY o.customer_id, u.customer_name;
这个查询把customer_name也加入GROUP BY,同时通过关联保证每个客户只有一条用户记录,不会出现同一客户多个姓名导致分组膨胀的问题。配合合适的索引,性能通常也好于在订单表里保留冗余字段。
总结来说,ONLY_FULL_GROUP_BY带来的报错并不是要开发者必须关闭检查,而是促使大家把分组语义写清楚。临时调整sql_mode可以应急,但从长期维护角度看,使用ANY_VALUE()或改写查询结构更能保证代码质量和数据一致性。如果项目刚从旧版升级,建议先定位所有受影响的SQL,逐条修改,而不是直接在生产环境关闭严格模式。
ONLY_FULL_GROUP_BYsql_mode分组查询修改时间:2026-10-05 05:15:35