mysql事务注解_事务注解(@Transactional)引起的数据覆盖故障

最近组织团队内技术培训,刘聪为分享的一个跟事务和写数据库相关的case(bug)很有代表性。用事务,要小心!

一、故障现象

车辆交付履约流程上两个节点(工程项目)A和B, A修改一条数据记录item(工单),然后发消息给B,B也会对item进行修改。

故障现象,有时候(不是必现)感觉A没有成功修改item这条数据,而日志显示A修改成功了数据item!

看一下具体代码实现。下图是工程A代码,3个红框依次动作。

1、开启事务

2、修改工单记录item

3、向下游节点发送mq消息

e6e0e14bb06fef46dc4278578f23bbc1.png

下图是下游消费mq消息的节点B,红框表示采用JPA技术修改数据记录item

692e3be05a0f2087b936e52c219dd89c.png

二、原因分析

这个过程总共经历5个步骤,见下图

a7647eebc8dc59ed96c3600de7599b7c.png

1、节点A开启一个事务,修改数据表中某条数据item

2、A向B发送mq消息,再做些其他事情,提交事务

3、节点B,消费mq消息

4、节点B读出数据item

5、节点B在内存中修改数据item某些字段,写回数据库

注意到第1、2步骤是在一个事务中。存在一种可能,B节点收到mq消息,执行第4步骤,读取item数据后,步骤1、2的事务才完成提交。由于数据库事务隔离级别,这种情况下,第4步骤读到的数据并不是A节点在第1步写的,已经读到脏数据了。当第5步写回数据的时候,就可能造成老数据覆盖A写的新数据。

这里有两个细分场景

1、第1步、第5步修改同一个字段。这种情况,第4步骤读到脏数据

c4988f801347a4049569043f7f20f9fd.png

2、第1步、第5步修改不同字段。第4步读到col2字段的oldvalue,第5步目的是修改col3的值,但是采用jpa或者mybatis的一些默认写法,会把col2的oldvalue更新回数据库。

一般的ORMapping框架利用一个vo对象写数据库记录,没有修改的字段不会更新(代码里并没有改col2的值),但是第4步读取数据后,第1步对数据item进行了修改。这样默认的写库方法,会check记录的变化,然后把col2字段的值更新。这样就出现了旧值覆盖新值的问题。

7c473c68342aadb739c479ddb4373adb.png

三、解决办法

1、考虑到实施成本,如果修改不同的字段,不存在竞争关系。只需要在第5步写库的环节指定更新字段就能快速解决这个问题。事实上,生产环境下也是选择的这个方案临时修复。

2、解决办法1显然不够优秀。更好的做法,把第2步发mq消息从事务中拆出来,等第1步操作commit后在发mq消息。这个办法涉及到一些逻辑的梳理(业务代码里会有不少的if……else),代码的改动。这样处理仍然不够完美,第1步执行完了,第2步失败了怎么办?在这里可能需要一些额外的代码工作保证第2步执行成功。

3、如果业务压力不大,也可以考虑从数据库的事务隔离级别方面入手来解决这个问题。

4、业务上,第1步到第5步如果需要强一致,了解一下分布式事务

https://www.jianshu.com/p/16b1baf015e8

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值