Spring事务背后的真相:我们被骗了这么多年?

11 阅读6分钟



故事的开头:面试官的“老梗”

上个月我去面了一家还不错的互联网公司,坐标杭州。项目上刚好不那么忙,我心想,去试试水,也算是看看市场行情。

面试开始五分钟,面试官就抛出了这道题:

“你讲讲Spring事务是怎么实现的?”

我当时一听,心里偷笑:这题我熟,春招秋招社招必考题,面试圈的“Hello World”。

于是我立刻答道:

“Spring事务通过AOP实现的,在方法上加上 @Transactional 注解,Spring会用代理拦截方法调用,创建事务、提交事务、回滚事务。”

面试官听着点头,但脸上没有一丝波澜。他继续追问:

“那你知道数据库层的事务是怎么实现的吗?Spring怎么协调的?”

我一愣。数据库层?不是我说完 AOP 和代理就够了吗?

我顿了几秒,开始强行接住:

“数据库本身有事务支持,Spring只是通过事务管理器调用JDBC接口…”

话没说完,面试官突然说了一句话:

“其实你刚刚说的那些都只是表象,真正的事务发生在数据库内部,是靠 redo log 和 binlog 这样的机制实现的,Spring 只是配合你演了一出戏。

那一刻,我突然像是被人点醒了武林盲点。

你以为你理解了Spring事务?其实你只是看懂了表象

我们很多人,尤其是工作 1~3 年的小伙伴,可能都能说出下面这些话:

  • Spring 事务使用 @Transactional 实现。
  • Spring 提供 PlatformTransactionManager 管理事务。
  • Spring 使用 AOP 或 CGLIB 动态代理来包裹方法,实现前置开启事务、后置提交或回滚。

没错,这都对。

但是,这只是“Spring层面的事务包装”,而不是事务的本质

让我们打个比方:

假如Spring是剧组,@Transactional 是舞台

我们写下:

就像我们写下一个剧本:

  • 主角转账
  • 扣钱 -> 加钱

Spring 会出动一个“导演”TransactionManager,它会:

  • 在方法执行前: 调用 JDBC 的 connection.setAutoCommit(false),开启事务
  • 在方法执行后: 如果没异常,connection.commit();否则,connection.rollback()

你看,这就是我们眼里的“事务”。但你有没有想过:

Spring这么做,能保证你的数据真的一致吗?

我们都知道,事务的4大特性:ACID

这其中的原子性持久性,不是靠Spring实现的,而是数据库底层通过 redo logbinlog 硬刚出来的。

数据库才是真正的“事务执行者”

让我们来看看数据库是怎么处理事务的。

我们以 MySQL 的 InnoDB 存储引擎为例。

1、redo log:持久化的保障者

当你开启一个事务后,在执行 update、insert、delete 等操作时,数据库其实并没有立即改动磁盘上的数据页

它只是:

  • 先在内存(buffer pool)里改数据
  • 再写入一份 redo log(重做日志)

只有等你执行 commit 时:

  • 数据库会将 redo log 标记为已提交
  • 后台线程慢慢把脏页刷新到磁盘

也就是说,redo log 才是事务是否成功的关键。

哪怕宕机了,只要 redo log 在,数据库就能“重做”你的操作。

2、undo log:回滚的守护者

而在执行每一个数据修改时,数据库会记录对应的undo log,也就是“怎么把这一步撤销”。

如果事务执行中途抛出异常,Spring调用了rollback(),数据库就通过 undo log 让数据恢复原样。

3、binlog:MySQL 的官方记账本

除了 redo log 和 undo log,还有一个经常被提到的 binlog(归档日志),它是 MySQL Server 层记录的日志,用于主从同步、数据恢复等。

但你要记住一件事:

  • redo log 是 InnoDB 引擎层的
  • binlog 是 MySQL Server 层的

二者都写成功,事务才算真正“提交成功”。

Spring事务只是“导演”,不是演员

那我们回过头来说说,Spring 到底干了什么?

答案很简单:

Spring 是一个“事务协调器”。

它通过声明式或编程式事务管理,告诉数据库:

“老哥,接下来我要演一场戏,你看着点,该加锁就加锁,该提交就提交。”

Spring事务的实现流程:

  • 拦截方法调用(AOP)
  • 获取当前数据源连接
  • 调用 connection.setAutoCommit(false) 开启事务
  • 调用目标方法
  • 若无异常,执行 commit();否则 rollback()
  • 清理资源,释放连接

这一切,背后的关键类是:

  • TransactionInterceptor(事务拦截器)
  • DataSourceTransactionManager(事务管理器)
  • TransactionSynchronizationManager(事务同步器)

而这些类本身,并不负责数据一致性

它们只是告诉数据库该干什么,真正的数据安全,是数据库层做的。

为啥说“没有数据库事务支持,Spring啥也干不了”?

假设你用的是一个不支持事务的数据库(比如一些 NoSQL 数据库),你在方法上加再多的 @Transactional 都没用。

因为:

  • Spring 调用 setAutoCommit(false) 报错
  • 调用 rollback() 没效果
  • 数据库根本没有 redo log,无法回滚!

所以说到底:

Spring 是事务的“外壳”,数据库才是事务的“核心”。

我们不能把“事务的一切”都归功于Spring。

面试官说的话,值得反复琢磨

回到我的面试现场,当我听完面试官的一句话之后,感觉就像是被打通了任督二脉。

他说:

“真正能保障事务一致性的,是数据库引擎内部对日志的使用。而 Spring,只是帮你把这些逻辑用优雅的方式封装了一下。”

听懂这句话,才算是“真正理解了Spring事务”。

总结一下,真正能打的知识点来了!

如果你也在准备 Java 后端社招,这些点一定要掌握:

1. Spring事务的三种方式:

  • 编程式事务(繁琐,灵活)
  • 声明式事务(常用,基于注解)
  • XML配置事务(古早风格)

2. 核心类和接口:

  • PlatformTransactionManager
  • DataSourceTransactionManager
  • TransactionDefinition
  • TransactionStatus
  • TransactionInterceptor

3. 数据库事务的实现:

  • redo log:持久化保证
  • undo log:回滚保证
  • binlog:主从/恢复记录

4. 常见面试深入问题:

  • @Transactional 为啥加在 private 方法上不生效?
  • 多线程中事务怎么保证一致性?
  • Spring事务传播机制有哪几种?
  • Spring事务和数据库隔离级别如何协同?

写在最后:别再被“表象”骗了

现在回头看,很多技术我们以为懂了,其实只是“听懂了名词,没理解机制”。

就像 Spring 事务。

它的本质,其实就是你和数据库的一场“双人舞”。

Spring负责指挥,数据库负责执行。没有 redo log、undo log、binlog 的支持,Spring只是“空喊口号”。

END

希望这篇文章,能帮你真正理解事务背后的那些机制,也希望你下次面试能“打到核心”。

如果你喜欢这类“故事+技术”的风格,点个在看或者转发给你的技术伙伴吧,我们一起成长!

我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!