面试官问我“事务隔离级别”,结果我用买可乐的故事拿下了Offer

77 阅读6分钟



引言

大家好,我是小米,31岁,爱折腾代码也爱喝咖啡。今天给大家讲一个我社招面试时的“惊魂瞬间”——一个看似简单的Java后端面试题,却差点让我当场“哑火”。

那是去年秋天的一个下午,天气晴到暴晒。我穿着一件白衬衫去面试一家互联网金融公司,想着八成又是常规的Java八股文,最多来点Spring、JVM调优这种“老朋友”话题。没想到,面试官盯着我笑了一下,说:“小米,咱来聊聊事务吧。你先给我说说,事务的隔离级别有哪些?MySQL的默认隔离级别是什么?”

好家伙,这问题看似平平无奇,但要真讲清楚,可就不是两句话能糊弄过去的事。

先来个“事务”的小故事

我说,面试官,这个问题我得先铺垫一下。

假设你和我去超市买饮料,看到货架上最后两瓶可乐。你先把一瓶放进购物车,这时我也拿走另一瓶,然后我们都去结账。这个场景里,如果用数据库来模拟,就相当于我们同时对同一份库存数据做操作。

如果没有事务隔离,可能发生的事有:

  • 你买单时发现库存为0,生气地瞪了我一眼(脏读
  • 你刚看到库存还有1瓶,下一秒我买走了,结账时却告诉你没货了(不可重复读
  • 你明明刚查到有库存1瓶,但还没下单,库存居然变成2了(幻读

于是,数据库为了防止我们在超市“打架”,就搞出了事务隔离机制,来保证数据的安全性和一致性。

事务的四个隔离级别

我伸出四根手指对面试官说:标准SQL定义了四个隔离级别,像爬楼梯一样从低到高,每级多防御一点问题,但并发性能会差一点。

1、Read Uncommitted(读未提交)

  • 你能看到别人没提交的事务改动。
  • 可能会发生脏读
  • 场景:库存刚减1,我还没提交,你查到的库存已经变了,但我回滚后,数据又恢复了。

2、Read Committed(读已提交)

  • 只能看到别人已经提交的事务改动。
  • 避免了脏读,但可能发生不可重复读
  • 场景:你两次查询同一条库存记录,第一次查库存是1,第二次查却是0,因为中间有人提交了事务。

3、Repeatable Read(可重复读)

  • 同一事务内,多次读取同一行数据,结果是一样的。
  • 避免了脏读、不可重复读,但可能发生幻读
  • 场景:你查库存表有10条记录,下单时再查,记录变成了11条,因为有人插入了一条新数据。

4、Serializable(可串行化)

  • 最高隔离级别,所有事务串行执行,像一个个排队买单。
  • 避免了脏读、不可重复读和幻读,但并发性能最差。

我停顿了一下,看了看面试官的表情,发现他点了点头,但明显想听更多细节。

MySQL的“特别”之处

我说,这就得聊到MySQL的“个性”了。

MySQL默认的事务隔离级别是 Repeatable Read(可重复读) ,但它用了一套叫 MVCC(多版本并发控制) 的机制,把幻读也一并给解决了(大多数情况)。

面试官抬了下眉毛:“你给我讲讲MVCC是怎么干掉幻读的?”

我笑了笑,心里想,这下该秀一下了。

MVCC——多版本并发控制的“魔法”

MVCC就像是给每条记录拍了一张历史快照,并且不同事务看到的是不同版本的数据。

  • 当你开始事务时,你会拿到一个“时间戳”(事务ID)。
  • 查询数据时,你只会看到在这个时间戳之前已经提交的版本。
  • 别人新增的数据,如果事务ID比你的大,你就看不到。

这样,哪怕别人插入了新记录,你在自己的事务视角里,这些新数据是隐形的,所以幻读问题就消失了。

当然,这并不是万能的,如果你用SELECT ... FOR UPDATE去加锁,MySQL还是会触发锁机制,这时候就进入了另一套并发控制逻辑。

面试官的“追问地狱”

我刚讲到这,面试官又补刀:“那既然MySQL默认是可重复读,还能改成读已提交吗?业务中你会改吗?”

我说,能改,用:

但是要不要改,得看业务场景:

  • 如果你是金融转账、库存扣减这种强一致性场景,可重复读是更安全的选择。
  • 如果你是高并发订单查询、不太怕幻读的业务,读已提交性能更好。

我还顺带吐槽了一句:很多开发为了图快,甚至在Java里直接开READ UNCOMMITTED,结果测试环境跑得飞快,线上数据却乱成一锅粥。

Java与事务隔离级别的配合

聊到这里,我切回Java的视角:“在Spring事务管理中,隔离级别是可以通过@Transactional注解直接指定的,比如isolation = Isolation.REPEATABLE_READ,这样就能和MySQL的默认级别保持一致。”

不过要注意,如果数据库和Java配置的隔离级别不一致,以数据库为准。别在代码里写了READ_COMMITTED,结果数据库还是REPEATABLE_READ,最后查出来的结果和你预期不一样。

我给的“面试加分小结”

我总结道:

  • 四个隔离级别——记清楚每级能防什么问题。
  • MySQL默认是可重复读,但用MVCC解决了大多数幻读。
  • 业务选型要平衡性能与一致性,别盲目追高隔离级别。
  • Java配置要和数据库保持一致,避免踩坑。

面试官听完,露出了一个“还不错”的笑容。我心里松了口气——果然,事务隔离这个问题,不仅考你记忆,还考你对底层机制的理解。

后来我在工作中遇到的“真实坑”

上岗后没多久,我们团队就遇到一个“幽灵库存”问题:库存扣减事务隔离级别设置成了读已提交,结果高并发下多个线程同时读到库存还有1,然后都去扣,结果库存变成负数。

最后,我们把隔离级别切回了可重复读,并配合FOR UPDATE加锁,才解决了这个问题。

这件事让我彻底明白:事务隔离级别,不是教科书上的摆设,它直接关系到业务的正确性和线上事故的频率。

建议

如果你也在准备Java社招面试,我的建议是:

  • 不要只背“默认是可重复读”这种答案,要能解释为什么。
  • 最好能讲出MVCC的作用,甚至加上锁机制的补充,这会让面试官觉得你理解很深。
  • 多用生活化的例子,比如超市买可乐、银行转账,让面试官觉得你思路清晰,表达生动。

END

写到这,我又喝了一口咖啡,回想起那场面试,心里暗暗庆幸——幸好当时我不仅记住了答案,还能用故事讲出来。要不然,那份Offer可能就和那瓶可乐一样,被别人拿走了。

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