如果仔细看看上面执行流程就会发现步骤2会带来两次连续的回调,这个连续回调也引发了本文的问题。
在测试兑换失败场景时我这边要把扣的积分返还给用户,操作伪代码如下:
ServiceImpl:
@Transactional
public void dealOrderExchangeNotice(....){
RedisLock lock = null;
try{
lock=new RedisLock(bizId);
if (lock.lock()) {
//查询订单
IntegralShoppingOrder shoppingOrder = selectOne(bizId);
//shoppingOrder.getStatus()==1 代表订单扣积分成功 可以返还积分
if (shoppingOrder != null && shoppingOrder.getStatus() == 1) {
//返还积分
//更新订单状态为 4(订单失败)
}
}catch (Exception e) {
}finally {
if (lock != null) {
lock.unlock();
}
}
}
如果没有出现问题看着上面的代码感觉没有啥问题的…
测试时发现每次都是给用户返还了两次积分(相当于花100送200了,这哪了得…),刚开始看上面的代码看了好久没有发现问题,加上log后查询服务器日志发现失败订单几乎在同一时间会收到两条回调信息,
(勉强算作一个高并发吧),两个请求都拿到了锁且shoppingOrder的getStatus()都是一样的,感觉到问题了出现重复读了…
解决过程
两个请求都拿到了锁证明第一个回调请求已经执行完毕了,按道理应该将订单状态更新成4了第二个请求查询到的也应该是4,但是还是出现同样的值说明第二个请求查询时第一个没有提交事务。
这样明确出两个排查方向 重复读(mysql MVCC原理)、事务提交(spring 事务机制)。
mysql MVCC原理
mysql默认事务隔离级别是 RR(Repeatable Read,可重复读),事务A在读到一条数据之后,此时事务B对该数据进行了修改并提交,那么事务A再读该数据,读到的还是原来的内容。
MVCC的实现,是通过保存数据在某个时间点的快照来实现的。也就是说,不管需要执行多长时间,每个事务看到的数据是一致的。根据事务开始的时间不同,每个事物对同一张表,同一时刻看到的数据可能是不一样的。
由此可以确定第二个请求执行查询时第一个请求事务没有提交,两者的事务版本号是一样的所以查询的值是一样的,因此问题不在数据库了!
小知识:
第一个SELECT执行的时候,当前事务取到了系统版本号n(并不是begin的时候就生成版本号,而是执行事务内第一个语句时生成),系统版本号自增为n+1。此后,其他事务的更新操作能取到的系统版本号最小为n+1,所以当前事务再次SELECT将看不见它们的更新。
spring 事务机制
Spring 事务管理分为编程式和声明式两种。编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体的逻辑与事务处理解耦。
声明式事务管理使业务代码逻辑不受污染,因此实际使用中声明式事务用的比较多。
小知识:
1、默认配置下 Spring 只会回滚运行时、未检查异常(继承自 RuntimeException 的异常)或者 Error。
2、@Transactional 注解只能应用到 public 方法才有效。
很明显我这边也是采用声明式事务,Aop自动提交事务是在dealOrderExchangeNotice代码块中的方法执行完毕后才执行事务提交工作
ps:在群里面讨论时有一个群友说事务提交是在finally执行之前,这个观点是错误的
因为这个还在一个群里面被人喷了讨论的话题老旧
我画了一个执行图很清晰的说明了问题所在(不懂千万不要空想动手画一画可能马上明白了)
最后把上面的加锁代码转到controller层后重试没有出现多返积分的问题了
Controller:
public void dealOrderExchangeNotice(....){
RedisLock lock = null;
try{
lock=new RedisLock(bizId);
if (lock.lock()) {
S.dealOrderExchangeNotice(....);
}finally {
if (lock != null) {
lock.unlock();
}
}
}
ServiceImpl:
@Transactional
public void dealOrderExchangeNotice(....){
lock = null;
# 最后
我想问下大家当初选择做程序员的初衷是什么?有思考过这个问题吗?高薪?热爱?
既然入了这行就应该知道,这个行业是靠本事吃饭的,你想要拿高薪没有问题,请好好磨练自己的技术,不要抱怨。有的人通过培训可以让自己成长,有些人可以通过自律强大的自学能力成长,如果你两者都不占,还怎么拿高薪?
架构师是很多程序员的职业目标,一个好的架构师是不愁所谓的35岁高龄门槛的,到了那个时候,照样大把的企业挖他。为什么很多人想进阿里巴巴,无非不是福利待遇好以及优质的人脉资源,这对个人职业发展是有非常大帮助的。
如果你也想成为一名好的架构师,那或许这份**[Java核心架构笔记](https://gitee.com/vip204888/java-p7)**你需要阅读阅读,希望能够对你的职业发展有所帮助。
**中高级开发必知必会:**
名好的架构师,那或许这份**[Java核心架构笔记](https://gitee.com/vip204888/java-p7)**你需要阅读阅读,希望能够对你的职业发展有所帮助。
**中高级开发必知必会:**
![](https://img-blog.csdnimg.cn/img_convert/039a1385e628ae04216c9d1df73d1863.png)