ROLLBACK TO SAVEPOINT 不释放行锁是 InnoDB 的设计行为,因其锁生命周期绑定整个事务而非保存点,回滚仅撤销数据变更而不释放已持有的行锁、间隙锁或临键锁。

ROLLBACK TO SAVEPOINT 不释放行锁是 InnoDB 的设计行为
MySQL 的 SAVEPOINT 本质不是事务分段,而是 undo log 中的一个标记位置。执行 ROLLBACK TO SAVEPOINT sp1 时,InnoDB 只回滚该点之后的 DML 数据变更(比如撤销 INSERT、UPDATE),但**不会释放这些操作已获取的行锁、间隙锁或临键锁**。这是因为锁的生命周期绑定在整个事务上,而非保存点。
常见错误现象:ROLLBACK TO SAVEPOINT 执行成功后,另一事务尝试 UPDATE 同一行却卡住——你查 information_schema.INNODB_TRX 会发现第一个事务仍处于 TRX_STATE = 'LOCK WAIT' 或 'RUNNING' 状态,且锁未释放。
- INSERT 后回滚到保存点:新插入行的排他锁(X lock)仍持有,其他事务无法更新/删除该主键值
- SELECT ... FOR UPDATE 后回滚:锁依然生效,哪怕 SELECT 的结果集已“被撤销”
- 长事务中反复设点 + 回滚:锁持续累积,极易触发
ERROR 1205 (HY000): Deadlock found when trying to get lock
为什么 autocommit=1 下 SAVEPOINT 完全无效
Savepoint 必须在显式事务中使用。MySQL 默认 autocommit=1,此时每条语句都是独立事务,SAVEPOINT sp1 语句本身会执行成功(返回 Query OK),但后续 ROLLBACK TO SAVEPOINT sp1 一定报错:SAVEPOINT sp1 does not exist。
验证方式:SELECT @@autocommit; 必须为 0;且必须先执行 BEGIN 或 START TRANSACTION(仅 SET autocommit = 0 不够)。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 连接池复用场景下,若上个请求没
COMMIT或ROLLBACK,当前请求一执行SAVEPOINT就可能失败:事务已非活跃状态 - DDL 操作(如
ALTER TABLE)会隐式触发COMMIT,导致之前所有SAVEPOINT立即失效
同名 SAVEPOINT 覆盖无声,锁状态更难追踪
执行两次 SAVEPOINT sp1,第二次会静默覆盖第一次——不报错、无日志、不警告。但旧 sp1 对应的 undo 位置和 binlog offset 已丢失,再 ROLLBACK TO SAVEPOINT sp1 只能回到第二次设点的位置。
这意味着:你以为回滚到了“插入用户前”的状态,其实只是回到了“插入用户头像后”的状态;而头像那条 INSERT 持有的行锁,依然卡着别人。
- 命名建议带上下文,如
sp_after_user_insert、sp_before_payment_update,避免复用 -
RELEASE SAVEPOINT sp1是唯一显式清理方式,虽事务结束时自动清除,但中间主动释放能减少锁残留误判 - 回滚后若继续执行新 DML,这些操作将覆盖原保存点之后的状态,不可再回到更早保存点
真正要解决锁残留,得靠 COMMIT 或全量 ROLLBACK
只要事务没结束,所有锁就一直挂着。哪怕你 ROLLBACK TO SAVEPOINT 十次,只要没 COMMIT 或 ROLLBACK,锁就还在。这不是 bug,是 InnoDB 保证事务原子性和隔离性的基础机制。
高并发或逻辑复杂场景下,别依赖 SAVEPOINT 模拟嵌套事务。它解决不了锁竞争问题,反而容易掩盖真实瓶颈。
- 优先拆成多个短事务,每个事务职责单一、持有锁时间短
- 存储过程里设保存点,只用于逻辑分段与错误通知,事务边界仍应由应用层控制
- 排查锁残留,直接查
performance_schema.data_locks和INNODB_TRX,别信客户端返回信息

















