declare exit handler for sqlexception 必须配合 start transaction 才有效,否则 rollback 无效;应优先按 sqlstate 精确捕获异常,exit handler 触发后立即退出块,continue 则需手动判错回滚,且 myisam 和 ddl 会破坏事务边界。

DECLARE EXIT HANDLER FOR SQLEXCEPTION 必须配合 START TRANSACTION 才有效
单独声明 DECLARE EXIT HANDLER FOR SQLEXCEPTION 不会触发自动回滚——MySQL 不会因为你写了 handler 就替你开启事务。事务边界必须显式用 START TRANSACTION 划定,否则 handler 触发后只执行内部语句(比如 ROLLBACK),但因无活跃事务,ROLLBACK 实际无效,调用方也收不到错误。
常见错误现象:存储过程里插了重复主键,handler 被触发、ROLLBACK 也执行了,但查表发现数据已部分写入;或者根本没报错,调用方以为成功。
- 必须把业务 SQL 包在
START TRANSACTION和COMMIT之间 -
DECLARE EXIT HANDLER建议放在BEGIN后第一行,避免遗漏 - 如果过程里调用了其他也含事务的存储过程,注意嵌套事务不生效(MySQL 不支持)——子过程的
COMMIT会提前提交父过程的整个事务
只写 SQLEXCEPTION 是懒办法,优先按 SQLSTATE 精确捕获
SQLEXCEPTION 是兜底类型,它捕获所有未被 SQLWARNING 或 NOT FOUND 拦下的错误,但掩盖具体原因。比如主键冲突('23000')和除零错误('22012')都会进同一个 handler,无法区分哪些可重试、哪些该立刻终止。
实际开发中应优先用 SQLSTATE 字符串声明 handler:
-
DECLARE EXIT HANDLER FOR SQLSTATE '23000':主键/唯一键冲突、外键失败,适合记录日志 + 返回用户友好提示 -
DECLARE EXIT HANDLER FOR SQLSTATE '22003':数值越界(如向TINYINT插入 300),需校验输入合法性 -
DECLARE EXIT HANDLER FOR SQLSTATE '22012':除零或NULL参与算术运算,应在计算前加判空逻辑
注意:DECLARE HANDLER 不接受 MySQL 错误码(如 1062),写 FOR 1062 会直接报语法错误。
CONTINUE 和 EXIT handler 的行为差异直接影响事务可靠性
选错 handler 类型会让事务控制彻底失效。核心区别在于执行流是否中断:
-
DECLARE EXIT HANDLER:触发后立即退出当前BEGIN...END块,适合包裹在事务块内——天然防止“部分提交” -
DECLARE CONTINUE HANDLER:触发后继续执行 handler 后面的语句,必须手动检查状态变量并显式ROLLBACK,否则事务悬而未决
典型翻车场景:
- 在
EXIT HANDLER里只SET @err = 1却不ROLLBACK:事务卡住,连接释放时可能被服务端自动回滚,但调用方完全感知不到 - 用
CONTINUE HANDLER捕获异常后忘了IF @has_error THEN ROLLBACK; END IF;:后续 SQL 继续执行,数据状态不可预测
ROLLBACK 必须显式写,RESIGNAL 强烈建议加上
handler 里只写 ROLLBACK 不够。MySQL 默认不会把原始错误透传给客户端,调用方(如 Python 的 pymysql)可能只收到空结果或默认返回值,变成“静默失败”。
正确做法是:ROLLBACK + RESIGNAL,且 RESIGNAL 必须放在最后(若前面有日志插入等操作):
DECLARE EXIT HANDLER FOR SQLSTATE '23000'
BEGIN
INSERT INTO error_log(msg, created_at) VALUES ('Duplicate key on transfer', NOW());
ROLLBACK;
RESIGNAL; -- 放在这里才能确保日志写入后再抛出原错误
END;
最容易被忽略的底层限制:MyISAM 表不支持事务,写了 START TRANSACTION 和 ROLLBACK 也白搭;ALTER TABLE、CREATE TABLE 这类 DDL 会隐式提交,导致前面的 DML 无法回滚——这些不验证,异常处理就是纸糊的。











