MySQL触发器中如何使用OLD关键字来查询触发器前的旧数据值?
- 内容介绍
- 文章标签
- 相关推荐
本文共计855个文字,预计阅读时间需要4分钟。
MySQL触发器中想读取旧数据,必须使用OLD,但它是伪记录(pseudo-row),只在实际特定时机存在,且字段名必须与原表定义完全一致。
哪些触发器能用 OLD?
OLD 仅在 DELETE 和 UPDATE 触发器中可用;INSERT 触发器里访问 OLD 会直接报错:ERROR 1363 (HY000): There is no OLD row in on INSERT trigger。
-
BEFORE DELETE:可读OLD.id、OLD.name等,用于日志记录或条件校验 -
AFTER DELETE:仍可读OLD,但不能改,也不能再影响原操作(语句已执行完) -
BEFORE UPDATE:最常用场景,OLD.col和NEW.col同时可用,适合做变更比对 -
AFTER UPDATE:OLD还在,但NEW已不可修改(改了也无效)
OLD.col 字段名写错会怎样?
字段名区分大小写,且必须与表结构定义一字不差。比如建表时字段是 user_name,就不能写成 OLD.username 或 OLD.USER_NAME —— MySQL 会静默返回 NULL,而不是报错,极难排查。
- 建议先查
DESCRIBE table_name确认字段真实名称 - 含特殊字符(如横线、空格)的字段名必须用反引号包裹:
OLD.`user-id` - 跨库引用时,
OLD无法直接带库名前缀(如db1.OLD.col不合法),只能在同库触发器内使用
比较旧值是否变化时,NULL 怎么处理?
直接写 OLD.status != NEW.status 在任一端为 NULL 时结果恒为 NULL(即 false),导致逻辑失效。MySQL 没有三值逻辑短路,所以不能靠 AND / OR 绕过。
- 正确做法是显式判断:
(OLD.status IS NULL) != (NEW.status IS NULL) OR OLD.status != NEW.status - 更简洁用安全等于:
NOT (OLD.status NEW.status)(是 MySQL 特有空值安全比较符) - 如果只关心某字段被“真正修改”,推荐组合写法:
IF NOT (OLD.col NEW.col) THEN ... END IF;
为什么往其他表 INSERT 失败会导致主事务回滚?
触发器属于原 SQL 事务的一部分。你在 BEFORE UPDATE 里写 INSERT INTO log_table ... VALUES (OLD.id, OLD.name),一旦这条 INSERT 因权限、磁盘满、字段长度超限等失败,整个原始 UPDATE 就会一起回滚 —— 这不是 bug,是 ACID 的体现。
- 若日志写入非关键路径,应改用异步方式(如应用层发消息),而非强依赖触发器
- 无法规避该行为,但可提前检查:比如
INSERT IGNORE或加CONTINUE HANDLER捕获特定错误(仅限存储过程体,触发器中不支持) - 最容易被忽略的是:哪怕只是 SELECT 其他表,只要涉及锁竞争,也可能拖慢主更新,尤其在高并发 UPDATE 场景下
本文共计855个文字,预计阅读时间需要4分钟。
MySQL触发器中想读取旧数据,必须使用OLD,但它是伪记录(pseudo-row),只在实际特定时机存在,且字段名必须与原表定义完全一致。
哪些触发器能用 OLD?
OLD 仅在 DELETE 和 UPDATE 触发器中可用;INSERT 触发器里访问 OLD 会直接报错:ERROR 1363 (HY000): There is no OLD row in on INSERT trigger。
-
BEFORE DELETE:可读OLD.id、OLD.name等,用于日志记录或条件校验 -
AFTER DELETE:仍可读OLD,但不能改,也不能再影响原操作(语句已执行完) -
BEFORE UPDATE:最常用场景,OLD.col和NEW.col同时可用,适合做变更比对 -
AFTER UPDATE:OLD还在,但NEW已不可修改(改了也无效)
OLD.col 字段名写错会怎样?
字段名区分大小写,且必须与表结构定义一字不差。比如建表时字段是 user_name,就不能写成 OLD.username 或 OLD.USER_NAME —— MySQL 会静默返回 NULL,而不是报错,极难排查。
- 建议先查
DESCRIBE table_name确认字段真实名称 - 含特殊字符(如横线、空格)的字段名必须用反引号包裹:
OLD.`user-id` - 跨库引用时,
OLD无法直接带库名前缀(如db1.OLD.col不合法),只能在同库触发器内使用
比较旧值是否变化时,NULL 怎么处理?
直接写 OLD.status != NEW.status 在任一端为 NULL 时结果恒为 NULL(即 false),导致逻辑失效。MySQL 没有三值逻辑短路,所以不能靠 AND / OR 绕过。
- 正确做法是显式判断:
(OLD.status IS NULL) != (NEW.status IS NULL) OR OLD.status != NEW.status - 更简洁用安全等于:
NOT (OLD.status NEW.status)(是 MySQL 特有空值安全比较符) - 如果只关心某字段被“真正修改”,推荐组合写法:
IF NOT (OLD.col NEW.col) THEN ... END IF;
为什么往其他表 INSERT 失败会导致主事务回滚?
触发器属于原 SQL 事务的一部分。你在 BEFORE UPDATE 里写 INSERT INTO log_table ... VALUES (OLD.id, OLD.name),一旦这条 INSERT 因权限、磁盘满、字段长度超限等失败,整个原始 UPDATE 就会一起回滚 —— 这不是 bug,是 ACID 的体现。
- 若日志写入非关键路径,应改用异步方式(如应用层发消息),而非强依赖触发器
- 无法规避该行为,但可提前检查:比如
INSERT IGNORE或加CONTINUE HANDLER捕获特定错误(仅限存储过程体,触发器中不支持) - 最容易被忽略的是:哪怕只是 SELECT 其他表,只要涉及锁竞争,也可能拖慢主更新,尤其在高并发 UPDATE 场景下

