ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

彻底搞懂 MySQL 1364 报错:默认值、严格模式与排查方案

彻底搞懂 MySQL 1364 报错:默认值、严格模式与排查方案 做后端开发和数据库运维这几年Field XXX doesnt have a default value是我遇到频率最高的 MySQL 插入报错之一。这个报错几乎不需要看完整日志就能猜到大概原因但真正有意思的是——同样的英文提示背后的触发路径可能完全不同对应的解决方案也分好几个层次。很多人第一次遇到时手忙脚乱改字段、改 SQL、改配置挨个试一遍最后虽然不报错了却不知道到底是哪一步起了作用更不知道有没有埋下新的坑。这篇文章会从原理、排查、解法、实战案例到面试追问把这条报错彻底讲透。不管你是一年经验的 CRUD 选手还是要给生产库做结构变更的负责人只要照着下面的思路走一遍基本能定位到根因并且选到最稳妥的修复方案。1. 报错到底在说什么一条报错的两种触发路径1.1 字段定义里“没有默认值”意味着什么先看一个最简单的表结构CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(50) NOT NULL, age INT NOT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这张表里age字段是 NOT NULL同时没有 DEFAULT。也就是说你插入数据的时候MySQL 要求你必须显式给出age的值否则它就不知道往这个非空字段里塞什么。此时执行INSERT INTO user (name) VALUES (张三);MySQL 直接就会甩出这条错误ERROR 1364 (HY000): Field age doesnt have a default value这个报错的本意很简单你插入了一条记录但某个 NOT NULL 且无默认值的字段没有拿到值MySQL 无法完成这次插入。但事情没这么简单。同样的表同样的 SQL换个数据库版本可能就不报错了。差别在哪儿答案是 sql_mode。1.2 严格模式才是真正的“执法者”MySQL 5.7 开始默认开启了严格模式STRICT_TRANS_TABLES这也是大多数人第一次被这个报错砸中的原因。在非严格模式下往 NOT NULL 字段插入 NULL 或者缺省值MySQL 不会报错而是把该字段替换为“隐式默认值”数值类型塞 0字符串类型塞空字符串时间类型塞当前时间或零值。然后给你甩一条 warning 完事。在严格模式下行为被纠正了——缺省值直接报错插入失败事务回滚。用一句话概括严格模式把“默认补值”的隐性操作变成了“强制校验”的显式报错。这里有一个很多人忽略的细节严格模式的报错对象不只有“完全没有默认值”的字段。只要字段是 NOT NULL即使它有 DEFAULT如果你显式插入 NULL同样会报错。举个例子CREATE TABLE t_order ( id INT NOT NULL AUTO_INCREMENT, status TINYINT NOT NULL DEFAULT 0, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 下面这条在严格模式下报同样的错 INSERT INTO t_order (status) VALUES (NULL);所以排查的第一步是别急着改表结构先分清楚你属于哪种情况是字段真的没有默认值还是你插入了 NULL还是字段有默认值但被省略后依然报错。1.3 报错词条里的隐藏线索这条报错的完整形态通常长这样ERROR 1364 (HY000): Field create_time doesnt have a default value注意几个关键信息ERROR 1364是错误码搜索引擎、日志系统、监控告警里都可以直接用这个数字精确检索。Field create_time精确指向了具体字段不需要你自己去猜哪个字段出了问题。报错信息里没有显示是哪张表、哪条 SQL这需要结合应用日志或者手动复现来定位。如果你用的是 ORM 框架报错可能被包装成类似DataIntegrityViolationException或者SQLIntegrityConstraintViolationException的异常但底层错误码还是 1364。这一点在排查时非常重要——很多人绕了一大圈去查 ORM 配置其实直接看原始异常链里的Caused by就能一步到位。2. 排查三件套字段、sql_mode、SQL 语句2.1 第一步看表结构确认字段定义排查这个报错我建议你先不看 SQL先看表结构。没必要把整张表的字段全背下来只看报错里指出的那个字段就够。SHOW CREATE TABLE user;或者用 DESC 也可以DESC user;重点看三件事这个字段是不是NOT NULL。它有没有DEFAULT值。它是不是自增列、生成列、或者带有 ON UPDATE 之类的特殊属性。这三种情况修复方式完全不同。如果字段是NOT NULL DEFAULT 0那问题多半在插入语句本身如果字段是NOT NULL且没有 DEFAULT那就是字段定义的问题。这里有个高频场景特别值得提醒很多业务表在早期设计时时间字段用的是DATETIME NOT NULL但没有默认值。因为 MySQL 5.6 之前 DATETIME 不能直接用DEFAULT CURRENT_TIMESTAMP导致很多老表对时间字段的插入完全依赖应用代码传值。代码逻辑一变漏传了报错就来了。2.2 第二步检查 sql_mode判断是不是严格模式在拦截SELECT sql_mode; SHOW VARIABLES LIKE sql_mode;两条命令都能看区别不大。输出结果是一长串由逗号分隔的模式参数像这样ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION其中有STRICT_TRANS_TABLES就说明当前会话处于严格模式。MySQL 5.7 以上版本默认都有这个参数8.0 也是。所以如果你没有手动改过配置大概率命中的就是严格模式拦截。如果你拿到一个老项目发现sql_mode是空的或者只包含少数几个参数那就要小心了——这说明数据库的校验规则比默认值宽松很多旧的 SQL 可能一直依赖“隐式补零”的特性在跑。一旦有人把 sql_mode 改标准了全站报错是分分钟的事。判断环境版本时还有个容易混淆的地方STRICT_TRANS_TABLES和STRICT_ALL_TABLES的区别。前者只在“事务表”上启用严格校验InnoDB 就是事务表后者对所有引擎都严格。绝大多数场景下你看到的是前者。2.3 第三步还原现场 SQL确认值是从哪里丢的表结构和 sql_mode 都看完了接下来要找到那条失败的 SQL。这里提供几个常用入口后端服务日志搜错误码 1364或者搜doesnt have a default value。MySQL 慢查询日志如果阈值设置得够低失败的插入也可能记录在里面。ORM 框架的 SQL 日志比如 MyBatis 的showSql、Hibernate 的format_sql都能看到绑定参数后的真实 SQL。找到 SQL 后用 MySQL 客户端手动执行一遍复现问题。注意把表名和字段名替换成生产环境的真实名称不要直接在测试库用SELECT 1这种语句去验证那不是同一回事。我总结一个排查速查表可以先存下来现象可能原因检查方向报错字段无 DEFAULT插入时字段未列出字段定义缺少默认值SHOW CREATE TABLE报错字段有 DEFAULT但插入值显式为 NULLSQL 拼接问题应用层日志、参数绑定报错字段是 DATETIME代码没传值老表设计缺陷表结构 应用代码开发环境不报错生产环境报错两套库 sql_mode 不一致sql_mode对比3. 四种解法怎么选从代码侧到结构侧到全局侧这一节是最核心的实操部分。同样是报错不同场景的最佳解法不一样我先给结论再逐个拆开讲。方案适用场景风险等级恢复速度方案A补全插入值单条 SQL 漏传字段无风险最快方案B给字段加 DEFAULT字段本身应该有一个业务默认值低风险中方案C字段改为可空业务允许该字段为空中风险慢方案D修改 sql_mode海量 SQL 都不规范短期内无法全改高风险最快但不建议3.1 方案A补全插入值治标也治本如果报错只是偶发的、个别的 SQL 漏了字段那最简单直接的做法就是在 INSERT 语句里把这个字段补上-- 原语句 INSERT INTO user (name) VALUES (张三); -- 修正后 INSERT INTO user (name, age) VALUES (张三, 18);如果用的是 ORM检查实体类里是否给这个字段赋了值或者是否在 INSERT 语句中遗漏了列。比如 MyBatis 的 insert 标签写死字段列表时就很容易出现这种漏列的情况。这个方案虽然是“治标”但很多时候其实就是正解——字段设计是有意设为 NOT NULL 的说明业务上这个值必须有那代码里漏传就是代码 bug把代码修好天经地义。3.2 方案B给字段设置合理的默认值如果这个字段本身就应该有一个业务默认值比如订单状态、开关标记、创建时间那给字段加默认值是更合理的做法。-- 给已有字段设置默认值 ALTER TABLE user ALTER COLUMN age SET DEFAULT 0;另一种写法连同字段属性一起调整ALTER TABLE user MODIFY COLUMN age INT NOT NULL DEFAULT 0 COMMENT 年龄;注意ALTER COLUMN ... SET DEFAULT和MODIFY COLUMN的区别。前者只改默认值不动字段类型和注释后者会把整列定义重写一遍如果字段类型、字符集、注释信息很多MODIFY 时要写完整否则容易把原有属性弄丢。这是实际操作里常见的翻车点。如果是时间字段MySQL 5.6.5 之后的版本支持这样设置ALTER TABLE t_order MODIFY COLUMN create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间;这里有个陷阱给 DATETIME 设置默认值时MySQL 8.0.13 之前不支持表达式默认值。如果你非要设成DEFAULT NOW()在 8.0.12 及以下会直接报语法错误。这属于另一个报错但排查时会和 1364 混在一起需要留意。3.3 方案C把字段改为可空有些业务场景下这个字段的值确实不一定存在。如果产品逻辑允许为空那就把字段改成 NULLALTER TABLE user MODIFY COLUMN age INT NULL DEFAULT NULL;这么做能立刻消除报错但要想清楚后果所有依赖这个字段的 SQL 都可能需要处理 NULLWHERE age 0和WHERE age IS NULL查出来的是不同数据。索引效率下降对 NULL 字段的查询特别是IS NULL条件索引利用率往往不如非空字段。代码里如果直接拿这个字段做运算NPE 之类的问题就来了。所以方案C虽然“松绑”最快但我通常不建议在没跟产品确认前就动手。3.4 方案D调整 sql_mode治标不治本的最後手段DBA 群里常有人问能不能直接把 STRICT_TRANS_TABLES 从 sql_mode 里去掉答案是能但强烈不建议在生产环境这么干。-- 查看当前 sql_mode SELECT GLOBAL.sql_mode; -- 去掉 STRICT_TRANS_TABLES会话级重启失效 SET SESSION sql_mode ONLY_FULL_GROUP_BY,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION; -- 全局修改要同时改配置文件否则重启又变回原样去掉严格模式后MySQL 会把缺省值变成隐式默认值行为回到 5.6 时代的“宽松模式”。但代价是你丢失了一次数据校验的机会——一个真实的业务错误可能被静默地替换成 0 或空字符串写进库里。等到哪天查数对不上再往回追溯成本远高于今天花十分钟改代码。如果确实要改建议只改会话级别或者特定业务库级别并且在变更记录里写清楚方便后续回溯。我个人只在一次历史遗留项目迁移时用过这个方案——三千多条历史 INSERT 语句全部没传字段改代码要排期两周才临时用会话级配置做了过渡后面代码改完立刻还原了 sql_mode。4. 实战拆解三个真实场景的处理过程4.1 场景一定时任务批量插入突然报错有一次线上告警数据同步任务的日志里全是Field source_channel doesnt have a default value。奇怪的是这个任务跑了快一年都没出过问题。排查过程先看表结构source_channel是VARCHAR(20) NOT NULL没有默认值。再看插入语句INSERT 列表里确实没有source_channel。翻 Git 提交记录发现前一天有人给这张表加了个source_channel字段并且直接用了NOT NULL无默认值老任务 SQL 没同步更新。解决方案有两处要做给source_channel加一个合理的默认值业务上默认是manual。更新定时任务的 INSERT 语句显式把source_channel写上。很多人加字段时写的是ALTER TABLE sync_log ADD COLUMN source_channel VARCHAR(20) NOT NULL;这行 SQL 在空表上没问题但表里已有历史数据时这条 DDL 会怎么处理存量行答案是 MySQL 会生成隐式默认值空字符串填充到存量行。严格模式下这个 ALTER 本身不报错但后续所有没有显式传值的 INSERT 就全哭了。正确姿势应该是ALTER TABLE sync_log ADD COLUMN source_channel VARCHAR(20) NOT NULL DEFAULT manual COMMENT 来源渠道;这里有个 MySQL 8.0 的细节在 8.0.12 之前的版本ADD COLUMN ... NOT NULL没有指定 DEFAULT 时MySQL 会自动填一个隐式默认值并且 DDL 执行期间会扫描全表、重建表如果表数据量大这期间 DML 基本都会被锁住。8.0.13 之后配合INSTANT算法部分 ADD COLUMN 可以瞬间完成但如果你还在 5.7这种 DDL 一定要挑业务低峰期做。4.2 场景二datetime 字段的“历史遗留问题”另一个高频场景是建表时create_time用了DATETIME NOT NULL却没设默认值。早期很多系统为了省事所有时间都由应用层传入逻辑上是没问题但架不住有人写代码时忘了传。例如这样一张表CREATE TABLE orders ( id INT NOT NULL AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, create_time DATETIME NOT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;在 MySQL 5.6.5 之前DATETIME 确实不支持DEFAULT CURRENT_TIMESTAMP所以老项目的 DBA 通常建议把 create_time 设为 TIMESTAMP。但 TIMESTAMP 有 2038 年的上限问题而且一个表里多个 TIMESTAMP 字段的行为在不同版本下还不太一样非常烦人。如果你们的库是 5.7 及以上直接改成ALTER TABLE orders MODIFY COLUMN create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间;这样应用层不传 create_time 也能自动填充。顺带说一下如果还需要一个 update_time 字段可以这样写update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP这个组合是 InnoDB 表记录创建时间和更新时间的标准姿势能省掉不少应用层的手工赋值。4.3 场景三ORM 框架插入 NULL 引发的“误伤”还有一个容易踩的坑实体类字段定义为基本类型比如 Java 里的int status默认值是 0一般不会传 NULL。但如果你用了包装类型Integer status并且没给这个属性赋值MyBatis 插入时可能会把这个字段拼成NULL于是就有Field status doesnt have a default value即便表里 status 有 DEFAULT 0也会报这个错因为显式插入 NULL 时MySQL 不会拿默认值来顶替。解决方式有三种实体类里给 status 初始化为 0。在 MyBatis 的 insert SQL 里用动态判断比如if teststatus ! nullstatus/if。用 COALESCE 函数在 SQL 层兜底INSERT INTO orders (status) VALUES (COALESCE(?, 0));这三种方案里最推荐第二种。因为第一种依赖开发自觉时间一长容易漏第三种虽然稳但 COALESCE 会掩盖代码里的真实意图排查问题时反而多了一层干扰。5. 面试官视角这条报错的三个衍生追问5.1 sql_mode 到底是什么有哪些常见模式sql_mode 是 MySQL 的一个运行时配置项它控制 MySQL 在数据校验、语法解析上采取哪种行为。它不是一个开关而是一组开关的组合。几个高频参数我整理成表模式作用影响STRICT_TRANS_TABLES事务表的严格模式插入/更新时严格校验报错而非警告STRICT_ALL_TABLES所有表的严格模式非事务表也严格校验影响范围更大NO_ZERO_IN_DATE禁止日期中的月/日为 0比如2024-00-10会被拒绝NO_ZERO_DATE禁止日期为全 00000-00-00会被拒绝ONLY_FULL_GROUP_BY分组查询必须列出所有非聚合列避免 SQL 语义含糊ERROR_FOR_DIVISION_BY_ZERO除数为 0 时直接报错区别于返回 NULLNO_ENGINE_SUBSTITUTION要建的表引擎不存在时直接报错避免静默替换为默认引擎很多人只知道 STRICT_TRANS_TABLES但面试官考察的是你是否理解这些模式之间的协作关系。比如NO_ZERO_DATE如果你把严格模式关闭了NO_ZERO_DATE可能也不会生效具体要看版本和组合方式。5.2 为什么生产环境不建议直接关闭严格模式一句话回答严格模式本质上是一道数据质量的防线。关闭它意味着把原本会拦截的错误、直接以“脏数据”的形式写入磁盘。举个例子用户注册接口漏传了手机号字段是phone VARCHAR(20) NOT NULL严格模式下报错前端会看到“系统繁忙”用户会重试非严格模式下这条记录会被写成空字符串后续营销系统拉数据时看到一堆phone 的记录还不知道这些用户是谁。长此以往数据仓库里全是这种“半成品”数据清洗成本远超修几个接口的成本。5.3 生产库的 sql_mode 应该谁说了算这里有个常见争执开发说“表结构没问题是 DBA 把 sql_mode 改严格了”DBA 说“严格模式是默认配置你们 SQL 写得不规范”。我的意见是如果团队没有统一的数据库规范sql_mode 就不应该随便动。应该在项目初始化阶段就定下来写进数据库规范文档里。比如所有 NOT NULL 字段必须有 DEFAULT。所有时间字段统一用 DATETIME DEFAULT CURRENT_TIMESTAMP。所有 INSERT 语句必须显式列出字段列表。禁止对已有 NOT NULL 字段用ADD COLUMN ... NOT NULL无默认值的方式添加。定好规范之后再遇到 1364 报错就不是“谁改配置”的问题而是“谁违反规范”的问题处理起来快得多。6. 写了这么多年代码才总结出的避坑清单6.1 改表结构前的“过门三问”每次要对正报错的表做 ALTER TABLE先问自己三个问题这个表有多少行数据如果是千万级大表DDL 会不会锁表很久5.7 和 8.0 的行为差异很大务必先查版本改完之后存量数据的该字段值是否符合新约束有没有下游任务在读取这张表字段变更会不会导致他们的抽取脚本报错这三个问题里最容易被忽略的是第三个。我见过一次因为给表加默认值结果下游 BI 报表里的字段映射全乱了第二天的晨会数据全部对不上。所以 DDL 不是“改了就行”而是“改完要通知所有消费方”。6.2 修改 sql_mode 前先做三件事如果实在要调 sql_mode至少做好这三个动作SELECT GLOBAL.sql_mode;先把当前值完整保存下来。只去掉 STRICT_TRANS_TABLES其他参数不要动避免连带影响 ONLY_FULL_GROUP_BY 等其他校验行为。修改后立刻跑一遍核心业务的回归测试不要等用户来投诉。会话级修改只对当前连接有效全局修改要同时更新配置文件。MySQL 8.0 里配置写在my.cnf或者通过SET PERSIST的方式持久化具体用哪种取决于你的部署方式。如果用了云数据库通常控制台里可以直接改参数组。6.3 处理报错时最容易忽略的“同案犯”这条报错还有一个经常一起出现的兄弟Data too long for column。如果你在修 1364 时发现有些字段虽然能插入但值超长被截断了那大概率同一个 SQL 在改造时还会碰上下一个报错。建议一次排查时把表里所有字段的长度约束、字符集都过一遍别修一半又炸一次。6.4 从根上减少这类报错建表规范模板最后分享一个我一直在用的建表模板能规避掉一大部分默认值相关的坑CREATE TABLE demo ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态1正常 0删除, remark VARCHAR(255) NULL DEFAULT NULL COMMENT 备注, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT示例表;四个原则NOT NULL 字段必须带 DEFAULT哪怕是 0 或空字符串。可空字段显式写NULL DEFAULT NULL不让语义模糊。时间字段统一用 DATETIME 的默认时间戳方案。字符集统一 utf8mb4避免中文乱码引发一系列隐性报错。按这个模板建的表基本不会再出现 1364 这类默认值报错。如果后来又加了 NOT NULL 字段记得三问里第一问的答案就是“必须写 DEFAULT”。说白了Field XXX doesnt have a default value这条报错本身并不复杂难的是在紧急时刻分清“谁该背锅”是字段定义不严谨是 SQL 没写全还是 sql_mode 策略变了。我个人处理这类问题的顺序永远是先看结构、再看模式、最后找 SQL这个顺序能帮你最快缩小范围。最后再提醒一句任何修复动作在动生产库之前把变更 SQL 先在测试库执行一遍确认 DDL 耗时和锁表情况再决定什么时间窗口上线。这行干得久了你会发现大部分线上故障不是“没方案”而是“方案没走完验证流程就上了”。
返回列表