MySQL触发器中对NEW字段显式赋值会覆盖表定义的DEFAULT约束及ON UPDATE机制,优先级为:显式传入值 > 触发器SET赋值 > DEFAULT/ON UPDATE;未传值时NEW字段为NULL,不自动取默认值。

MySQL触发器里对 NEW 字段显式赋值,会覆盖建表时定义的默认值逻辑——不是“失效”,而是根本没机会执行。
触发器赋值优先级高于字段 DEFAULT 约束
MySQL 在插入或更新时,字段值的最终来源有明确优先级:显式传入值 > 触发器中 SET NEW.col = ... > 表定义的 DEFAULT。只要你在 BEFORE INSERT 或 BEFORE UPDATE 里写了 SET NEW.col = ...,哪怕右边是 NULL 或空字符串,该字段就再也不会 fallback 到 DEFAULT。
- 常见错误现象:建表时写了
status VARCHAR(20) DEFAULT 'active',但触发器里写了SET NEW.status = NULL;→ 最终入库就是NULL,不是'active' - 更隐蔽的情况:触发器里写
SET NEW.updated_at = NOW();,而字段定义是updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP→ 后者完全被绕过,ON UPDATE逻辑彻底不触发 - TEXT/BLOB 类型字段本就不支持
DEFAULT,但有人误以为加了触发器就能“模拟默认值”,结果发现NEW.content = ''导致空字符串入库,而非预期的NULL或占位文本
INSERT 时未传值 ≠ 触发器能读到 DEFAULT 值
很多人以为“我没给字段赋值,触发器里读 NEW.col 就该是默认值”,这是错的。NEW.col 在触发器中反映的是 SQL 语句实际传入的值:没传就是 NULL(除非字段声明了 NOT NULL 且无默认值,此时会报错),跟 DEFAULT 完全无关。
- 例如:
INSERT INTO user (name) VALUES ('Alice');→NEW.email在触发器中为NULL,哪怕字段定义是email VARCHAR(100) DEFAULT 'no-email@example.com' - 正确做法是在触发器里主动补值:
IF NEW.email IS NULL THEN SET NEW.email = 'no-email@example.com'; END IF; - 别依赖
COALESCE(NEW.email, '...')直接参与计算——如果字段允许NULL,且你又没在触发器里设值,它就真为NULL,后续表达式可能整体变NULL
UPDATE 触发器里改 NEW.col 会破坏 ON UPDATE CURRENT_TIMESTAMP
这是最常被忽略的冲突点。TIMESTAMP 或 DATETIME 字段的 ON UPDATE CURRENT_TIMESTAMP 是 MySQL 自动维护机制,但它只在“你没手动干预该字段”时才生效。一旦触发器里出现 SET NEW.updated_at = ...,不管右边是 NOW() 还是固定时间,这个自动机制就彻底失效。
- 后果:主键更新、索引变更等不涉及业务字段的操作,也会漏掉时间戳更新,导致
updated_at滞后于真实修改时间 - 安全做法:如果必须用触发器控制时间戳,就删掉字段定义里的
ON UPDATE,全部由触发器统一管理;否则,就别碰NEW.updated_at,让它走原生逻辑 - 注意:
NOW()和CURRENT_TIMESTAMP在触发器里行为一致,但前者更兼容旧版本;后者若用在赋值语句右侧(如SET NEW.updated_at = CURRENT_TIMESTAMP;),MySQL 8.0+ 支持,5.7 可能报错
真正要命的不是语法报错,而是字段值“看起来正常”却违背了设计意图——比如 created_at 被触发器设成 NOW(),但 updated_at 因为同时写了 SET NEW.updated_at = NOW(),导致它和创建时间永远相同,后续任何更新都无法刷新。这种隐性逻辑覆盖,查日志都难定位。


















