MySQL 5.7升至8.0后INSERT报错“Field 'xxx' doesn't have a default value”源于sql_mode严格化(如默认启用NO_ZERO_DATE、强化STRICT_TRANS_TABLES),应优先修改代码显式赋值或设默认值,而非禁用严格模式。

MySQL 5.7 升级到 8.0 后 INSERT 报错 Field 'xxx' doesn't have a default value
这是最典型的 sql_mode 兼容性问题。5.7 默认启用 STRICT_TRANS_TABLES,而 8.0 进一步强化了严格模式(比如默认加入 NO_ZERO_DATE),导致原来能插入的语句在新版本直接失败。
实操建议:
- 先查清当前模式:
SELECT @@sql_mode;,对比新旧环境差异,重点关注是否含STRICT_TRANS_TABLES、NO_ZERO_IN_DATE、ERROR_FOR_DIVISION_BY_ZERO - 不要全局关 strict:禁用
STRICT_TRANS_TABLES虽能“修复”报错,但会掩盖数据质量问题,比如隐式截断、零日期入库等 - 优先改代码:对非空字段显式赋值,或设默认值(
DEFAULT CURRENT_TIMESTAMP或DEFAULT '') - 若必须临时兼容,可在会话级调整:
SET SESSION sql_mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,...(填回你确认安全的子集)
my.cnf 里配置 sql_mode 为什么重启后不生效?
常见原因是配置写错位置或被覆盖。MySQL 启动时按顺序加载多个配置文件,后加载的会覆盖前面的;同时,某些云数据库(如阿里云 RDS、腾讯云 CDB)禁止用户修改全局 sql_mode,即使配置了也会被服务端强制重置。
实操建议:
- 确认配置位置:在
[mysqld]段下写,不是[client]或[mysql];例如:sql_mode = "STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION"
- 检查是否被覆盖:运行
mysqld --verbose --help | grep "Default options"查看实际加载路径,再用SELECT @@global.sql_mode;验证生效值 - 云环境绕过限制:改应用层连接串,在
jdbcUrl末尾加?sessionVariables=sql_mode='...'(MySQL JDBC 8.0+ 支持),或连接后立即执行SET SESSION sql_mode = ...
PHP PDO 插入时间字段为 0000-00-00 00:00:00 在 MySQL 8.0 失败
MySQL 8.0 默认启用 NO_ZERO_DATE 和 NO_ZERO_IN_DATE,禁止零值日期写入。老项目常依赖这个“占位”,升级后全量报错 Incorrect datetime value: '0000-00-00 00:00:00'。
实操建议:
- 别退回到宽松模式:关闭
NO_ZERO_DATE会让无效时间入库,后续排查数据异常成本更高 - 用
NULL替代零值:建表时把时间字段设为NULL,PHP 中传null而非字符串'0000-00-00 00:00:00' - 批量修正存量数据:用
UPDATE tbl SET created_at = NULL WHERE created_at = '0000-00-00 00:00:00';(注意备份) - ORM 层拦截:Laravel 的
casts、ThinkPHP 的自动时间戳等,可统一将空值转为NULL而非零日期
不同版本间迁移 SQL 脚本时,GROUP BY 报错 Expression #1 of SELECT list is not in GROUP BY clause
这是 ONLY_FULL_GROUP_BY 生效导致的。5.7 默认开启,8.0 继续保留;但很多旧脚本习惯写 SELECT a, b, COUNT(*) FROM t GROUP BY a(b 不在 GROUP BY 里),在严格模式下直接报错。
实操建议:
- 理解本质:该模式要求 SELECT 列要么是聚合函数结果,要么明确出现在 GROUP BY 中——这是 SQL 标准行为,不是 MySQL bug
- 两种修法:① 补全 GROUP BY:
GROUP BY a, b;② 用 ANY_VALUE() 包裹非分组列:SELECT ANY_VALUE(a), b, COUNT(*)(MySQL 5.7+) - 避免一刀切关掉:禁用
ONLY_FULL_GROUP_BY可能返回不确定结果(比如 b 取哪一行的值?),尤其在主从复制或审计场景下风险明显 - CI/CD 中加校验:用
mysqld --sql-mode="..." --verbose --help模拟目标环境启动,预跑脚本捕获报错
真正麻烦的不是 mode 切换本身,而是那些藏在业务逻辑深处、靠“恰好能跑通”维持多年的 SQL —— 它们不会报错,但结果可能已错很久。上线前务必用真实数据集做全链路验证,而不是只看 DDL 是否执行成功。











