MySQL没有INSERTED和DELETED表,仅支持按操作类型单独定义触发器;INSERT用NEW,UPDATE用NEW和OLD,DELETE用OLD;无法在一个触发器中同时响应INSERT、UPDATE或DELETE,否则报错ERROR 1359。

直接看 INSERTED 和 DELETED 两个虚拟表是否存在记录——这是 MySQL(注意:实际是 SQL Server 的语义)和 MySQL 触发器机制的根本差异点。但你要问的是 MySQL,那得先明确:MySQL **没有** INSERTED 或 DELETED 表。它用的是 NEW 和 OLD,且触发器必须按操作类型(INSERT、UPDATE、DELETE)单独定义,不能像 SQL Server 那样在一个触发器里混写三类操作。
MySQL 不支持单触发器内判断 INSERT/UPDATE
你不能写一个 FOR EACH ROW 触发器同时响应 INSERT, UPDATE,MySQL 会报语法错误:ERROR 1359 (HY000): Trigger cannot have both INSERT and UPDATE in its definition。所以「在一个触发器里判断」这个需求,在 MySQL 中根本不可行——这不是技巧问题,是语法限制。
常见错误现象:
- 复制 SQL Server 写法,如
CREATE TRIGGER t1 BEFORE INSERT, UPDATE ON t FOR EACH ROW ...→ 直接报错 - 试图在
BEFORE INSERT触发器里访问OLD.id→ 报错Unknown column 'OLD.id' in 'NEW/OLD row'
正确做法:分写两个触发器,用 NEW/OLD 存在性反推
虽然不能合写,但你可以通过「触发器类型本身」来确定操作类型:BEFORE INSERT 触发时,OLD 一定不存在;BEFORE UPDATE 触发时,NEW 和 OLD 都存在。所以逻辑上无需“判断”,而是靠触发器绑定的动作来区分。
实操建议:
- 新增日志或校验逻辑,统一放在
BEFORE INSERT触发器中,只处理NEW值 - 修改相关逻辑(比如更新时间戳、检查字段变更),必须放
BEFORE UPDATE触发器,并显式比较OLD.col != NEW.col - 若业务上真需要统一入口(比如都写进 audit_log 表),就建两个触发器,分别插入
operation = 'INSERT'或'UPDATE'
示例(记录操作类型):
CREATE TRIGGER user_insert_audit
BEFORE INSERT ON users
FOR EACH ROW
INSERT INTO audit_log (table_name, operation, row_id, changed_at)
VALUES ('users', 'INSERT', NEW.id, NOW());CREATE TRIGGER user_update_audit
BEFORE UPDATE ON users
FOR EACH ROW
INSERT INTO audit_log (table_name, operation, row_id, changed_at)
VALUES ('users', 'UPDATE', NEW.id, NOW());为什么不能像 SQL Server 那样用 EXISTS(SELECT ... FROM inserted)
因为 MySQL 根本不提供 inserted 或 deleted 虚拟表。它的触发器模型是「每事件一触发器」,每个触发器上下文里只有确定的 NEW(INSERT/UPDATE 有)、OLD(UPDATE/DELETE 有)。强行模拟会导致逻辑混乱和维护困难。
容易踩的坑:
- 在
BEFORE INSERT中写IF OLD.id IS NOT NULL THEN ...→ 语法错误,OLD不可见 - 以为
IF NEW.updated_at = OLD.updated_at能判断字段没变 → 实际只要写了SET updated_at = ...,触发器就执行,且=对NULL返回NULL,要用安全比较 - 为省事把 INSERT 和 UPDATE 逻辑硬塞进存储过程,再在两个触发器里调用 → 可行但掩盖了语义,后续排查难
真正要小心的,不是“怎么判断”,而是别拿 SQL Server 的思维套 MySQL。触发器类型即操作类型,这是 MySQL 的契约。一旦接受这点,代码结构反而更清晰——毕竟你本来就不该在同一个函数里处理完全不同的数据生命周期阶段。


















