mysql 5.6 升级到 8.0 后 group by 报错,根本原因是 8.0 默认启用 only_full_group_by 模式,要求 select 中所有非聚合字段必须显式出现在 group by 子句中;应优先改写 sql(如删冗余字段、用 any_value() 或补全 group by),而非禁用该模式。

MySQL 5.6 升级到 8.0 后 GROUP BY 报错,几乎可以断定是 ONLY_FULL_GROUP_BY 模式被默认启用——这不是 bug,是行为收敛,它在帮你暴露原本就模糊的 SQL 语义。
报错典型现象:ERROR 1055 (42000)
执行类似 SELECT id, name, COUNT(*) FROM user GROUP BY status; 这类语句时,直接报错:
ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause
原因很直接:MySQL 8.0 默认 sql_mode 包含 ONLY_FULL_GROUP_BY,而 5.6 默认不启用。该模式要求:所有出现在 SELECT 列表中的**非聚合字段**(如 id、name),必须显式出现在 GROUP BY 子句中,否则拒绝执行。
旧写法能跑,是因为 5.6 引擎“随便挑一行”填充 name,结果不可控;8.0 拒绝这种不确定性。
为什么不能简单 SET SESSION sql_mode 去掉 ONLY_FULL_GROUP_BY
临时关闭看似省事,但会掩盖真实问题:
- 应用逻辑可能依赖“随机选值”,换环境或数据分布变化后结果突变
- ORM 自动生成的统计查询(如 Django 的
.values('status').annotate(...))若漏字段对齐,关掉后照样返回歧义数据 -
ANY_VALUE()是显式兜底,SET关掉是隐式放行——前者可读、可维护,后者等于把校验开关焊死在“关”位
更关键的是:SET SESSION sql_mode=... 只影响当前连接,应用重启、连接池复用后失效,容易误判“已修复”。
真正要改的不是配置,是 SQL 本身
根据业务意图选择对应改法,不是一刀切:
- 如果只关心分组统计,删掉多余字段:
SELECT status, COUNT(*) FROM user GROUP BY status; - 如果确实需要带
name,且接受任意一个值(比如展示示例名称),用ANY_VALUE(name):SELECT ANY_VALUE(id), ANY_VALUE(name), status, COUNT(*) FROM user GROUP BY status; - 如果
status和status_name是一对一关系,把它加进GROUP BY:GROUP BY status, status_name
ANY_VALUE() 从 MySQL 5.7.5 起支持,8.0 完全兼容;它不会优化器推断,但至少让语义明确、结果稳定。
配置文件里改 sql_mode 的坑
在 my.cnf 或 my.ini 中修改 sql_mode 是持久化方案,但要注意:
- MySQL 8.0 已移除
NO_AUTO_CREATE_USER,若配置里还留着,启动会报错:ERROR 1231 (42000): Variable 'sql_mode' can't be set to the value of 'NO_AUTO_CREATE_USER' - 某些新版配置文件实际使用的是
sql-mode(带短横线)而非sql_mode,搜不到就查 120 行附近 -
SET GLOBAL sql_mode=...不重启就生效,但服务重启后丢失;my.cnf修改必须重启 MySQL 才加载
最稳妥的配置值(8.0 兼容):sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
别漏掉 STRICT_TRANS_TABLES——它管 INSERT 缺失非空字段的问题,和 ONLY_FULL_GROUP_BY 是同一波严格化动作。











