必须用declare exit handler for sqlexception配合start transaction才能触发回滚,仅声明handler不开启事务则无效;应优先按sqlstate精确捕获错误(如'23000'、'22003'),exit handler确保立即退出并回滚,continue handler需手动检查状态,且必须显式rollback并加resignal透传错误。

不加 DECLARE HANDLER 就等于没处理异常——SQL失败后继续执行,事务不会自动回滚,调用方收不到错误,业务逻辑大概率错乱。
必须用 SQLEXCEPTION,但不能只写它
单独写 DECLARE EXIT HANDLER FOR SQLEXCEPTION 能捕获所有未被 SQLWARNING 或 NOT FOUND 拦下的错误,但会掩盖具体原因,导致无法区分可重试冲突(如主键重复)和不可恢复错误(如除零、类型越界)。实际中应优先按 SQLSTATE 精确捕获:
-
'23000':主键/唯一键冲突、外键约束失败,适合记录日志 + 重试或返回友好提示 -
'22003':数值越界(如TINYINT插入 256),需检查输入合法性 -
'22012':除零、NULL参与算术运算,应提前判空或设默认值 - 避免仅用
SQLEXCEPTION做兜底 handler,除非你真打算统一处理所有错误
EXIT 和 CONTINUE 的行为差异直接决定事务是否可靠
选错 handler 类型会让事务控制完全失效:
-
DECLARE EXIT HANDLER:触发后立即退出当前BEGIN...END块,适合包裹在START TRANSACTION内——能天然防止“部分提交” -
DECLARE CONTINUE HANDLER:捕获后继续执行 handler 后面的语句,适合仅设标志位或记日志,但必须手动检查状态并显式ROLLBACK - 在
EXIThandler 中只SET变量却不ROLLBACK,事务会卡住;连接释放时可能被自动回滚,但调用方完全感知不到
ROLLBACK 必须显式写,RESIGNAL 强烈建议加上
handler 里只写 ROLLBACK 不够,调用方很可能收不到错误信息,变成“静默失败”:
- 必须在
EXIT HANDLER中调用ROLLBACK,否则事务不会结束 - 加
RESIGNAL才能让原始错误透出到客户端(比如 Python 的pymysql.err.IntegrityError) - 若 handler 中做了自定义日志或补偿操作,
RESIGNAL必须放在最后,否则后续语句不执行 - 示例写法:
DECLARE EXIT HANDLER FOR SQLSTATE '23000' BEGIN INSERT INTO error_log (msg, created_at) VALUES ('Duplicate key', NOW()); ROLLBACK; RESIGNAL; END;
最容易被忽略的底层限制:MyISAM 和 DDL 会破坏事务边界
哪怕 handler 写得再规范,以下两点不验证就形同虚设:
-
MyISAM表不支持事务,写了START TRANSACTION和ROLLBACK也无效 -
ALTER TABLE、CREATE TABLE等 DDL 语句会隐式提交,导致前面的INSERT无法回滚 - 不要依赖
@@error_count或@@warning_count获取错误详情——它们只反映会话级计数,不是当前错误上下文











