mysql 5.7.5+ 或 8.0 默认启用 only_full_group_by 模式,要求 select 中非聚合字段必须出现在 group by 或用 any_value() 等包裹;可通过 set session 修改 sql_mode 临时禁用,或修改配置文件永久调整,但应优先优化 sql 语义。

MySQL 升级到 5.7.5+ 或 8.0 后 GROUP BY 报错,不是语法错了,而是它开始严格执行 SQL 标准——ONLY_FULL_GROUP_BY 模式默认启用,要求 SELECT 列表里每个非聚合字段必须显式出现在 GROUP BY 子句中,或被 ANY_VALUE()、MAX() 等函数包裹。
查确认:当前 sql_mode 是否含 ONLY_FULL_GROUP_BY
别猜,先执行:SELECT @@sql_mode; 或 SELECT @@GLOBAL.sql_mode;。如果返回结果里包含 ONLY_FULL_GROUP_BY(通常排在第一位),就是它导致的报错。注意输出是英文逗号+空格分隔的字符串,比如 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,...'。云数据库如阿里云 RDS 可能限制 @@GLOBAL.sql_mode 写权限,SET GLOBAL 会直接报 ERROR 1227。
改 SQL:优先用 ANY_VALUE() 或补全 GROUP BY
关模式是捷径,但会让结果不可控——比如 SELECT id, name, COUNT(*) FROM user GROUP BY status 中的 id 和 name 值会随机取自分组内某行,下次查询可能就变了。更安全的做法是让语义明确:
- 如果只想要统计值,删掉冗余字段:
SELECT status, COUNT(*) FROM user GROUP BY status - 如果确实要带
name且确认分组内值一致(比如status和status_name是一对一),加进GROUP BY:GROUP BY status, status_name - 如果业务上接受“任取一个”,用
ANY_VALUE()显式表达意图:SELECT ANY_VALUE(id), ANY_VALUE(name), status, COUNT(*) FROM user GROUP BY status。注意:ANY_VALUE()是 MySQL 特有,跨库迁移时需重写。
调 sql_mode:临时会话级修改最安全
开发调试或 CI 流水线跑通时,用这条命令即可,不影响其他连接,断开重连自动还原:
SET SESSION sql_mode = (SELECT REPLACE(@@sql_mode, 'ONLY_FULL_GROUP_BY', ''));
别手拼字符串,比如 SET SESSION sql_mode = 'STRICT_TRANS_TABLES,...',容易漏项;也别设空值 '',MySQL 会自动补回默认集,ONLY_FULL_GROUP_BY 还在。JDBC 的 connectionInitSql 里直接写这句可能被忽略,建议放在应用首次 query 前显式执行。
永久关闭:仅限自建 MySQL,且必须写全 sql_mode
生产环境若真要永久禁用,只能改配置文件并重启服务。编辑 /etc/my.cnf,在 [mysqld] 段下添加:
sql_mode = STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
关键细节:
- 必须严格按此顺序、无空格、无单引号、无换行
- 漏掉任何一项(比如
STRICT_TRANS_TABLES),MySQL 启动时会自动补全默认值,ONLY_FULL_GROUP_BY就又回来了 - 云数据库不支持改配置文件,得走控制台「参数模板」,且部分厂商模板根本不开放
sql_mode编辑权限
改完必须执行 sudo systemctl restart mysql,再用 SELECT @@GLOBAL.sql_mode; 验证。
真正难的不是怎么关掉它,而是判断哪条 SQL 真的需要保留非聚合字段——有些 ORM 自动生成的查询,表面看没问题,实际分组内字段值并不稳定,关了模式只是把问题藏得更深。











