mysql触发器无法捕获运行时异常,错误会直接导致dml回滚且无法用declare handler等机制处理;唯一可行方式是在before触发器中预检并signal主动报错。

MySQL触发器根本不能捕获运行时异常
直接说结论:你无法在 BEFORE 或 AFTER 触发器里用 DECLARE HANDLER、TRY...CATCH 或 GET DIAGNOSTICS 捕获错误。一旦触发器内语句出错(比如 INSERT 违反唯一约束、UPDATE 更新被锁定的行、除零、字段为 NOT NULL 却赋 NULL),整个原始 DML 语句立即回滚,触发器执行中断,什么日志都留不下。
常见现象包括:ERROR 1062 (23000): Duplicate entry、ERROR 1048: Column 'xxx' cannot be null、ERROR 1422: Explicit or implicit commit is not allowed——这些错误不会进入任何 handler,也不会被 RESIGNAL 捕获。
想“处理异常”,只能靠 BEFORE 触发器主动预检 + SIGNAL
既然没法事后捕获,就只能事前拦截。在 BEFORE INSERT 或 BEFORE UPDATE 中,用 SELECT ... INTO 查询、IF 判断、SIGNAL 主动报错,是唯一可控的方式。
- 适合场景:检查邮箱格式、判断用户名是否已存在、校验金额非负、确认外键关联记录存在等
- 注意并发安全:
SELECT ... INTO只能防住“明显违规”,无法 100% 避免唯一冲突(需配合应用层重试或存储过程的CONTINUE HANDLER FOR 1062) -
SIGNAL必须带明确SQLSTATE或错误码,例如SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '邮箱已被注册' - 避免在触发器里做耗时查询(如全表扫描),否则会拖慢所有 DML
示例:防止插入空邮箱
DELIMITER $$
CREATE TRIGGER check_email_not_null
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
IF NEW.email IS NULL OR TRIM(NEW.email) = '' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '邮箱不能为空';
END IF;
END$$
DELIMITER ;
真正能捕获错误的是存储过程,不是触发器
如果你需要记录错误到 error_log 表、执行补偿逻辑、或根据错误码分支处理,必须把核心逻辑从触发器移出去,改用带 DECLARE HANDLER 的存储过程封装。
-
CONTINUE HANDLER FOR 1062可捕获唯一冲突并继续执行后续语句(如转为UPDATE) -
EXIT HANDLER FOR SQLEXCEPTION可在出错时写日志,再用RESIGNAL向上抛原始错误 - 触发器禁止调用含事务或 handler 的存储过程(MySQL 明确报错
ERROR 1422) - 应用层应调用该存储过程,而非直连
INSERT/UPDATE
示例:邮箱冲突时更新最后登录时间
DELIMITER $$
CREATE PROCEDURE upsert_user(IN p_email VARCHAR(255))
BEGIN
DECLARE CONTINUE HANDLER FOR 1062
UPDATE users SET last_login = NOW() WHERE email = p_email;
INSERT INTO users (email, created_at) VALUES (p_email, NOW());
END$$
DELIMITER ;
别让触发器碰自己正在修改的表
这是最容易被忽略、却高频报错的点:在触发器中对触发它的同一张表执行 INSERT/UPDATE/DELETE,MySQL 会直接拒绝,报 ERROR 1442: Can't update table 'xxx' in stored function/trigger。
- 哪怕只是
AFTER INSERT里去查本表最新一条记录,也属于“访问被修改表”,可能失败 - 替代方案:用临时表暂存中间结果;或改用事件调度器(
EVENT)延迟操作;或彻底放弃,交由应用层异步处理 - 日志类需求(如记录谁改了哪条数据)建议写入另一张独立日志表,且该表不要和业务表共用主键/唯一索引
复杂点在于:错误不是总立刻暴露。有些逻辑在单线程测试时正常,一上生产并发就崩——因为预检(SELECT)和实际写入之间存在时间窗口,而触发器又不支持加锁控制。这种情况下,靠触发器“处理异常”本身就是设计误用。











