当Transactional碰到锁,有个大坑,要小心。 (7)

org.springframework.transaction.support.AbstractPlatformTransactionManager#commit

当Transactional碰到锁,有个大坑,要小心。

在 commit 之前还有两个判断,如果事务被标记为 rollback-only 了,还是得回滚。

而且,你看日志。

我这事务还没提交呢,锁就被释放了?

当Transactional碰到锁,有个大坑,要小心。

接着往下看 commit 相关的逻辑,我们就会遇到老朋友:

当Transactional碰到锁,有个大坑,要小心。

HikariCP,SpringBoot 2.0 之后的默认连接池,强得一比,在之前的文章里面介绍过。

关于事务的提交,就不大篇幅的介绍了。

给大家指个路:

com.mysql.cj.protocol.a.NativeProtocol#sendQueryString

在这个方法的入口处打上断点:

当Transactional碰到锁,有个大坑,要小心。

然后你会发现很多的 SQL 都会经过这个地方。

所以,为了你顺利调试,你需要在断点上设置一下:

当Transactional碰到锁,有个大坑,要小心。

这样只有 SQL 语句是 commit 的时候才会停下来。

又一个调试小细节,送给你,不客气。

现在,我们知道原因了,那我现在把代码稍微变一下:

当Transactional碰到锁,有个大坑,要小心。

把 ReentrantLock 换成了 synchronized。

那你说这个代码还会不会有问题?

当Transactional碰到锁,有个大坑,要小心。

说没有问题的同学请好好反思一下。

这个地方的原理和前面讲的东西是一模一样的呀,肯定也是有问题的。

这个加锁方式就是错误的。

所以你记住了,以后面试官问你 @Transactional 的时候,你把标准答案先背一遍之后,如果你对锁这块的知识点非常的熟悉,就可以在不经意间说一下结合锁用的时候的异常场景。

别说你写的,就说你 review 代码的时候发现的,深藏功与名。

另外记得扩展一下,现在都是集群服务了,加锁得上分布式锁。

但是原理还这个原理。

既然都聊到分布式锁了,这和面试官又得大战几个回合。

是你主动提起的,把面试官引到了你的主战场,拿几分,不过分吧。

一个面试小技巧,送给你,不客气。

当Transactional碰到锁,有个大坑,要小心。

解决方案

现在我们知道问题的原因了。

解决方案其实都呼之欲出了嘛。

正确的使用锁,把整个事务放在锁的工作范围之内:

当Transactional碰到锁,有个大坑,要小心。

这样,就可以保证事务的提交一定是在 unlock 之前了。

对不对?

当Transactional碰到锁,有个大坑,要小心。

说对的同学,今天就先到这里,请回去等通知啊。

别被带到沟里去了呀,朋友。

你仔细想想这个事务会生效吗?

提示到这里还没想明白的同学,赶紧去搜一下事务失效的几种场景。

我这里说一个能正常使用的场景:

当Transactional碰到锁,有个大坑,要小心。

只是这种自己注入自己的方式,我觉得很恶心。

如果项目里面出现了这样的代码,一定是代码分层没有做好,项目结构极其混乱。

不推荐。

还可以使用编程式事务的方式去写,自己去控制事务的开启、提交、回滚。

比直接使用 @Transactional 靠谱。

除此之外,还有一个骚一点的解决方案。

其他地方都不动,就只改一下 @Transactional 这个地方:

当Transactional碰到锁,有个大坑,要小心。

把隔离级别串行化,再次跑测试用例,绝对不会出现超卖的情况。

甚至都不需要加锁的逻辑。

你觉得好吗?

当Transactional碰到锁,有个大坑,要小心。

好啥啊?

串行化性能跟不上啊!

这玩意太悲观了,对于同一行的数据,读和写的时候都会进行加锁操作。当读写锁出现冲突的时候,后面来的事务就排队等着。

这个骚操作,知道就行了,别用。

你就当是一个没啥卵用的知识点就行了。

但是,如果你们是一个不追求性能的场景,这个没有卵用的知识点就变成骚操作了。

rollback-only

前面提到了这个 rollback-only,为了更好的行文,所以我一句话就带过了,其实它也是很有故事的,单独拿一节出来简单说一下,给大家模拟一下这个场景。

以后你见到这个异常就会感觉很亲切。

Spring 的事务传播级别默认是 REQUIRED,含义是如果当前没有事务,就新建一个事务,如果上下文中已经有一个事务,则共享这个事务。

直接上代码:

当Transactional碰到锁,有个大坑,要小心。

这里有 sellProduct、sellProductBiz 两个事务,sellProductBiz 是内层事务,它会抛出了异常。

当执行整个逻辑的时候,会抛出这个异常:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzdfsx.html