mysql存储过程不支持try-catch,必须用declare handler;推荐declare exit handler for sqlexception配rollback和resignal,且需避免隐式提交、嵌套事务污染。

MySQL存储过程里不能用try-catch
MySQL服务端压根不识别try、catch关键字,写进去直接报语法错误。所谓“异常处理”,在存储过程中只能靠DECLARE HANDLER实现,而且它只是声明一个响应动作,不会中断执行流,也不能替代应用层的错误控制逻辑。
常见错误现象:ERROR 1064 (42000): You have an error in your SQL syntax,就因为写了try { START TRANSACTION; ... }这种伪代码。
-
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION:捕获后继续往下走,适合配合IF t_error = 1 THEN ROLLBACK; END IF;手动判断 -
DECLARE EXIT HANDLER FOR SQLEXCEPTION:捕获后立刻退出当前BEGIN...END块,更接近“中断”语义,推荐用于主事务逻辑 - 无论哪种handler,都必须显式写
ROLLBACK,它不会自动触发
必须用EXIT HANDLER才能可靠回滚
用CONTINUE HANDLER时,异常发生后存储过程仍会执行后续语句,如果没加IF判断和ROLLBACK,事务就悬在那里——既没提交也没回滚,可能锁表、阻塞其他操作。
正确写法示例(MySQL 5.7实测有效):
DELIMITER $$
CREATE PROCEDURE safe_transfer()
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL; -- 可选:把原错误重新抛出,方便调用方感知
END;
<p>START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
END$$
DELIMITER ;</p>
-
EXIT HANDLER确保异常一发生就跳出事务块,避免遗漏ROLLBACK -
RESIGNAL不是必须,但建议加上,否则调用方收不到错误信息,只看到返回空或默认值 - 不要在handler里只设变量、不
ROLLBACK,那是无效防护
嵌套存储过程是回滚失效的高发区
MySQL不支持真正的嵌套事务。如果A过程调用B过程,而B过程内部也写了START TRANSACTION和COMMIT,那么B的COMMIT会把A的整个事务提前提交掉——A后续即使出错,ROLLBACK也无事务可滚。
典型症状:调用完存储过程,查表发现部分数据已写入,但按理该全部回滚。
- 排查方法:
SELECT TRX_ID, TRX_STATE FROM information_schema.INNODB_TRX;看是否有长时间ACTIVE的事务 - 解决方案:所有被调用的子过程必须去掉
START TRANSACTION和COMMIT,只做DML;事务控制权完全交给顶层过程 - 若必须复用逻辑,改用函数(FUNCTION)封装查询类操作,避免事务污染
隐式提交会让ROLLBACK彻底失效
哪怕你写了完美的EXIT HANDLER和ROLLBACK,只要事务中执行了DDL语句(如CREATE TABLE、ALTER TABLE、TRUNCATE TABLE),MySQL就会立刻隐式提交当前事务,之后再ROLLBACK什么也不会做。
这个坑特别隐蔽,因为不报错,只是回滚“看起来没生效”。
- 检查是否误用了
CREATE TEMPORARY TABLE——它也会隐式提交 - DDL前后用
SELECT @@autocommit;和SELECT TRX_ID FROM information_schema.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID();确认事务状态 - DDL操作一律剥离到事务外,或改用
INSERT ... SELECT等DML方式模拟
事务真正可靠的回滚,不取决于handler写得多漂亮,而取决于:有没有被隐式提交打断、有没有被嵌套过程污染、以及handler是否用EXIT而非CONTINUE。这三个点漏掉任何一个,ROLLBACK就形同虚设。











