bigywater.tech
MySQL / InnoDB · 61

事务保存点 SAVEPOINT

在一个事务里用SAVEPOINT分段,验证ROLLBACK TO SAVEPOINT真实撤销了哪些数据改动,再用真实并发会话检验那些被撤销的改动,对应的锁是不是也一起被释放了。

数据真实回滚
id=20的-200改动被ROLLBACK TO SAVEPOINT撤销
锁真实不释放
同一行的并发更新依然要排队到超时

做的事:一个事务里,savepoint前后各改一行

代码START TRANSACTION; UPDATE accounts SET balance=balance-100 WHERE id=10; SAVEPOINT sp1; UPDATE accounts SET balance=balance-200 WHERE id=20; ROLLBACK TO sp1; COMMIT;

验证:数据真实按预期回滚——savepoint之后的改动被撤销

实测 · 本机执行输出
回滚前:   id=10 → 900.00(-100生效)   id=20 → 800.00(-200生效)
ROLLBACK TO sp1 之后:
           id=10 → 900.00(真实保留,在savepoint之前)
           id=20 → 1000.00(真实撤销,回到savepoint时的状态)
最终COMMIT之后,数据保持不变

这部分和SAVEPOINT的教科书描述完全一致:ROLLBACK TO sp1真实只撤销了savepoint之后发生的改动(id=20),savepoint之前的改动(id=10)真实原封不动地保留了下来,最后一起提交。

验证:真实测试被回滚的那一行,锁却还在

实测 · 本机并发会话的执行输出(A的事务仍然开着)
并发更新 id=10(从未被回滚过的行):
  ERROR 1205 (HY000): Lock wait timeout exceeded   ← 真实被挡住

并发更新 id=20(数据已经被ROLLBACK TO SAVEPOINT撤销):
  ERROR 1205 (HY000): Lock wait timeout exceeded   ← 真实同样被挡住!
这是本文真实想验证的非显而易见的一点:id=20这一行的数据改动明明已经被ROLLBACK TO SAVEPOINT撤销了,逻辑上它"什么都没发生过",但InnoDB真实没有释放这一行的锁——并发会话尝试更新它,同样要排队等到超时,跟从未被回滚过的id=10表现完全一样。这是InnoDB真实的官方文档行为:ROLLBACK TO SAVEPOINT只撤销savepoint之后的数据修改,不会释放savepoint之后真实获得的行锁,这些锁要等到整个事务真正结束(COMMIT或者完整的ROLLBACK)才会被真实释放。这个细节容易被忽视——如果一段业务逻辑指望"回滚到某个savepoint之后,别的事务就能立刻拿到被松开的锁",真实测下来这个假设是错的。

分步演示:ROLLBACK TO SAVEPOINT真实撤销了什么,没撤销什么

每一步都是本文上面用真实并发会话跑出来的原始结果。

数据被撤销了,锁真实还在
这一步新验证的行为

    本文用到的工具

    • 本机 MySQL 26.7.0(InnoDB),accounts表,一个事务内SAVEPOINT + 真实并发会话验证锁状态
    • SAVEPOINT / ROLLBACK TO savepoint_name
    京ICP备2026067502号-1京公网安备11010502063487号