DECLARE EXIT HANDLER必须配合START TRANSACTION才有效,否则仅捕获错误却不回滚,事务悬置;正确顺序是先START TRANSACTION,再声明EXIT HANDLER,最后执行业务SQL,并显式编写ROLLBACK与RESIGNAL。

DECLARE EXIT HANDLER 必须配 START TRANSACTION 才有效
不配 START TRANSACTION 的 DECLARE EXIT HANDLER FOR SQLEXCEPTION 是个摆设——它能捕获错误,但不会自动回滚,事务状态仍挂着,可能锁表、阻塞其他连接。
常见错误写法是先声明 handler,再开事务,或干脆漏掉 START TRANSACTION。正确顺序必须是:
-
START TRANSACTION在最前(事务真正开始) - 紧接着声明
DECLARE EXIT HANDLER FOR SQLEXCEPTION - 然后才是业务 SQL(
INSERT/UPDATE等)
否则异常发生时 handler 还没注册上,直接抛错退出,事务既没提交也没回滚。
ROLLBACK 必须显式写,不能靠 handler 自动触发
MySQL 存储过程里没有隐式回滚机制。EXIT HANDLER 触发后只退出当前作用域,ROLLBACK 得自己写,否则数据已改、事务未结,后果比失败更糟。
典型安全写法包含 RESIGNAL,否则调用方收不到原始错误码(比如 1062 主键冲突),只能看到空结果或默认返回值:
DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; RESIGNAL; END;
注意:RESIGNAL 要放在 ROLLBACK 后,否则事务还没撤就往外抛错,状态不一致。
多步校验要用 CONTINUE HANDLER + 标志变量
当你需要执行完所有 SQL 再统一判断成败(比如先插日志、再查状态、最后更新主表),就得放弃 EXIT HANDLER,改用 CONTINUE HANDLER 配合标志变量。
关键点:
- 在
START TRANSACTION前初始化变量:DECLARE has_error INT DEFAULT 0; - handler 只负责设标记:
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET has_error = 1; - 所有 SQL 执行完后,用
IF has_error THEN ROLLBACK; ELSE COMMIT; END IF;统一收口
这种模式下,某条 SQL 报错后后续语句仍会执行——你得确保这些语句本身不破坏数据一致性(比如不能有“先删后插”这类依赖顺序的操作)。
DDL 语句会让 ROLLBACK 失效,必须提前规避
CREATE、DROP、ALTER 这类 DDL 语句会触发 MySQL 隐式提交,一旦执行,前面所有 START TRANSACTION 就立刻终结,ROLLBACK 对它和它之前的操作都无效。
所以:
- 事务块里绝对不要混 DDL —— 比如在转账过程中顺手
CREATE TEMPORARY TABLE,会导致前面的UPDATE无法回滚 - 真要动态建表,必须拆到事务外,或改用
SAVEPOINT分段控制(但SAVEPOINT也挡不住 DDL 的隐式提交) - DDL 带来的复制/同步风险更大:MySQL 9.6.0 虽把外键约束上移到 SQL 层,但 DDL 本身的事务边界没变,Binlog 记录仍不可逆
事务不是万能胶,DDL 就是那个撕开它的口子——写之前先看一眼有没有 CREATE 或 ALTER,比事后 debug 更省时间。


















