📌 答题前先看:触发器写作模板
DELIMITER $$
CREATE TRIGGER 触发器名
{BEFORE | AFTER} {INSERT | UPDATE | DELETE}
ON 表名
FOR EACH ROW
BEGIN
-- 触发时执行的 SQL
END $$
DELIMITER ;
① NEW/OLD 用错 —— 插入用 NEW、删除用 OLD、修改两个都能用
② BEFORE/AFTER 选错 —— 想改新值用 BEFORE、想记日志用 AFTER
③ 事件忘开调度器 —— SET GLOBAL event_scheduler = ON
📝 第一部分 · 概念巩固
先用几道选择题把核心概念过一遍。
下列代码中,哪个会引起报错?
📖 查看答案
✅ 错误的是:B
INSERT 触发器中没有 OLD(因为是新插入的记录,没有"旧值"可言)。
对应关系表:
① INSERT:只有 NEW(新插入的)
② UPDATE:NEW(新值)+ OLD(旧值)都有
③ DELETE:只有 OLD(被删除的)
口诀:"插入用 NEW,删除用 OLD,修改两个都能用"
需求:"插入员工记录时,如果工资 (salary) 小于 0,自动改为 0 再保存"。下列触发器类型 正确 的是?
📖 查看答案
✅ 答案:A · BEFORE INSERT
分析:
① 要 修改新值(小于 0 改成 0)→ 必须用 BEFORE(AFTER 时数据已经写入,再改 NEW 也来不及)
② 插入事件 → INSERT
所以是 BEFORE INSERT。
口诀:"先校验用 BEFORE,后记录用 AFTER"
下列需求中,必须使用事件(不能用触发器) 实现的是?
📖 查看答案
✅ 答案:B
判断标准:
① "由数据操作触发"(INSERT/UPDATE/DELETE)→ 用 触发器
② "由时间触发"(每天/每小时/到点)→ 用 事件
分析:
A:用户注册 = 表的 INSERT → 触发器
B:每天凌晨 3 点 = 时间到 → 事件 ✅
C:删除订单 = 表的 DELETE → 触发器
D:修改价格 = 表的 UPDATE → 触发器
口诀:"触发器响应数据变化,事件响应时间到来"
需求:"明天上午 10 点把临时表 temp 清空(只清一次)"。下列哪种 SCHEDULE 写法 正确?
📖 查看答案
✅ 答案:B
判断要点:
① 题目说"只清一次" → 用 AT(一次性)
② AT 后面跟 具体的时间字符串,格式 'YYYY-MM-DD HH:MM:SS'
分析:
A 错:EVERY 是周期性,会"每天都清空"
C、D 错:MySQL 不识别 TOMORROW、NEXT MORNING 这种自然语言
口诀:"AT = 在某时(一次),EVERY = 每隔(周期)"
关于触发器和事件,下列说法 错误 的是?
📖 查看答案
✅ 错的是:C
事件没有 NEW 和 OLD。NEW/OLD 是触发器特有的,因为触发器关联到具体的"行"操作。
事件是"到时间就跑",并不关联任何具体的行,所以没有 NEW/OLD 概念。
📖 第二部分 · 读代码(预测结果)必考
本部分给一段代码,不要急着运行,先动脑分析执行流程,再对照答案。
已经创建了下面的触发器,执行 INSERT 后,sc 表中插入的记录长什么样?
DELIMITER $$
CREATE TRIGGER check_score
BEFORE INSERT ON sc
FOR EACH ROW
BEGIN
IF NEW.score IS NULL THEN
SET NEW.score = 0;
ELSEIF NEW.score > 100 THEN
SET NEW.score = 100;
END IF;
END $$
DELIMITER ;
-- 然后执行:
INSERT INTO sc VALUES ('s1', 'c1', NULL);
INSERT INTO sc VALUES ('s2', 'c2', 85);
INSERT INTO sc VALUES ('s3', 'c3', 120);
📖 查看答案
逐条分析:
NEW.score = NULL → 走 IF 分支 → NEW.score 改为 0
实际写入:('s1', 'c1', 0)
NEW.score = 85 → IF 不满足(不是 NULL),ELSEIF 不满足(不大于 100)→ NEW.score 不改
实际写入:('s2', 'c2', 85)
NEW.score = 120 → IF 不满足,ELSEIF 满足(120 > 100)→ NEW.score 改为 100
实际写入:('s3', 'c3', 100)
BEFORE 触发器中可以修改 NEW,会真的改变写入的数据。这就是为什么"数据校验和清洗"通常用 BEFORE 触发器。
已有触发器 + 学生表数据如下,执行 UPDATE 后,update_log 表中会插入什么记录?
-- 学生表 s 中现有数据:
-- sno='s1' sn='张三' maj='信息'
DELIMITER $$
CREATE TRIGGER log_update
AFTER UPDATE ON s
FOR EACH ROW
BEGIN
INSERT INTO update_log VALUES
(OLD.sno, OLD.maj, NEW.maj, NOW());
END $$
DELIMITER ;
-- 然后执行(假设 NOW() 此时是 '2024-12-20 10:00:00'):
UPDATE s SET maj = '计算机' WHERE sno = 's1';
📖 查看答案
分析过程:
把 s1 的 maj 从 '信息' 改成 '计算机'
OLD = (sno='s1', sn='张三', maj='信息') ← 旧值
NEW = (sno='s1', sn='张三', maj='计算机') ← 新值
插入 update_log,对应字段值:
① OLD.sno → 's1'
② OLD.maj → '信息'(旧专业)
③ NEW.maj → '计算机'(新专业)
④ NOW() → '2024-12-20 10:00:00'
① UPDATE 触发器中,OLD 和 NEW 都可用 —— 一个是修改前的,一个是修改后的
② 这就是"记日志"的典型套路:用 OLD 拿旧值,用 NEW 拿新值,加上时间戳,全部塞到日志表
阅读下面的事件代码,分析它的作用,并指出可能存在的问题。
CREATE EVENT clean_old_logs
ON SCHEDULE EVERY 1 DAY
DO
DELETE FROM login_log
WHERE login_time < NOW() - INTERVAL 30 DAY;
📖 查看答案
事件的作用:
EVERY 1 DAY = 每天执行一次(周期性事件)
DELETE FROM login_log WHERE login_time < NOW() - INTERVAL 30 DAY
= 删除 login_log 表中 30 天前的登录记录
这个事件的作用:每天自动清理 30 天前的登录日志,避免日志表无限增长。
① 事件调度器没开就不会执行 —— 必须先 SET GLOBAL event_scheduler = ON
② 没有指定 STARTS,事件会从创建时立即开始计时,可能跟预期的执行时间不同
③ 实际生产中,应该指定凌晨低峰时段执行,避免影响业务
💻 第三部分 · 写代码必考
本部分要求 根据需求自己写触发器或事件。先在纸上画出框架,再对照答案。
需求:在员工表 emp 上创建一个触发器,插入员工时,如果工资 (salary) 小于 0,自动改为 0;如果大于 100000,自动改为 100000。
📖 查看答案
解题思路:
① 要"修改新值" → 必须用 BEFORE
② 是 插入 时校验 → INSERT
③ 用 NEW.salary 拿到要插入的工资值,用 SET NEW.salary = ... 修改
DELIMITER $$
CREATE TRIGGER check_salary
BEFORE INSERT ON emp
FOR EACH ROW
BEGIN
IF NEW.salary < 0 THEN
SET NEW.salary = 0;
ELSEIF NEW.salary > 100000 THEN
SET NEW.salary = 100000;
END IF;
END $$
DELIMITER ;
① 别忘了 DELIMITER —— 触发器内部有多条以 ; 结尾的语句
② FOR EACH ROW 必写
③ SET NEW.salary = ... 真的会改写入的值,这就是 BEFORE 的妙用
需求:商品表 product 中价格变化时,记录到 price_log 表。已有日志表如下:
price_log(pid CHAR(10), old_price DECIMAL(10,2), new_price DECIMAL(10,2), change_time DATETIME)
写一个触发器,当商品价格被修改时,自动记录:商品 ID、旧价格、新价格、修改时间。
📖 查看答案
解题思路:
① 要"记日志"(数据已经改了再记)→ 用 AFTER
② 是 修改事件 → UPDATE
③ 既要旧值(OLD.price)又要新值(NEW.price) → UPDATE 触发器都有
DELIMITER $$
CREATE TRIGGER log_price_change
AFTER UPDATE ON product
FOR EACH ROW
BEGIN
INSERT INTO price_log VALUES
(OLD.pid, OLD.price, NEW.price, NOW());
END $$
DELIMITER ;
更好的写法:加个 IF 判断,只在 OLD.price 和 NEW.price 不同时才插日志(避免没改价格也记一条):
CREATE TRIGGER log_price_change
AFTER UPDATE ON product
FOR EACH ROW
BEGIN
IF OLD.price != NEW.price THEN
INSERT INTO price_log VALUES
(OLD.pid, OLD.price, NEW.price, NOW());
END IF;
END
需求:当从 course(课程表)中删除一门课时,自动删除所有学生选这门课的选课记录(在 sc 表中)。
📖 查看答案
解题思路:
① 删除 course 之前,先去删 sc 中的关联记录(不然外键约束可能导致 course 删不掉)→ 用 BEFORE DELETE
② DELETE 触发器中只有 OLD —— 用 OLD.cno 拿到要删的课程号
DELIMITER $$
CREATE TRIGGER cascade_delete_sc
BEFORE DELETE ON course
FOR EACH ROW
BEGIN
DELETE FROM sc WHERE cno = OLD.cno;
END $$
DELIMITER ;
① DELETE 触发器只有 OLD,没有 NEW
② OLD.cno 就是被删除那门课的课程号
③ 这种"级联删除"也可以用外键约束的 ON DELETE CASCADE 实现,触发器只是另一种思路
需求:创建一个事件 daily_archive,每天凌晨 2 点,把订单表 orders 中 30 天前的订单 移动到归档表 orders_archive,然后从 orders 表中删除这些数据。
📖 查看答案
解题思路:
① "每天凌晨 2 点" → EVERY 1 DAY + STARTS '日期 02:00:00'
② 多条 SQL → 用 BEGIN…END 包起来 + DELIMITER
③ "30 天前" 用 NOW() - INTERVAL 30 DAY
-- 先确保事件调度器开启
SET GLOBAL event_scheduler = ON;
DELIMITER $$
CREATE EVENT daily_archive
ON SCHEDULE EVERY 1 DAY
STARTS '2024-12-21 02:00:00'
DO
BEGIN
-- 1. 先把 30 天前的订单复制到归档表
INSERT INTO orders_archive
SELECT * FROM orders
WHERE order_time < NOW() - INTERVAL 30 DAY;
-- 2. 然后从原表删除
DELETE FROM orders
WHERE order_time < NOW() - INTERVAL 30 DAY;
END $$
DELIMITER ;
① 调度器必须开启,否则事件不跑
② 多条 SQL 要用 BEGIN...END 包起来
③ 用了 BEGIN...END,就要 DELIMITER 改定界符
不写 STARTS,事件从创建时立即开始周期。写 STARTS 可以指定从某个时间点开始。
例如题目要求"每天凌晨 2 点",用 STARTS '2024-12-21 02:00:00' 锚定第一次执行时间,之后每隔 1 天跑一次。
🎯 第四部分 · 综合大题期末重点
本部分把整章知识揉到一起,是 必考综合大题。
某电商订单系统有以下需求,请用触发器和事件实现:
- 需求 A:插入新订单时,如果
status(订单状态)为空,自动设为'待付款' - 需求 B:修改订单状态时,自动把变化记录到
order_status_log表(订单 ID、旧状态、新状态、修改时间) - 需求 C:每天凌晨 1 点,自动把"已完成"超过 90 天的订单状态改为
'已归档'
📖 查看答案
需求 A:BEFORE INSERT 触发器(设默认状态)
DELIMITER $$
CREATE TRIGGER set_default_status
BEFORE INSERT ON orders
FOR EACH ROW
BEGIN
IF NEW.status IS NULL THEN
SET NEW.status = '待付款';
END IF;
END $$
DELIMITER ;
需求 B:AFTER UPDATE 触发器(记日志)
DELIMITER $$
CREATE TRIGGER log_status_change
AFTER UPDATE ON orders
FOR EACH ROW
BEGIN
-- 只在 status 真的变化时才记日志
IF OLD.status != NEW.status THEN
INSERT INTO order_status_log VALUES
(OLD.order_id, OLD.status, NEW.status, NOW());
END IF;
END $$
DELIMITER ;
需求 C:周期性事件(自动归档)
-- 先开启调度器
SET GLOBAL event_scheduler = ON;
CREATE EVENT archive_old_orders
ON SCHEDULE EVERY 1 DAY
STARTS '2024-12-21 01:00:00'
DO
UPDATE orders
SET status = '已归档'
WHERE status = '已完成'
AND complete_time < NOW() - INTERVAL 90 DAY;
① 三个需求分别用了 BEFORE INSERT、AFTER UPDATE、EVENT —— 涵盖本章核心知识点
② 数据校验用 BEFORE,记日志用 AFTER,定时任务用 EVENT —— 这是必考的"选型思维"
③ 注意 需求 C 触发的 UPDATE 也会触发需求 B 的触发器,每条归档操作也会记日志 —— 这是连锁效应的经典场景
需求 C 的事件触发了大量 UPDATE,每条都会触发需求 B 的触发器记日志。如果订单数巨大,可能导致 性能问题或日志表爆炸。实际生产中要权衡。
📝 复习重点回顾
通过本次练习,你应该已经:
- ✅ 掌握 BEFORE/AFTER、NEW/OLD、AT/EVERY 等核心概念
- ✅ 能 读懂 触发器和事件代码,预测执行结果
- ✅ 能 按需求写出 简单的触发器和事件
- ✅ 会做 从业务到代码 的综合大题
按出现频率排序:
- 写触发器代码(综合大题,分值最高)
- NEW 和 OLD 在三种操作中的对应(必考)
- BEFORE vs AFTER 的选择(应用题)
- 触发器 vs 事件 的对比(简答)
- AT vs EVERY 的区别(选择)
① "插入用 NEW,删除用 OLD,修改两个都能用" —— NEW/OLD 对应
② "先校验用 BEFORE,后记录用 AFTER" —— 触发时机选择
③ "AT = 在某时(一次),EVERY = 每隔(周期)" —— 事件调度
④ "触发器响应数据变化,事件响应时间到来" —— 选型口诀
⑤ "事件不跑?先看调度器开了没" —— 必查