区分 BEFORE 触发器修改 NEW 与 AFTER 触发器只能读取行值的行为
MySQL 触发器里最容易混淆的不是 BEFORE 和 AFTER 的拼写,而是“当前能读到哪一行、当前能不能改值”。处理自动清洗字段时,应该在 BEFORE 触发器里修改 NEW;处理审计或历史记录时,应该在 AFTER 触发器里读取 OLD 和 NEW。OLD 始终是只读值,AFTER 中即使能引用 NEW,也不能再靠赋值改变已经完成的行操作。
MySQL 8.4 官方资料:https://dev.mysql.com/doc/refman/8.4/en/trigger-syntax.html
先按事件判断 OLD/NEW 是否存在,再按触发时机判断 NEW 是否允许赋值:INSERT 只有 NEW,DELETE 只有 OLD,UPDATE 同时有 OLD 和 NEW;真正需要改待写入数据时,把 SET NEW 放在 BEFORE。
INSERT:没有旧行,只能引用NEW.col_name。DELETE:没有新行,只能引用OLD.col_name。UPDATE:OLD是更新前值,NEW是待更新值或更新后值。OLD只读;NEW只有在BEFORE中才适合通过SET修改。
先把三个触发事件的值域分开
OLD 和 NEW 不是两张可以随意切换的临时表,而是由触发事件决定的行值别名。INSERT 没有可供比较的旧行,所以不存在 OLD;DELETE 没有待写入的新行,所以不存在 NEW;UPDATE 才同时拥有前后两个视角。
下面这张图只表达引用关系和写入权限,是静态说明图,不是数据库客户端截图,也不是运行结果。写触发器前先按这个矩阵排除一半错误,通常比遇到报错后再猜更快。

| 事件 | 可引用的别名 | 别名含义 | 可修改对象 |
|---|---|---|---|
| INSERT | NEW | 即将插入的新行 | BEFORE 中可修改 NEW |
| UPDATE | OLD、NEW | 更新前值、待更新值 | BEFORE 中可修改 NEW,OLD 只读 |
| DELETE | OLD | 即将删除的现有行 | OLD 只读 |
用 BEFORE 规范化待写入的 NEW
假设订单表允许调用方传入状态,但业务约定空状态统一变成 pending,大写状态统一转成小写。这个动作必须发生在行写入之前,否则数据库已经接受了原始值,后续的 AFTER 阶段只能观察,不能再把行改回规范状态。
-- 创建示例表;状态列的默认值只覆盖未提供值的场景
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
status VARCHAR(20) NOT NULL,
updated_at TIMESTAMP NOT NULL
);
-- 在 UPDATE 之前整理待写入的状态值;SET NEW 会影响本次行更新
DELIMITER //
CREATE TRIGGER orders_before_update
BEFORE UPDATE ON orders
FOR EACH ROW
BEGIN
-- OLD.status 只能读取,不能被赋值;只规范化 NEW.status
SET NEW.status = LOWER(TRIM(NEW.status));
-- 空字符串回退到业务默认状态,避免把无意义值写入表中
IF NEW.status = '' THEN
SET NEW.status = 'pending';
END IF;
END//
DELIMITER ;
这里的 NEW.status 表示本次 UPDATE 准备写入的值,触发器可以在数据库真正修改该行前把它整理好。OLD.status 则代表修改前的值,适合拿来比较“状态是否发生变化”,不适合拿来做赋值目标。
如果是 INSERT,同样可以在 BEFORE INSERT 中给 NEW 的字段补默认值;如果是 DELETE,则没有可供赋值的 NEW。这种按事件拆开的写法能让触发器的输入输出边界保持清楚。
用 AFTER 记录已经完成的前后值
审计记录关注的是一次成功完成的行变更,因此更适合放在 AFTER UPDATE。此时可以同时读取 OLD 和 NEW,把更新前状态、更新后状态和业务主键写入审计表。AFTER 中的 NEW 是用于读取的结果视角,不是再次修改主表的入口。
-- 审计表只保存状态变化,不把触发器元数据混进业务订单表
CREATE TABLE order_status_audit (
audit_id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_id BIGINT NOT NULL,
old_status VARCHAR(20) NOT NULL,
new_status VARCHAR(20) NOT NULL,
changed_at TIMESTAMP NOT NULL
);
-- AFTER 阶段读取已完成的前后值,记录一次成功的状态变化
DELIMITER //
CREATE TRIGGER orders_after_update
AFTER UPDATE ON orders
FOR EACH ROW
BEGIN
-- 只有真正发生状态变化时才写审计,减少无意义记录
IF NOT (OLD.status NEW.status) THEN
INSERT INTO order_status_audit
(order_id, old_status, new_status, changed_at)
VALUES
(NEW.id, OLD.status, NEW.status, CURRENT_TIMESTAMP);
END IF;
END//
DELIMITER ;
是 MySQL 的 NULL 安全比较运算符。示例用它判断前后状态是否真正不同,避免普通比较遇到 NULL 时得到未知结果。审计触发器只读取 OLD 和 NEW,把写入目标放在独立的审计表中。

为什么 AFTER 中的 SET NEW 不会改变主表
触发器的动作时机决定了它能做什么。BEFORE 在行操作完成前触发,修改 NEW 等于修改即将提交给表的值;AFTER 只有在前置触发器和行操作成功后才执行,主表这一行已经完成本次变化,因此再写 SET NEW.column = ... 没有可改变的待写入阶段。
-- 下面是错误的放置方式:AFTER 中不能用 SET NEW 改回主表字段 DELIMITER // CREATE TRIGGER orders_after_update_invalid AFTER UPDATE ON orders FOR EACH ROW BEGIN -- 这里试图修改已经完成的行操作,不能作为 AFTER 的修正手段 SET NEW.status = 'pending'; END// DELIMITER ;
需要改变主表最终值时,应把修正规则移动到对应的 BEFORE INSERT 或 BEFORE UPDATE。需要记录变更时,则在 AFTER 中读取值并写入审计表。不要把“修改主表”和“记录主表变化”塞进同一个错误时机。
按 INSERT、UPDATE、DELETE 选择正确引用
除了时机,还要核对事件。把 UPDATE 触发器中的写法原样复制到 INSERT 或 DELETE,常见结果就是引用了不存在的别名。可以把实际需求先翻译成“我要读旧值、读新值,还是要改待写入值”,再选择事件和时机。
-- INSERT 只能读取 NEW;适合给新行补齐可计算字段
CREATE TRIGGER orders_before_insert
BEFORE INSERT ON orders
FOR EACH ROW
BEGIN
-- 新行没有 OLD;把空状态整理为统一的业务值
IF NEW.status IS NULL OR TRIM(NEW.status) = '' THEN
SET NEW.status = 'pending';
END IF;
END;
-- DELETE 只能读取 OLD;适合把被删除行的关键字段写入归档表
CREATE TRIGGER orders_after_delete
AFTER DELETE ON orders
FOR EACH ROW
BEGIN
-- 删除后没有 NEW;审计内容必须来自 OLD
INSERT INTO order_status_audit
(order_id, old_status, new_status, changed_at)
VALUES
(OLD.id, OLD.status, 'deleted', CURRENT_TIMESTAMP);
END;
上面的两个例子分别体现“新行进入前补值”和“旧行离开后记账”。DELETE 的 AFTER 仍然可以读 OLD,但不能凭空构造一个代表新行的 NEW;INSERT 的 BEFORE 也不能拿 OLD 做前后比较。
发布前检查权限、定义和失败边界
触发器代码看起来正确,还要检查执行账号的权限和失败行为。MySQL 文档说明,读取相关列需要相应的 SELECT 权限,在 BEFORE 中修改 NEW 还需要目标列的 UPDATE 权限;触发器本身的创建也需要关联表的 TRIGGER 权限。生产发布不要只把 DDL 复制到共享账号下执行。
-- 查看触发器最终定义,确认事件和 BEFORE/AFTER 没有写反 SHOW CREATE TRIGGER orders_before_update; SHOW CREATE TRIGGER orders_after_update; -- 查看表级触发器清单,避免只改了一个同名意图的触发器 SELECT TRIGGER_NAME, EVENT_MANIPULATION, ACTION_TIMING, ACTION_STATEMENT FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA = DATABASE() AND EVENT_OBJECT_TABLE = 'orders' ORDER BY ACTION_TIMING, EVENT_MANIPULATION, TRIGGER_NAME; -- 查看当前会话权限;发布前按最小权限核对,而不是默认放大账号权限 SHOW GRANTS;
还要把失败边界写进发布说明:BEFORE 或 AFTER 触发器报错都会让触发它的语句失败;对事务表,语句失败通常会回滚该语句带来的变化。因而审计表字段约束、触发器账号权限和业务表写入要一起设计,不能只关注 SET NEW 是否能通过语法解析。
常见问题
UPDATE 触发器里的 OLD 和 NEW 分别是什么?
OLD 是更新前的列值,NEW 是准备更新或更新后的列值。需要比较变化时读取两者,需要改变最终写入值时只在 BEFORE 中赋值给 NEW。
AFTER UPDATE 还能读取 NEW 吗?
可以。AFTER 适合读取本次成功行操作对应的 NEW,例如写审计记录;但它不再是修改主表待写入值的阶段。
DELETE 触发器为什么不能使用 NEW?
DELETE 没有新行,触发器只能使用 OLD 读取即将删除的现有行。归档和审计字段应从 OLD 取值。
BEFORE 中修改 NEW 需要注意什么?
确认触发器事件确实是 INSERT 或 UPDATE,执行账号拥有目标列的更新权限,并且赋值后的值仍满足列类型、非空和业务约束。不要把 AFTER 中的修正逻辑当作替代方案。
小结
判断 MySQL 触发器中的 OLD 和 NEW,可以固定按两步走:先按 INSERT、UPDATE、DELETE 确认别名是否存在,再按 BEFORE、AFTER 确认是否处于可修改待写入值的阶段。清洗和补值放在 BEFORE,审计和记录放在 AFTER;OLD 始终只读,AFTER 中的 NEW 主要用于读取,不要再尝试通过赋值修改已经完成的主表行。
使用 FOLLOWS 或 PRECEDES 明确同一事件多个触发器的先后关系
- 上一篇
- 使用 FOLLOWS 或 PRECEDES 明确同一事件多个触发器的先后关系
- 下一篇
- 用验证报告代替布尔结果定位 JSON Schema 中缺失或类型错误的字段
-
- 数据库 · MySQL | 1小时前 |
- 用验证报告代替布尔结果定位 JSON Schema 中缺失或类型错误的字段
- 230浏览 收藏
-
- 数据库 · MySQL | 1小时前 |
- 使用 FOLLOWS 或 PRECEDES 明确同一事件多个触发器的先后关系
- 333浏览 收藏
-
- 数据库 · MySQL | 1小时前 | MySQL · 视图 · MySQL视图 CHECK TABLE SHOW CREATE VIEW 视图失效 表结构迁移
- 定位底层表变更导致的视图失效,并用 CHECK TABLE 检查定义问题
- 161浏览 收藏
-
- 数据库 · MySQL | 1小时前 |
- 用嵌套可更新视图对比 LOCAL 与 CASCADED 对写入条件的检查范围
- 363浏览 收藏
-
- 数据库 · MySQL | 1小时前 |
- 说明 CREATE TABLE SELECT 默认忽略隐藏列时如何显式选择并检查新表属性
- 266浏览 收藏
-
- 数据库 · MySQL | 1小时前 |
- 区分通配查询与显式列名查询在 INVISIBLE 列上的返回差异
- 213浏览 收藏
-
- 数据库 · MySQL | 1小时前 |
- 用 LOAD DATA IGNORE 导入时识别违反 CHECK 的行并保留警告信息
- 170浏览 收藏
-
- 数据库 · MySQL | 1小时前 | MySQL · 迁移 · mysql 数据迁移 CHECK NOT ENFORCED ENFORCED
- 比较 NOT ENFORCED 与 ENFORCED 的迁移步骤并处理存量脏数据
- 395浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 450次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 520次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 535次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 480次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 304次使用
-
- MySQL JSON_TABLE 展开数组时如何保留缺失字段
- 2026-09-10 501浏览
-
- MySQL 分区表怎么处理跨分区唯一键:分区列约束与建表取舍
- 2026-08-30 501浏览
-
- MySQL权限管理设置全攻略
- 2026-03-29 501浏览
-
- MySQL分片实现方法及常见方案解析
- 2025-06-24 501浏览
-
- MySQL表空间碎片怎么清理?超详细优化教程
- 2025-06-13 501浏览

