触发器定义必须指定 AFTER
或 BEFORE
和事件类型
MySQL 触发器不能脱离执行时机和操作类型独立存在。必须明确是
BEFORE INSERT、
AFTER UPDATE或
BEFORE DELETE三者之一,且只能作用于单表。常见错误是漏写
AFTER/
BEFORE,导致语法报错
ERROR 1064;或试图在同一个触发器里响应多个事件(比如
INSERT OR UPDATE),这是不被支持的。
实操建议:
一个触发器只绑定一种事件(INSERT/
UPDATE/
DELETE)和一种时机(
BEFORE/
AFTER) 若需多事件响应,必须创建多个触发器,命名时带上事件标识便于管理,例如
trg_user_insert_audit、
trg_user_update_audit
BEFORE触发器可修改
NEW行值(如自动填充
created_at),
AFTER则不能改,但能安全引用
NEW.id做关联插入
NEW
和 OLD
只在对应事件中可用,且不可混淆
在
INSERT触发器中,只有
NEW可用,代表即将插入的行;
DELETE中只有
OLD,代表将被删除的行;
UPDATE中两者都可用,
NEW是新值,
OLD是旧值。误用(比如在
INSERT里读
OLD.name)会直接报错
ERROR 1327(Undeclared variable)。
实操建议:
写触发器前先确认事件类型,再决定访问NEW还是
OLD对
UPDATE,常用
IF NEW.status != OLD.status THEN ... END IF;做变更检测,避免无意义逻辑执行 注意
NEW在
BEFORE INSERT中可赋值,在
AFTER INSERT中只读;同理
OLD在
BEFORE DELETE中只读
触发器中不能调用含副作用的函数,比如 UUID()
、NOW()
要谨慎
MySQL 允许在触发器中用
NOW()、
UUID()等函数,但它们在语句级复制(SBR)模式下可能导致主从不一致——因为从库重放时时间/UUID 不同步。虽然 5.7+ 默认用 ROW 格式,但若 DBA 切回 SBR,这类触发器就会出问题。
实操建议:
优先用CURRENT_TIMESTAMP(作为列默认值)替代触发器里写
NOW()需要唯一 ID 时,用
auto_increment主键或
UUID_SHORT()(它基于服务器ID+时间,可重复性低且 SBR 安全) 绝对避免在触发器中调用
SLEEP()、发起外部 HTTP 请求、写文件等操作——MySQL 不允许
触发器调试困难,上线前必须用 SELECT
模拟 + 错误日志验证
MySQL 不提供触发器单步调试能力,也没有
RAISE(直到 8.0.16+ 才有
SIGNAL)。一旦触发器内部出错(如除零、字段不存在),整个 DML 语句会失败并回滚,但错误信息往往只显示“Trigger xxx has failed”,不指明哪一行。
实操建议:
写完触发器后,先手动执行一遍其中的 SQL 片段(把NEW.xxx换成真实值),确认语法和逻辑正确 在测试库开启
general_log = ON,观察实际执行的语句流;或查
error_log看是否有
TRIGGER相关警告 涉及复杂逻辑时,在触发器开头加
INSERT INTO debug_log VALUES (NOW(), 'trg_foo_start');(需提前建表),但上线前务必删掉——否则影响性能且暴露敏感路径
触发器真正难的不是写,而是它隐式运行、无法单独测试、出错时没有上下文。线上环境只要有一个字段名拼错或条件漏判,就可能卡住整张表的写入。
