MySQL 5.7+ 因启用 ONLY_FULL_GROUP_BY 模式导致 GROUP BY 报错,要求 SELECT 中非聚合字段必须出现在 GROUP BY 中;临时可会话级禁用,永久需修改配置文件,但推荐重写 SQL 用窗口函数或 JOIN 替代。

MySQL 5.7+ 执行 GROUP BY 报错:Expression #1 of SELECT list is not in GROUP BY clause
这是 MySQL 5.7 默认启用 ONLY_FULL_GROUP_BY SQL 模式后最典型的报错。不是你的 SQL 写错了,而是 MySQL 突然“变严格”了——它要求 SELECT 列表里的所有非聚合字段,必须明确出现在 GROUP BY 子句中。
常见错误现象:
写了个 SELECT id, name, COUNT(*) FROM user GROUP BY status;,直接报错,哪怕 id 和 name 在逻辑上由 status 决定(比如每个 status 只对应一个 name),MySQL 也不认。
- 这不是 bug,是设计行为:防止隐式依赖导致结果不可预测
- 临时绕过方法是改
sql_mode,但治标不治本;长期应重写 SQL - 改配置只影响当前会话或全局,重启后可能失效(取决于修改方式)
临时禁用 ONLY_FULL_GROUP_BY(会话级)
适合开发调试、CI 脚本临时跑通,不建议上线使用。
执行这条语句即可:
SET SESSION sql_mode = (SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));
- 仅对当前连接生效,断开即恢复原状
- 不用重启 MySQL,也不需要管理员权限(只要允许 SET)
- 注意:如果
@@sql_mode里有多个模式用逗号分隔,REPLACE可能误删其他模式里的子串(比如把STRICT_TRANS_TABLES里的TABLES干掉),更安全的写法是用正则或手动拼接
永久关闭 ONLY_FULL_GROUP_BY(配置文件修改)
适用于测试环境或明确接受宽松语义的业务系统,但生产库慎用。
编辑 MySQL 配置文件(通常是 /etc/my.cnf 或 /etc/mysql/mysql.conf.d/mysqld.cnf),在 [mysqld] 段落下添加:
sql_mode = "STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"
- 必须显式列出你想要的全部模式,不能只写
sql_mode = ""—— 空值会被 MySQL 忽略,回退到默认值 - 改完要重启 MySQL:
sudo systemctl restart mysql(或mysqld) - 验证是否生效:
SELECT @@sql_mode;,确认输出里没有ONLY_FULL_GROUP_BY - 某些云数据库(如阿里云 RDS、腾讯云 CDB)不开放配置文件修改,只能通过控制台参数模板调整
真正靠谱的解法:重写 GROUP BY 查询
靠关模式掩盖问题,不如让 SQL 本身符合标准。大多数报错场景其实有清晰的语义替代方案。
例如原查询:SELECT id, name, status, COUNT(*) FROM user GROUP BY status;
- 如果真想取每个
status下任意一条记录的id和name,用窗口函数更安全:SELECT id, name, status FROM (SELECT id, name, status, ROW_NUMBER() OVER (PARTITION BY status ORDER BY id) AS rn FROM user) t WHERE rn = 1; - 如果
id是主键,status是外键关联字段,那应该先JOIN再分组,而不是把主表字段全塞进SELECT - MySQL 8.0+ 支持函数依赖推导(如
id → name),但需确保字段间存在确定性关系且索引支持,实际中极少自动触发
复杂点在于:很多旧业务 SQL 的 GROUP BY 本质是“取最新一条”或“取某个优先级最高的值”,这类逻辑没法靠开关解决,得结合业务含义补全聚合逻辑或改用子查询/窗口函数。别省那几行代码,模式关了,问题还在那儿。










