mysql 8.0 默认启用 only_full_group_by 导致 group by 报错,因非聚合列未出现在 group by 中;需改写 sql 用 any_value() 或补全 group by,并确保 order by 具备唯一性以避免分页乱序。

MySQL 8.0 默认启用 ONLY_FULL_GROUP_BY 导致 GROUP BY 报错
升级到 MySQL 8.0 后,原来能跑的 SELECT id, name FROM user GROUP BY dept_id 突然报错:Expression #1 of SELECT list is not in GROUP BY clause。这不是 SQL 写错了,是 MySQL 8.0 默认启用了更严格的 SQL 模式。
根本原因是 MySQL 8.0 将 ONLY_FULL_GROUP_BY 加入了默认 sql_mode,要求所有 SELECT 列要么是聚合函数结果,要么必须出现在 GROUP BY 子句中。
- 临时绕过(仅开发/测试):执行
SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';,去掉ONLY_FULL_GROUP_BY - 长期方案必须改 SQL:确保非聚合列都在
GROUP BY中,或用ANY_VALUE()显式声明容忍(如SELECT ANY_VALUE(id), name FROM user GROUP BY dept_id) - 注意
ANY_VALUE()不是“随便取一个”,它只是告诉 MySQL “我知道这列不唯一,我接受语义不确定性”——业务上真依赖某个特定值时,得用子查询或窗口函数重写
ORDER BY + LIMIT 在分页场景下因隐式排序失效
旧代码里写 SELECT * FROM order WHERE status = 1 ORDER BY id LIMIT 20, 10,升级后分页结果乱序、重复或漏数据。问题出在 MySQL 8.0 对 ORDER BY 的语义更严格:当排序字段有重复值(比如多个订单同属一个 user_id),数据库不再保证相同值内部的物理顺序稳定。
这导致分页偏移量(LIMIT 20, 10)每次执行可能捞到不同行,尤其在并发写入时更明显。
- 必须补全排序键,让每行都有唯一可比较的顺序:比如改成
ORDER BY status, id或ORDER BY create_time DESC, id DESC - 避免用
LIMIT offset, size做深分页;超过几千行建议改用游标分页(WHERE create_time ) - 如果应用层已缓存了旧排序逻辑(比如前端按某字段点一下就切排序),检查是否漏传了二级排序字段
JSON 字段操作函数行为变化:json_extract 返回类型与空值处理
MySQL 5.7 里 JSON_EXTRACT(json_col, '$.name') 返回带引号的字符串(如 "Alice"),而 8.0 默认返回去引号后的值(Alice)。升级后,原本用 = 'Alice' 匹配的条件突然不生效,或者 CONCAT() 拼接时报类型错误。
更隐蔽的是空路径行为:5.7 对不存在的路径返回 NULL,8.0 在某些嵌套深度下可能返回空 JSON 对象而非 NULL,影响 IS NULL 判断。
- 统一用
JSON_UNQUOTE(JSON_EXTRACT(...))获取字符串值,避免隐式类型转换差异 - 判断字段是否存在,优先用
JSON_CONTAINS_PATH(json_col, 'one', '$.name')而不是JSON_EXTRACT(...) IS NULL - 批量更新 JSON 字段时,
JSON_SET()和JSON_REPLACE()行为一致,但JSON_INSERT()在键已存在时不覆盖——这点 5.7 和 8.0 相同,但容易被误以为是升级引入的问题
视图定义中包含 NOW()、CURRENT_USER() 等非确定性函数被拒绝创建
MySQL 8.0 要求视图必须是“确定性”的,否则创建失败:CREATE VIEW v_user AS SELECT *, NOW() as updated_at FROM user 会报错 View's SELECT contains a non-deterministic function。这不是 bug,是安全加固:防止复制或备份时因函数调用时机不同导致主从不一致。
这类视图常见于审计日志、临时标记场景,升级后直接崩掉。
- 把非确定性逻辑移到应用层或存储过程中,视图只做数据筛选和关联
- 若必须保留时间戳,可在基表加
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP字段,视图直接引用该列 - 已有视图需迁移:用
SHOW CREATE VIEW v_user查定义,手动拆解,把函数调用替换为确定性等价逻辑(例如用UNIX_TIMESTAMP()替代NOW()不行,它仍是非确定性的)
真正麻烦的不是语法报错,而是那些藏在 ORM 自动生成 SQL 里的隐式函数调用——比如 Django 的 extra() 或 SQLAlchemy 的 func.now() 插入到视图定义中,得翻源码或日志才能定位。











