mysql触发器中正确访问修改前后字段值:update触发器可用old和new,insert仅new,delete仅old;字段名须严格匹配原表(大小写敏感),不可赋值old,不可在before delete中用new。

触发器里怎么正确访问修改前后的字段值
MySQL 触发器中,OLD 和 NEW 不是变量,而是只读的行级引用对象:在 UPDATE 或 DELETE 触发器中,OLD 可用;在 INSERT 或 UPDATE 中,NEW 可用。误写成 @OLD.col 或 OLD_col 会直接报错 Unknown column 'OLD.xxx' in 'field list'。
实操建议:
-
OLD.col_name和NEW.col_name必须严格匹配原表字段名(大小写敏感取决于系统配置) - 不能对
OLD赋值,也不能在BEFORE DELETE中用NEW - 如果字段允许 NULL,
OLD.col在INSERT中不可用,强行引用会触发ERROR 1327 (42000): Undeclared variable: OLD
INSERT/UPDATE/DELETE 对应的历史表插入逻辑怎么写
三类操作需分别处理,不能共用一个触发器。历史表通常要额外记录操作类型、时间、操作人(如从 USER() 获取),且主键不能照搬原表 —— 否则 UPDATE 会因主键冲突失败。
常见错误:把历史表设计成和原表完全一样,然后在 BEFORE UPDATE 里写 INSERT INTO history_table SELECT * FROM OLD —— 这会因主键/唯一键重复被拒绝。
实操建议:
- 历史表主键设为自增
id,另加op_type ENUM('INSERT','UPDATE','DELETE')、op_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP、op_user VARCHAR(100) -
INSERT触发器只读NEW;DELETE只读OLD;UPDATE两者都可用,适合做字段级变更比对 - 避免在触发器里调用存储函数或复杂子查询,否则可能拖慢主表 DML 性能
如何只记录真正变化的字段,而不是整行快照
全量记录虽简单,但冗余高、查询慢。用 IF 判断字段是否变化,可缩小日志体积,也便于后续审计定位。但注意:NULL 值比较必须用 IS NULL / IS NOT NULL,不能用 = 或 !=。
示例(在 BEFORE UPDATE 中):
IF OLD.name != NEW.name OR (OLD.name IS NULL) != (NEW.name IS NULL) THEN INSERT INTO user_history (user_id, field, old_value, new_value, op_time) VALUES (OLD.id, 'name', OLD.name, NEW.name, NOW()); END IF;
实操建议:
- 字符串字段建议用
IFNULL(OLD.col, '') != IFNULL(NEW.col, '')统一处理 NULL,更简洁 - 时间戳、数字等类型可直接比较,但要注意时区和精度(如
DATETIME(6)微秒级需显式指定) - 别在触发器里拼接 JSON 字符串存变更详情 —— MySQL 5.7+ 虽支持
JSON_OBJECT,但性能损耗明显,更适合应用层处理
触发器执行失败会导致主表操作回滚吗
会。MySQL 中触发器属于事务一部分:BEFORE 触发器报错(比如插入历史表时违反 NOT NULL 约束),主 INSERT/UPDATE/DELETE 就不会执行;AFTER 报错则整个事务回滚。这点常被忽略,导致“数据没改成功但也没提示错”的假象。
实操建议:
- 历史表字段尽量设为
TEXT或VARCHAR(2000),避免因长度超限触发失败 - 所有写入历史表的操作,都加上
ON DUPLICATE KEY UPDATE或提前INSERT ... SELECT ... WHERE NOT EXISTS防重,但注意这会增加锁竞争 - 生产环境务必开启
log_error_verbosity = 3,否则触发器内部错误只会记到 error log,不返回给客户端
最麻烦的是跨库写入历史表 —— 如果历史库临时不可达,整个业务 DML 就卡死。真要解耦,得放弃触发器,改用应用层双写或 binlog 解析方案。











