before insert触发器可自动填充非虚拟列、非自增主键(或显式覆盖)、非显式赋值的not null字段,如created_at、uuid、status、slug等;需用delimiter避免语法错误,禁递归修改同表,时区敏感宜用utc_timestamp()存utc时间。

BEFORE INSERT触发器能自动填充哪些字段
只要字段允许写入(非 GENERATED ALWAYS AS 虚拟列、非主键自增且未显式设为 NULL),就能在 BEFORE INSERT 中赋值。常见用途包括:created_at 时间戳、uuid、status 默认值、slug 衍生字段等。注意:不能修改 AUTO_INCREMENT 主键本身,但可干预其插入值(需确保不冲突)。
-
NEW.created_at := NOW()是安全的;NEW.id := 123在主键已设AUTO_INCREMENT时会覆盖自增逻辑(仅当明确想跳过自增时才这么做) - 若表有
NOT NULL字段但 INSERT 语句没提供值,必须在触发器中赋值,否则触发器执行后仍会报Field 'xxx' doesn't have a default value - 触发器中不能调用存储函数返回结果再赋给
NEW.xxx—— 实际可以,但要注意函数不能含非确定性操作(如NOW()在函数内是允许的,但SELECT RAND()在某些 MySQL 版本+严格模式下可能报错)
创建语法和必须避开的坑
核心是用 DELIMITER 切换语句结束符,否则分号会让 MySQL 误认为触发器定义提前结束。另外,触发器名在库内必须唯一,且不能与表同名。
DELIMITER $$
CREATE TRIGGER users_set_created_at
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
IF NEW.created_at IS NULL THEN
SET NEW.created_at = NOW();
END IF;
IF NEW.status IS NULL THEN
SET NEW.status = 'active';
END IF;
END$$
DELIMITER ;
- 漏写
DELIMITER $$和结尾的DELIMITER ;→ 报错You have an error in your SQL syntax - 在触发器里写
INSERT INTO ...或UPDATE ...操作同一张表 → 触发Can't update table 'xxx' in stored function/trigger(MySQL 禁止“递归”修改) -
NEW是只读别名,只能用SET NEW.xxx = ...赋值,不能SELECT ... INTO NEW.xxx(语法错误)
时间字段用 NOW() 还是 CURRENT_TIMESTAMP
二者在 BEFORE INSERT 中行为一致,都取当前事务开始时刻的时间。区别在于语义和建表时的默认约束场景:CURRENT_TIMESTAMP 更常用于列定义的 DEFAULT,而触发器里用哪个都行。但要注意时区——它们都按 MySQL 服务端时区(system_time_zone 或 time_zone 变量)返回值。
- 如果应用层传的是 UTC 时间,但 MySQL 时区设为
'Asia/Shanghai',NOW()返回的就是东八区时间,不是 UTC - 想存 UTC 时间?改用
UTC_TIMESTAMP(),它不受time_zone设置影响 - 不要在触发器里写
NEW.updated_at = NOW()——BEFORE INSERT不处理更新,那是BEFORE UPDATE的事
调试触发器不生效的三件事
最常见原因是触发器根本没被调用,而不是逻辑写错了。先确认基础链路通不通。
- 检查是否真执行了
INSERT:用SELECT * FROM information_schema.TRIGGERS WHERE EVENT_OBJECT_TABLE = 'users';确认触发器存在且状态为ENABLED - 确认 INSERT 语句没显式给目标字段赋值:比如写了
INSERT INTO users (created_at) VALUES (NULL),那IF NEW.created_at IS NULL会进分支;但如果写了INSERT INTO users (created_at) VALUES ('2020-01-01'),就不会触发填充 - 开启通用日志或用
SELECT NOW(), SLEEP(0.1), NOW();在触发器里临时加日志(不推荐生产用),更靠谱的是在应用层打日志,比对 INSERT 前后字段值变化
触发器逻辑看似简单,但字段是否为空、时区是否对齐、INSERT 是否带显式值——这三个点任意一个没对上,填充就会静默失效。











