必须显式写START TRANSACTION才能开启事务保护,因MySQL默认autocommit=1,每条DML独立提交,不开启事务则ROLLBACK无效。

存储过程中不加 START TRANSACTION 就没有事务保护
很多人写完存储过程发现某条 INSERT 报错后,前面成功的语句还是落库了——根本原因就是没显式开启事务。MySQL 默认是自动提交模式(autocommit=1),每条 DML 语句单独成事务,执行完立刻生效,ROLLBACK 对它完全无效。
必须在存储过程开头明确写 START TRANSACTION 或 BEGIN,才能把后续所有操作包裹进同一个事务上下文。否则所谓“回滚”只是空谈。
- 不支持事务的引擎(如
MyISAM)即使写了START TRANSACTION也无回滚能力,务必确认表用的是InnoDB -
CREATE PROCEDURE本身不开启事务,它只是容器;事务边界由内部 SQL 控制 - 存储过程里调用其他存储过程,不会自动继承外层事务,需手动传递或重新开启
捕获错误必须用 DECLARE ... HANDLER FOR SQLEXCEPTION
仅靠 IF @@ERROR 0 不行——这是 SQL Server 的写法,MySQL 没有 @@ERROR 全局变量。MySQL 中必须用声明式异常处理器,否则错误一发生就中断执行,根本走不到你写的 ROLLBACK 逻辑。
典型结构是:
DECLARE t_error INTEGER DEFAULT 0; DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET t_error = 1;
注意:CONTINUE 表示出错后继续执行后续语句(比如跳到 IF t_error = 1 THEN ROLLBACK; END IF;),而 EXIT 会直接退出当前 BEGIN...END 块,容易遗漏清理逻辑。
- 一个存储过程中只能声明一个
SQLEXCEPTION处理器,重复声明会报错 - 处理器作用域只覆盖其所在
BEGIN...END块,嵌套块需各自声明 - 某些警告(如数据截断)不算
SQLEXCEPTION,不会触发 handler,需额外检查ROW_COUNT()或业务逻辑
COMMIT 和 ROLLBACK 必须成对出现在 handler 之后
常见错误是:只写 ROLLBACK 却忘了 COMMIT,或者反过来——结果是事务一直挂着,连接不释放,锁不释放,后续查询被阻塞。
正确做法是在 handler 设置标记后,统一用 IF 判断并收尾:
IF t_error = 1 THEN ROLLBACK; ELSE COMMIT; END IF;
特别注意:COMMIT 或 ROLLBACK 执行后,当前事务就结束了。如果后续还有 DML,又没再 START TRANSACTION,就会进入自动提交模式,失去原子性保障。
- 不要在
COMMIT后再写ROLLBACK,MySQL 会报错ERROR 1305 (42000): SAVEPOINT does not exist类似提示 - 避免在循环体内反复
COMMIT,这等于把大事务拆成多个小事务,丧失“全有或全无”的意义 - 如果存储过程需要返回状态,建议用
OUT参数传回成功/失败,而不是依赖外部判断事务是否结束
保存点(SAVEPOINT)适合局部回滚但别滥用
当存储过程里有多个逻辑段,只想回滚其中一段(比如批量插入中某几条失败),可以用 SAVEPOINT。但它不是万能的,且容易引发误解。
例如:
SAVEPOINT sp1; INSERT INTO t VALUES (1); SAVEPOINT sp2; INSERT INTO t VALUES (2); -- 出错 ROLLBACK TO sp1;
这段代码只会撤销 INSERT INTO t VALUES (2),但 INSERT INTO t VALUES (1) 仍保留——前提是没在 sp1 之前提交过。
-
SAVEPOINT不能跨事务存在,COMMIT或ROLLBACK后所有保存点自动失效 - 嵌套存储过程中使用
SAVEPOINT要格外小心,外层 rollback 可能连带清除内层定义的点 - 高并发下大量
SAVEPOINT会增加 undo log 压力,InnoDB 的回滚段可能争用,影响性能
真正难处理的从来不是语法,而是事务边界怎么划:哪些操作必须捆在一起,哪些可以独立;错误是该立即终止还是降级处理;以及——当存储过程被另一个事务调用时,它到底该自主提交,还是交由外层统一控制。这些决策比写对 ROLLBACK 重要得多。











