直接关闭only_full_group_by存在语义风险,因mysql会随机选取非聚合字段值导致结果不可预测、不可重现;推荐用any_value()显式声明容忍歧义,或补全group by,兼顾标准合规与业务确定性。

不能“安全”关闭,只能“可控地绕过”——因为关闭本身就有语义风险,关键是怎么选方式、在哪生效、是否留退路。
为什么直接关掉会有隐患
MySQL 5.7 开启 ONLY_FULL_GROUP_BY 不是 bug,而是对 SQL 标准的靠拢。比如这句:
SELECT department, name, COUNT(*) FROM employees GROUP BY department;
每个 department 对应多个 name,MySQL 不知道该返回哪一个 —— 关掉模式后它会随便挑一个(通常是第一个物理行),但这个行为不可预测、不可重现,线上查数据可能今天对、明天错。
常见错误现象包括:分页结果错乱、报表统计值漂移、相同 SQL 在不同从库返回不同结果。
- 这不是 MySQL “抽风”,是标准 SQL 明确禁止的非确定性查询
- MySQL 8.0 更进一步:即使你从
sql_mode里删了ONLY_FULL_GROUP_BY,底层仍会做部分校验 - 很多 ORM 自动生成的 SQL(如 Hibernate 的
GROUP BY id却 SELECT 多个字段)会在关掉后“看似正常”,实则埋雷
三种操作方式的实际影响范围
别只看“能不能执行”,要看“谁会受影响、能撑多久”:
-
SET SESSION sql_mode = '...':仅当前连接有效,应用重启或连接池重连即失效;适合临时调试,但 JDBC 连接池默认复用连接,可能意外延续 -
SET GLOBAL sql_mode = '...':所有新建立的连接生效,已存在的连接不变;需 SUPER 权限,且服务重启后丢失 - 修改
my.cnf中[mysqld]下的sql_mode:永久生效,但必须重启 MySQL;Linux 常见路径是/etc/mysql/mysql.conf.d/mysqld.cnf或/etc/my.cnf,Windows 是my.ini
注意:sql_mode 值必须完整复制原值再剔除 ONLY_FULL_GROUP_BY,漏掉其他项(比如 STRICT_TRANS_TABLES)可能导致插入截断不报错等更隐蔽问题。
比关闭更推荐的替代方案
如果只是想让旧 SQL 快速跑通,ANY_VALUE() 是副作用最小的选择:
SELECT ANY_VALUE(name), department, COUNT(*) FROM employees GROUP BY department;
它显式告诉 MySQL:“我知道这个 name 不确定,就随便取一个”。相比全局关模式,优势明显:
- 语义清晰,后续维护者一眼看出这是有意为之
- 不影响其他查询,不会导致全库非确定性行为扩散
- 兼容 MySQL 5.7.5+,无需改配置、不需重启
- 配合
EXPLAIN能确认是否真走索引,避免误用引发性能问题
JDBC 连接串里加参数靠谱吗
可以,但有前提:
- 必须用
sessionVariables=sql_mode='...'形式,例如:jdbc:mysql://localhost:3306/db?sessionVariables=sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,...' - 仅对本连接生效,适合多租户场景中为特定业务线单独降级
- 风险点:如果连接池(如 HikariCP)设置了
connection-init-sql,它会覆盖 JDBC 参数里的设置 - MySQL 8.0.19+ 已废弃该参数,改用
queryInterceptors,老项目迁移时容易遗漏
真正容易被忽略的是:关掉 ONLY_FULL_GROUP_BY 后,ORDER BY 字段也必须在 SELECT 或 GROUP BY 中出现,否则照样报错 —— 很多人只修了 SELECT 漏掉 ORDER BY,以为没生效。











