首页 / MySQL 入门教程 / 触发器 TRIGGER

MySQL 入门教程

触发器 TRIGGER

本教程共 46 篇 · 第 36 篇 · 更新于 2026-07-30 · 约 10 分钟阅读

MySQLMySQL 入门教程触发器TRIGGERBEFOREAFTERNEWOLD

36. 触发器 TRIGGER

本节目标:理解触发器的工作原理,掌握 BEFORE/AFTER 触发时机和 INSERT/UPDATE/DELETE 触发事件的组合,学会用 NEW 和 OLD 引用变更前后的数据,了解触发器的适用场景和坑。

36.1 触发器是什么

触发器(Trigger) 是一种特殊的存储过程,它不需要手动调用,而是在表上发生 INSERT、UPDATE 或 DELETE 操作时自动触发执行。

说白了,触发器就是给表装了个”监视器”—数据一变动,它就自动跑。

典型场景:

  • 插入用户时自动记录日志;
  • 删除订单前检查状态;
  • 更新余额时自动写入审计表。

36.2 创建触发器

基本语法:

CREATE TRIGGER 触发器名
{BEFORE | AFTER} {INSERT | UPDATE | DELETE}
ON 表名
FOR EACH ROW
BEGIN
    -- 触发器逻辑
END;

两个关键要素:

  • 触发时机:BEFORE(操作前触发)或 AFTER(操作后触发);
  • 触发事件:INSERT、UPDATE 或 DELETE。

三者组合,一共六种触发场景:

时机事件说明
BEFOREINSERT插入前触发,可修改待插入的值
AFTERINSERT插入后触发,可读取已插入的值
BEFOREUPDATE更新前触发,可修改待更新的值
AFTERUPDATE更新后触发,可读取更新前后的值
BEFOREDELETE删除前触发,可读取待删除的值
AFTERDELETE删除后触发,可读取已删除的值
Note

MySQL 的触发器是行级触发(FOR EACH ROW),即每变更一行就触发一次。没有语句级触发。

36.3 NEW 和 OLD 关键字

触发器内用 NEWOLD 引用变更前后的行数据:

事件OLDNEW
INSERT不可用插入的新值
UPDATE更新前的旧值更新后的新值
DELETE删除前的旧值不可用

实际例子:记录操作日志

-- 先建日志表
CREATE TABLE user_logs (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT,
    action VARCHAR(20),
    log_time DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 创建触发器:用户插入后自动记录日志
DELIMITER //
CREATE TRIGGER trg_user_insert
AFTER INSERT ON users
FOR EACH ROW
BEGIN
    INSERT INTO user_logs(user_id, action) VALUES(NEW.id, 'INSERT');
END //
DELIMITER ;

插入一条用户数据,日志自动生成:

INSERT INTO users(username, email, age) VALUES('zhangsan', 'zhangsan@test.com', 25);

SELECT * FROM user_logs;
-- 自动多了一条记录:user_id=1, action='INSERT'

BEFORE INSERT:修改待插入的值

BEFORE 触发器可以修改 NEW 的值,在数据真正写入前做调整:

DELIMITER //
CREATE TRIGGER trg_user_before_insert
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
    -- 自动把用户名转小写
    SET NEW.username = LOWER(NEW.username);
END //
DELIMITER ;
INSERT INTO users(username, email) VALUES('ZHANGSAN', 'test@test.com');
SELECT username FROM users WHERE email = 'test@test.com';
-- 结果:zhangsan(自动变小写了)
Tip

BEFORE 触发器适合做数据校验和预处理。如果校验不通过,可以用 SIGNAL SQLSTATE 抛出错误阻止操作。

用 SIGNAL 阻止非法操作

DELIMITER //
CREATE TRIGGER trg_check_age
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
    IF NEW.age < 0 OR NEW.age > 150 THEN
        SIGNAL SQLSTATE '45000'
            SET MESSAGE_TEXT = '年龄必须在 0 到 150 之间';
    END IF;
END //
DELIMITER ;
INSERT INTO users(username, age) VALUES('test', 200);
-- ERROR 1644 (45000): 年龄必须在 0 到 150 之间

36.4 UPDATE 触发器:同时用 OLD 和 NEW

更新操作时,OLD 是旧值,NEW 是新值,可以对比变化:

DELIMITER //
CREATE TRIGGER trg_order_update
AFTER UPDATE ON orders
FOR EACH ROW
BEGIN
    IF OLD.status != NEW.status THEN
        INSERT INTO order_logs(order_id, old_status, new_status)
        VALUES(OLD.id, OLD.status, NEW.status);
    END IF;
END //
DELIMITER ;

只有状态真的变了才记日志,避免无意义的日志。

36.5 查看和删除触发器

-- 查看所有触发器
SHOW TRIGGERS;

-- 查看指定库的触发器
SHOW TRIGGERS WHERE `Trigger` = 'trg_user_insert';

-- 查看触发器定义
SHOW CREATE TRIGGER trg_user_insert\G

-- 查 information_schema
SELECT TRIGGER_NAME, EVENT_MANIPULATION, ACTION_TIMING, EVENT_OBJECT_TABLE
FROM information_schema.TRIGGERS
WHERE TRIGGER_SCHEMA = '你的库名';

-- 删除触发器
DROP TRIGGER IF EXISTS trg_user_insert;
Warning

删除表时,表上的触发器会一起被删除。如果重建表,别忘了重建触发器。

36.6 触发器的局限性

触发器好用,但坑也不少:

1. 难以调试

触发器在后台自动执行,出了问题不容易发现。一个表上挂了多个触发器,排查链路很长。

2. 性能影响

每行变更都触发一次触发器逻辑。大批量 INSERT/UPDATE/DELETE 时,触发器会显著拖慢速度。

3. 隐式逻辑

开发者看到一条 INSERT INTO users(...) 语句,不知道背后还跑了一堆触发器逻辑。维护时容易踩坑。

4. 不能返回结果集

触发器内不能有 SELECT 返回结果给客户端(可以 SELECT INTO 变量)。

5. 同表同事件可有多个(需指定顺序)

MySQL 5.7.2+ 已支持同一张表、同一时机、同一事件创建多个触发器,但必须用 FOLLOWSPRECEDES 指定触发顺序。例如:

CREATE TRIGGER trg1 BEFORE INSERT ON users FOR EACH ROW
BEGIN ... END;

CREATE TRIGGER trg2 BEFORE INSERT ON users FOR EACH ROW
FOLLOWS trg1  -- 指定在 trg1 之后触发
BEGIN ... END;
Note

本教程基线为 MySQL 26.7.0,基于 8.0+ 内核,此限制早已不存在。如果你还在用 5.7.2 之前的版本,才会遇到”同表同事件只能一个”的限制。

Tip

我之前踩过这个坑—线上有个表挂了 AFTER UPDATE 触发器写日志,某天批量更新 10 万条数据,触发器逐行执行,跑了 20 分钟。批量操作场景,慎用触发器,改用存储过程批量处理更可控。

36.7 触发器 vs 存储过程 vs 事件调度

特性触发器存储过程事件调度器
触发方式数据变更时自动手动 CALL定时调度
参数无(通过 NEW/OLD)IN/OUT/INOUT
能否返回结果集不能不能
典型场景审计日志、数据校验复杂业务逻辑定时清理、统计

36.8 小结

  • 触发器在表数据变更时自动执行,不需要手动调用;
  • BEFORE 可修改待写入的值,AFTER 可读取已写入的值;
  • NEW 引用新值(INSERT/UPDATE),OLD 引用旧值(UPDATE/DELETE);
  • SIGNAL SQLSTATE 可在 BEFORE 触发器中阻止非法操作;
  • 触发器有调试难、性能差、逻辑隐式等局限,批量操作场景慎用。

下一节进入事务(Transaction),学习如何保证一组操作要么全成功、要么全撤销。