CREATE TRIGGER 是创建 MySQL 触发器的唯一方式,必须手写 SQL 并用 DELIMITER 切换结束符;BEFORE 可修改 NEW 字段,AFTER 仅能读取 OLD/NEW 且不可改数据;触发器内只能通过 OLD/NEW 访问当前行数据,不可直接查询原表;同一表同一事件+时机仅允许一个触发器。

CREATE TRIGGER 是创建 MySQL 触发器的唯一方式,它不能通过 GUI 工具或 ORM 自动生成,必须手写 SQL 并在数据库会话中执行。
触发器必须用 DELIMITER 改结束符
MySQL 默认以分号 ; 为语句结束符,但触发器体内部常含多条语句(比如 INSERT、IF、SET),若不改结束符,MySQL 会在第一个分号就尝试执行,报错 ERROR 1064。正确做法是临时切换结束符:
- 执行
DELIMITER //(或$$、###等非分号符号) -
CREATE TRIGGER ... BEGIN ... END //中用//结束整个定义 - 最后必须执行
DELIMITER ;恢复默认,否则后续所有语句都会因找不到结束符而失败
BEFORE 和 AFTER 的行为差异直接影响数据一致性
二者不是“早一点/晚一点”那么简单:
-
BEFORE INSERT/UPDATE:可读写NEW字段,比如SET NEW.created_at = NOW()或校验后SET NEW.status = 'pending';若校验失败,可用SIGNAL SQLSTATE '45000'中断主操作 -
AFTER INSERT/UPDATE/DELETE:NEW和OLD均只读,适合日志记录、更新统计表、发送通知等副作用操作;但不能修改当前行数据 - 误把
AFTER当成“还能改数据”的时机,会导致Can't update table in stored function/trigger错误
触发器里访问数据只能靠 OLD 和 NEW,没有别名也没隐式上下文
这是最容易写错的地方——你不能像普通查询那样写 SELECT * FROM student WHERE id = 1,触发器内无法直接查原表(会报 Can't reopen table)。所有数据都来自当前行变更:
-
INSERT:只有NEW可用,OLD不存在 -
UPDATE:OLD.column是旧值,NEW.column是新值;注意NEW.id = OLD.id通常成立(主键未改),但NEW.updated_at一般要手动设 -
DELETE:只有OLD可用,NEW不存在 - 想在
AFTER DELETE中记录被删数据的完整快照?必须在触发器里显式写出所有字段:INSERT INTO log (...) VALUES (OLD.id, OLD.name, ...)
一个表同一事件+时机只能有一个触发器
MySQL 不允许重复定义,比如:
- 已存在
tr_user_after_insert,再建同名或同AFTER INSERT ON user的触发器,会报ERROR 1359: Trigger already exists - 也不能靠“覆盖”实现更新:必须先
DROP TRIGGER IF EXISTS tr_user_after_insert,再重建 - 查看现有触发器用
SHOW TRIGGERS(当前库)或查information_schema.triggers,别依赖客户端 UI 显示是否完整
实际用多了就会发现,触发器真正的难点不在语法,而在于它藏在数据库深处——没人看日志时,它默默运行;出问题时,错误堆栈里根本不会提它的名字。调试时最有效的办法,是先在触发器开头加一条 INSERT INTO debug_log(msg) VALUES ('tr_xxx fired');,确认它真被触发了,再逐步加逻辑。


















