handler必须声明在begin后、所有declare之后且首个dml语句之前,否则mysql静默忽略;嵌套块需各自声明;错误类型需匹配,推荐用sqlexception;handler内操作须原子安全;事务需显式开启并配exit handler回滚。

HANDLER声明位置错了,MySQL直接忽略
最常见的情况是把 DECLARE HANDLER 写在了可执行语句之后,或者嵌套在 IF、LOOP 里。MySQL 要求它必须紧接在 BEGIN 后、所有 DECLARE(变量、游标)之后,且**必须在第一个 INSERT/SELECT INTO/UPDATE 之前**。
一旦顺序错乱,比如写成这样:
CREATE PROCEDURE p1() BEGIN DECLARE v INT DEFAULT 0; INSERT INTO t VALUES (1); -- 错!handler 还没声明,这句出错就直接中断 DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET v = 1; END
那 handler 就完全不会生效——MySQL 不报错,也不提示,静默跳过。
- 必须放在变量和游标声明之后、任何 DML 语句之前
- 嵌套的
BEGIN END块需各自声明 handler,外层的管不到内层 - 不要放在存储过程末尾,也不要放在条件分支内部
错误类型不匹配:NOT FOUND ≠ SQLEXCEPTION
用 DECLARE CONTINUE HANDLER FOR NOT FOUND 想捕获主键冲突或字段不存在?不可能触发。因为 NOT FOUND 只在游标 FETCH 到末尾、或 SELECT ... INTO 没查到数据时才触发;而 INSERT 失败、UPDATE 报错属于 SQLEXCEPTION。
实操建议:
- 统一用
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION,覆盖绝大多数运行时异常(如主键冲突 SQLSTATE '23000'、表不存在 '42S02') - 避免硬写 MySQL 错误号(如 1062),改用 SQLSTATE 更稳定
-
SQLWARNING仅用于警告类(如字符串截断),不是错误兜底方案
HANDLER 里再出错,整个过程就崩了
handler 体不是任意代码块:它只能做原子操作。如果在里面写 INSERT INTO log_table,但 log_table 不存在,或字段类型不匹配,就会二次触发 SQLEXCEPTION。而此时没有更上层的 handler,过程直接中断,看起来像“第一次就没捕获”。
安全做法:
- 只做三件事:赋值变量(
SET @err_flag = 1)、写入**已存在且结构稳定的日志表**、调用无副作用函数 - 绝对不要在 handler 里查业务表、建表、执行 COMMIT/ROLLBACK
- 需要错误上下文?必须用
GET DIAGNOSTICS,且它是 handler 内**第一条语句**,插在中间就失效
事务没显式开启,回滚根本不会发生
handler 只是“通知你出错了”,它本身不回滚。如果你没写 START TRANSACTION,或者依赖 AUTOCOMMIT = 0,那即使 handler 设置了标志位,也没法撤回已执行的 INSERT。
正确姿势:
- 开头必须显式
START TRANSACTION - 用
DECLARE EXIT HANDLER FOR SQLEXCEPTION包住整个事务块,并在里面写ROLLBACK - 别在 CONTINUE HANDLER 里硬塞
ROLLBACK——语法不允许,会报错
最容易被忽略的是:handler 生效的前提是“它被真正加载进了执行上下文”,而这个上下文从 BEGIN 开始、到第一个可执行语句为止,一步错,全盘失效。











