MySQL 中的 SA VEPOINT 必须在显式事务环境下使用,若 autocommit=1 则不会生效;同时,ROLLBACK TO SA VEPOINT 不会释放锁,同名保存点会被静默覆盖,而 DDL 语句触发的隐式提交也会让保存点立即失效。

SA VEPOINT 必须在显式事务里才有效
当 autocommit=1 时,执行 SA VEPOINT sp1 看起来像成功了,返回结果也可能是 Query OK;但如果继续执行 ROLLBACK TO SA VEPOINT sp1,通常就会报错:ERROR 1305 (42000): SA VEPOINT sp1 does not exist。根本原因并不是语法错误,而是当前事务实际上并没有真正开启。
要确认当前会话是否处于事务中,可执行 SELECT @@autocommit, @@in_transaction;,只有结果为 1 和 1 时,才说明当前已进入有效事务。仅执行 SET autocommit = 0 还不够,通常还需要配合 BEGIN 或 START TRANSACTION 才能正确使用 MySQL 保存点。
- 在连接池场景中尤其容易出问题:如果上一个请求没有执行
COMMIT或ROLLBACK,当前请求再发起SA VEPOINT,就可能因事务状态异常而失败 - ORM(例如 SQLAlchemy)默认未必直接暴露
SA VEPOINT能力,使用 PyMySQL 等原生驱动时,也必须确保所有 SQL 操作都基于同一个Connection实例
ROLLBACK TO SA VEPOINT 不释放锁
这是 MySQL 保存点在复杂业务里最容易被误解的地方之一:回滚到保存点之后,表面上数据似乎已经恢复,但其他事务仍然可能继续被阻塞。原因在于 InnoDB 在执行回滚到保存点时,并不会释放该保存点之后操作所持有的锁。
INSERT INTO t VALUES (1) + SA VEPOINT sp + ROLLBACK TO SA VEPOINT sp → 虽然该行数据已被撤销,但对应的插入意向锁(IX)以及隐式锁依旧存在,直到整个事务执行 COMMIT 或完整 ROLLBACK 为止。
SELECT ... FOR UPDATE之后再设置保存点并执行回滚,相关锁状态同样不会发生变化- 验证锁是否还存在,不能只看数据是否恢复,还应结合
information_schema.INNODB_TRX与INNODB_LOCK_WAITS进行排查 - 不要试图依赖
ROLLBACK TO SA VEPOINT处理并发冲突——它只能回退数据变更,无法回退锁占用
同名 SA VEPOINT 静默覆盖,命名必须带上下文
如果连续执行两次 SA VEPOINT loop_step,第二次会直接覆盖第一次,不会报错,也不会给出警告。因此,ROLLBACK TO SA VEPOINT loop_step 最终只会回到最近一次设置保存点的位置,而不是你原本以为的某次循环起点。
保存点名称超过 64 个字符时会被截断,并且大小写不敏感(SP1 与 sp1 会被视为同一个保存点)。更稳妥的做法,是使用“短前缀 + 业务标识 + 序号”的命名方式,例如 sp_ord_123、sp_batch_007,这样更适合复杂事务管理和排查问题。
- 在存储过程或循环逻辑中直接写死固定名称,极易造成回滚断点错位,影响业务逻辑正确性
RELEASE SA VEPOINT sp_name是唯一的显式清理手段;即使不执行释放也不会报错,但后续使用同名保存点时仍会被覆盖- DDL(如
ALTER TABLE)会触发隐式COMMIT,使当前事务中的所有保存点立即失效——这属于 MySQL 的既定设计,不是异常行为
SA VEPOINT 不是子事务,只是 undo log 偏移标记
从实现机制来看,SA VEPOINT 本质上只是 InnoDB 在事务结构中记录的一个 undo log 偏移位置,因此开销很小,但能力也相对有限:它不能按需撤销某一条独立语句,只能统一回退该保存点之后发生的所有变更,包括 DML 以及部分可能受事务影响的操作。
很多开发者容易误以为可以单独“撤销某一条 UPDATE”,实际上并不能做到——如果在这条语句之前没有提前设置 SA VEPOINT,那么回滚时就只能连同后续操作一起撤回。
- 触发器内部产生的修改,无法直接通过外层
ROLLBACK TO SA VEPOINT回退(除非触发器内部自己设置保存点并处理回滚) - 在 XA 事务模式下,
SA VEPOINT不可使用,这一点在分布式事务场景中尤其要注意 - 事务提交或整体回滚后,所有保存点都会自动清除,
RELEASE的意义更多在于表达清晰的事务意图,而不是强制要求
