mysql 8.0 默认启用 only_full_group_by 模式,要求 select 中非聚合字段必须显式出现在 group by 中或满足函数依赖,否则报错 expression #n;本质是语义校验变严格,非语法错误。

不是语法错,是语义校验变严格了——MySQL 8.0 默认启用 ONLY_FULL_GROUP_BY 模式,拒绝执行“非聚合字段未显式分组或未包裹”的查询。
报错信息里 Expression #N of SELECT list is not in GROUP BY clause 是什么意思
它直白指出:SELECT 列表中第 N 个字段(从 1 开始数)既没出现在 GROUP BY 子句里,也没被 MAX()、ANY_VALUE() 等函数处理,且 MySQL 判断它和分组列之间“不满足函数依赖关系”。
常见例子:
-
SELECT id, name, COUNT(*) FROM user GROUP BY status→id是 Expression #1,name是 #2,都违规 -
SELECT user_id, SUM(amount) FROM orders GROUP BY user_id ORDER BY created_at DESC→ORDER BY created_at也受约束,created_at不在GROUP BY里就报错
为什么 5.7 不报,8.0 就报
这不是 bug,是行为收敛:
- MySQL 5.7.5+ 其实已默认启用
ONLY_FULL_GROUP_BY,但很多旧环境手动关掉了它(比如sql_mode里压根没这串) - 升级到 8.0 后,配置重置或继承更严格的默认值,问题暴露
- 8.0 对“函数依赖”的判断更严:即使你写了
GROUP BY id,如果表没定义主键或唯一约束,它仍可能报not functionally dependent
ANY_VALUE() 能直接套用吗
能,但得确认业务是否真接受不确定性:
-
ANY_VALUE(name)显式告诉 MySQL:“我接受这个值从组内任意一行取”,比关模式更可控 - 但它不保证和
SUM(amount)来自同一行记录;也不触发隐式索引优化,大表分组时可能拖慢执行计划 - 如果
status和status_name是一对一映射,应该补进GROUP BY status, status_name,而不是用ANY_VALUE()掩盖建模缺陷 - 跨库迁移时,
ANY_VALUE()是 MySQL 特有函数,PostgreSQL 或 Oracle 不认
临时改 sql_mode 的坑在哪
临时禁用只该用于调试,且必须用对写法:
- 会话级安全写法:
SET SESSION sql_mode = (SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));—— 断开重连自动还原 - 别手拼字符串,比如
SET SESSION sql_mode = 'STRICT_TRANS_TABLES,',容易漏项,MySQL 会自动补回含ONLY_FULL_GROUP_BY的默认集 - 别设空值
'',MySQL 会强制恢复完整默认模式 - 云数据库(如阿里云 RDS)通常限制
@@GLOBAL.sql_mode写权限,SET GLOBAL直接报ERROR 1227
最易被忽略的一点:关掉模式后,结果仍是不确定的——同一查询两次执行,id 和 name 可能来自不同行;主从之间也可能因选行策略差异导致不一致。











