mysql 8.0默认开启only_full_group_by,可临时用set session sql_mode去除,或永久修改my.cnf配置;合规写法要求select中非聚合列必须全部出现在group by中,或仅选聚合结果与group by列。

MySQL 8.0 默认开启 ONLY_FULL_GROUP_BY 怎么关
MySQL 8.0+ 默认启用 ONLY_FULL_GROUP_BY,导致以前能跑的 GROUP BY 查询突然报错:Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column。这不是语法错了,是 SQL 模式变严格了。
临时关闭(当前会话)最安全,适合调试或兼容旧代码:
SET SESSION sql_mode = (SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));
永久关闭需改配置文件(如 /etc/mysql/my.cnf),在 [mysqld] 下加一行:
sql_mode = "STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"
注意:NO_AUTO_CREATE_USER 在 8.0.11+ 已废弃,别硬加;改完必须重启 MySQL 服务才生效。
不关 ONLY_FULL_GROUP_BY,怎么写合规的 GROUP BY
核心原则:SELECT 列里所有非聚合字段,必须完整出现在 GROUP BY 子句中(MySQL 5.7.5+ 支持函数依赖推导,但别依赖它)。
常见错误写法(会报错):
SELECT id, name, COUNT(*) FROM users GROUP BY dept_id;
正确写法有三种方向:
- 把所有非聚合列都放进 GROUP BY:
GROUP BY id, name, dept_id(但通常这不是你想要的“按部门分组”语义) - 只选聚合结果或 GROUP BY 列:
SELECT dept_id, COUNT(*), MAX(name) FROM users GROUP BY dept_id - 用子查询或窗口函数替代(如需要保留原始行粒度):
SELECT * FROM users u WHERE u.id = (SELECT MAX(id) FROM users u2 WHERE u2.dept_id = u.dept_id)
GROUP BY 字段含表达式时为什么还报错
比如写 GROUP BY DATE(created_at),但 SELECT 里用了 created_at 原始值——MySQL 不认为 created_at 函数依赖于 DATE(created_at),哪怕逻辑上成立。
解决办法只有两个:
- SELECT 里也用同样表达式:
SELECT DATE(created_at), COUNT(*) FROM logs GROUP BY DATE(created_at) - 或者显式别名并复用:
SELECT DATE(created_at) AS day, COUNT(*) FROM logs GROUP BY day
别指望 MySQL 自动推导表达式等价性,它只认字面一致。
应用层要不要统一处理这个模式差异
开发环境用低版本 MySQL、生产用 8.0+,容易漏掉这类报错。光靠测试覆盖不到所有 SQL 路径。
建议在连接初始化时强制对齐模式:
SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
而不是依赖全局配置。这样能避免因 DBA 修改全局 sql_mode 导致应用行为突变。
真正麻烦的不是报错本身,是有些 ORM 自动生成的 SQL 会悄悄混入非 GROUP BY 列(比如 Laravel 的 select('*') 配合 groupBy),这种地方最容易漏测。










