Expression #1 指 SELECT 列表中第一个既未聚合也未出现在 GROUP BY 子句中的字段,如 SELECT id, name, COUNT(*) FROM users GROUP BY dept_id 中的 id;该编号按字段顺序从 1 开始计数,用于精确定位语义模糊列。

MySQL 8.0 升级后 GROUP BY 报错,不是语法写错了,而是它开始严格执行 SQL 标准了——SELECT 列表里每个非聚合字段(比如 id、name)必须显式出现在 GROUP BY 子句中,否则直接拒绝执行。
报错信息里 “Expression #1” 是什么
错误提示 Expression #1 of SELECT list is not in GROUP BY clause 中的 Expression #1 指的就是 SELECT 列表里第一个既没被聚合函数包裹、也没出现在 GROUP BY 中的字段。例如:
SELECT id, name, COUNT(*) FROM users GROUP BY dept_id;
这里 id 是第 1 个非聚合非分组字段,所以报错。
- 这个编号从 1 开始,按
SELECT后字段顺序计数 - 报错位置不一定是业务关键字段,但能帮你快速定位哪一列“漏了处理”
- ORM(如 Laravel/Eloquent、MyBatis)自动生成的查询常因字段对齐疏忽触发此错误
为什么老 SQL 在 5.7 能跑,8.0 就挂了
MySQL 5.7.5 起默认启用 ONLY_FULL_GROUP_BY 模式,8.0 继承并强化了这一行为。此前版本虽支持该模式,但默认关闭。
- 旧代码在 MySQL 5.6 或未开启该模式的 5.7 实例中“侥幸通过”,实际返回的是每组中某一行的任意值(取决于存储引擎和数据物理顺序)
- 这种结果不可控:同一语句多次执行可能不同;测试环境与生产环境结果可能不一致
- 升级不是引入 bug,而是暴露了原有 SQL 的语义模糊性——你真需要那个
name吗?是最新一条?最早一条?还是随便一个?
ANY_VALUE() 不是万能兜底,但比关模式靠谱
ANY_VALUE() 是 MySQL 5.7.5+ 提供的显式声明方式,告诉服务器:“我知道这列在组内不唯一,我接受这个不确定性”。它比直接删掉 ONLY_FULL_GROUP_BY 更可控。
- 把
SELECT id, name, dept_id, COUNT(*)改成SELECT ANY_VALUE(id), ANY_VALUE(name), dept_id, COUNT(*) - 不能用于有强业务含义的字段(比如要取“最新创建的
id”,就得用窗口函数或子查询重写) - 低版本 MySQL(
- 它会抑制优化器的部分推断能力,但至少让意图可读、结果可预期
永久关掉 ONLY_FULL_GROUP_BY 的坑
云数据库(如阿里云 RDS、腾讯云 CDB)通常只允许通过控制台「参数模板」修改 sql_mode;自建库改 my.cnf 必须显式列出全部项,不能留空或只写 ""。
- 错误写法:
sql_mode = ""或sql_mode = "STRICT_TRANS_TABLES"→ MySQL 会自动补回默认值,ONLY_FULL_GROUP_BY仍在 - 正确写法(缺一不可):
sql_mode = "STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION" - 即使关掉了,MySQL 仍可能从每组中随机选一行代表值——这个“随机”取决于引擎、索引、数据分布,甚至查询计划变化
最常被忽略的一点:禁用 ONLY_FULL_GROUP_BY 只是让 SQL “能跑”,不等于结果“正确”。真正要解决的,是业务逻辑里对分组后字段的模糊依赖。


















