mysql不支持真正事务嵌套,start transaction会隐式提交前一事务;唯一局部回滚方案是savepoint,它仅在当前事务内设标记点,配合rollback to savepoint使用,且需确保autocommit=0。

存储过程里用 BEGIN … END 包着的事务,不能真正嵌套
MySQL 不支持真正的事务嵌套。你在存储过程中写多个 BEGIN TRANSACTION 或反复 START TRANSACTION,后一次会隐式提交前一次——不是挂起,是直接提交掉。这意味着所谓“内层事务回滚不影响外层”,根本不存在。
常见错误现象:ERROR 1305 (42000): SAVEPOINT does not exist 或回滚后发现外层数据也丢了,往往是因为误以为 START TRANSACTION 能压栈。
- MySQL 的事务模型是扁平的:同一连接只允许一个活跃事务
- 存储过程里调用另一个含
START TRANSACTION的过程,原事务立即结束 - 想局部回滚?只能靠
SAVEPOINT,它不开启新事务,只是当前事务里的标记点
SAVEPOINT 是唯一靠谱的“伪嵌套”手段
SAVEPOINT 不是事务,也不改变事务状态,它只是在当前事务里打个桩。后续可以用 ROLLBACK TO SAVEPOINT 回退到那一点,而不会影响该点之前的操作。
使用场景:存储过程处理多步逻辑(比如扣库存 → 记日志 → 更新积分),其中某一步失败,只想撤回这一步及之后的改动,前面的还得留着。
- 定义:
SAVEPOINT sp1;(名字任意,但同事务内不能重复) - 回滚:
ROLLBACK TO SAVEPOINT sp1; - 释放(可选):
RELEASE SAVEPOINT sp1;,避免占用内存,但不释放也不报错 - 注意:
COMMIT或整个事务ROLLBACK会自动清除所有SAVEPOINT
在存储过程中用 SAVEPOINT 的典型写法
别把 START TRANSACTION 塞进存储过程体;通常由调用方控制事务边界,过程内部只负责用 SAVEPOINT 切分逻辑段。
DELIMITER $$
CREATE PROCEDURE transfer_funds(IN from_id INT, IN to_id INT, IN amount DECIMAL(10,2))
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK TO SAVEPOINT transfer_step;
RESIGNAL;
END;
<p>START TRANSACTION;
UPDATE accounts SET balance = balance - amount WHERE id = from_id;
SAVEPOINT transfer_step; -- 标记转账主逻辑起点
UPDATE accounts SET balance = balance + amount WHERE id = to_id;
INSERT INTO transfers (from_id, to_id, amount) VALUES (from_id, to_id, amount);
COMMIT;
END$$
DELIMITER ;</p>
关键点:
-
SAVEPOINT必须在可能出错的操作 之前 定义 - 异常处理器里用
ROLLBACK TO SAVEPOINT,不是ROLLBACK全事务 - 如果过程被外部事务调用,
START TRANSACTION应移出过程,否则会强行提交外层事务
容易踩的坑:AUTOCOMMIT、存储过程参数和错误传播
即使用了 SAVEPOINT,也会因为环境配置或错误处理不当导致失效。
-
AUTOCOMMIT=1时,每个语句都是独立事务,SAVEPOINT创建后立刻被隐式提交 —— 务必确认连接处于AUTOCOMMIT=0状态 - 存储过程参数如果是
INOUT或OUT,别指望回滚SAVEPOINT能还原它们的值;变量状态不参与事务控制 -
RESIGNAL比LEAVE更安全:前者把原始错误抛给上层,后者可能吞掉错误码,让调用方误判成功 - 嵌套调用多个带
SAVEPOINT的过程时,SAVEPOINT名字必须全局唯一,否则第二个SAVEPOINT sp1会覆盖第一个
最常被忽略的是:SAVEPOINT 只对 DML 生效,对 DDL(如 CREATE TABLE)会强制提交当前事务,哪怕你刚打了桩。











