引言
大家好,我是小米,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岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!