记一次线上问题 → 事务去哪了

  • 时间:
  • 浏览:0
  • 来源:极速快3_快3讨论群_极速快3讨论群

开心一刻

  小羊:哎呀,前面有奶喝

  狗妈:这谁呀,走开

  小羊:让你喝点,能缘何的嘛

  狗妈:你喝就喝,咋还上头了呢?

  小羊:真香!

  狗妈:这羊犊子,真硬核!

问提背景

  一天早上,楼主兴致勃勃的逛着园子的之前 ,右下角的 QQ 头像嘀嘀嘀的闪了起来,定睛一看,哎我去,肾要之前 开始疼了,全版都是 ,头要之前 开始疼了

  客服 MM:太躺,有个客户充值成功后,赠送的积分不难 到账

  楼主:是全版都是 客户等级存在问题,不满足资格 ?

  客服 MM:客户等级是够的,他之前 的积分都正常到账了

  楼主:之前 的积分都到账了 ? 哪个客户,我去看看

  客服 MM:客户名是:xxx,对应的单号是:xxx,找到原因 了跟我知道你下

  楼主:好的,找到原因 了第一时间通知你

泰坦是楼主在公司内的花名,也是楼主的 LOL 本命英雄,慢慢的被传成太躺了,楼主也很无奈;
有小伙伴问楼主,你和客服 MM 哪些地方关系,光想看

头像闪动就肾疼了 ?

  这俩 问提问得好,改天楼主让你加鸡腿,其实楼主和客服其实挺熟悉的,工作交流挺多的,有之前 仅限于同事关系! 吾乃心系天下之人,岂能被儿女情长所困 ? 只可惜客服 MM 已名花有主,不然就,嘿嘿嘿,这俩 人 懂的(是那姓吾的小子心系天下,楼主不姓吾!)

问提处里

  积分赠送是最近新上的从前功能,上了全版都是 另从前星期了,到目前为止,也就这俩 客户反馈了这俩 问提,另外这俩 客户之前 的积分全版都是 赠送到账了的,应该是触发了这俩 未考虑到的边界条件,产生了异常,原因 积分未写入成功,照理来说,这应该是从前事务,要么都成功,要么全版都是 成功呀

  将会这俩 功能全版都是 楼主开发的,出于快速处里问提的考虑,楼主就找到了对应的开发同事小李,跟我知道你明了下状态,让你去排查下哪些地方原因

  过了一会,小李找到了楼主,之前 开始了他的排查分享

  小李:太躺,我想看 下日志,将会 xxx 状态未考虑到,原因 加积分记录的之前 抛异常了

  楼主:xxx 状态其实比较特殊,一般不难 考虑到,有之前 为哪些地方存款成功了,积分却没加成功,你用了异步不 care 结果的处里 ?

  小李:我是同步处里的,照理来说,应该要回滚的

  楼主:那就奇了怪了,你把写入积分的最好的依据给我下,我去看看代码

  几分钟之前 ,楼主找到了小李,跟我知道你了下缘何改,有之前 让你把边界限制的处里也加带,走紧急流程升到了线上

  问提处里后,小李又找到了楼主

  小李:太躺啊,为哪些地方之前 事务未回滚,而按我知道你的不难 改之前 事务就会回滚了 ?

  楼主:你去把你的椅子拿过来,我跟你好好讲讲!

问提复现

  注意啊,这全版都是 说升级了之前 线上又出现了同样的问提,这俩 楼主为了让这俩 人 更好的了解这俩 问提,模拟下当时的场景

  数据库版本 5.7.21 、存储引擎 InnoDB 、隔离级别 RR 、spring的传播机制 REQUIRED 、声明式事务 @Transactional 

  全版代码:data-init,后边的 TransactionMissTest ,关键代码如下

/**
 * 存款
 * 引入积分之前

的处里
 * @param loginName
 * @param amount
 * @return
 */
@Override
@Transactional(rollbackFor = Exception.class)
public TranMissCredit deposit(String loginName, BigDecimal amount) {
    TranMissCredit credit = creditMapper.getByLoginName(loginName);
    BigDecimal creditAfter = credit.getCredit().add(amount);

    TranMissCreditLog creditLog = new TranMissCreditLog(loginName, credit.getCredit(),
            amount, creditAfter, "充值: " + amount);
    credit.setCredit(creditAfter);

    creditMapper.update(credit);
    int count = creditLogMapper.insert(creditLog);

    return credit;
}

/**
 * 存款
 * 引入积分后的新增的最好的依据
 * @param loginName
 * @param amount
 * @param integration
 * @return
 */
@Override
public TranMissCredit deposit(String loginName, BigDecimal amount, int integration) {
    TranMissCredit credit = deposit(loginName, amount);             // 复用之前

的存款逻辑

    // 下面是新增的积分业务
    int integrationAfter = credit.getIntegration() + integration;
    TranMissIntegrationLog log = new TranMissIntegrationLog(loginName, credit.getIntegration(),
            integration, integrationAfter, "充值赠送积分: " + integration);
    credit.setIntegration(integrationAfter);

    creditMapper.update(credit);
    integrationLogMapper.insert(log);
    return credit;
}


// 调用的地方,最少

Controller
@Autowired
private IDepositService depositService;

@Test
public void deposit() {

    // 积分引入前的调用
    // TranMissCredit credit = depositService.deposit("zhangsan", new BigDecimal(3000));

    // 积分引入后的调用
    TranMissCredit credit = depositService.deposit("zhangsan", new BigDecimal(3000), 10);

}
View Code

  看上去好像没毛病吧,楼主你全版都是 蒙我了把 ? 蒙没蒙你,咱们找焦点访谈

  这俩 人 先看下初始状态,目前可不可不还能否了客户 zhangsan ,其额度 3000 ,积分 10 

  这俩 人 来手动造个异常,模拟边界条件的触发,修改新增的 deposit 最好的依据

  这俩 人 来看看结果

  哟嚯,额度加成功了,积分却没加成功,事务没生效!是全版都是 有点儿懵 ?

问提分析

  这俩 人 仔细观察下 deposit 最好的依据,从前有 @Transactional 修饰,从前不难 ,就这从前差别;虽说可不可不还能否了这从前差别,但 Spring 却在幕后替这俩 人 完成了这俩 事情

  Spring 事务原理

    关于这俩 ,我相信这俩 人 都能答上来这俩 ,底层实现这俩 动态代理(你还谁能谁能告诉我动态代理 ?那还不赶紧去看:设计模式之代理,手动实现动态代理,揭秘原理实现)

    当 Spring 检查到 @Transactional ,会给目标对象创建从前代理对象,有之前 在代理对象中给目标对象中被 @Transactional 修饰的最好的依据织入事务增强处里,例如从前

    将会目标对象中不难 被 @Transactional 修饰的最好的依据,在代理类中是怎样才能的了 ? 既然不难 被 @Transactional ,说明不前要事务增强处里嘛,那就直调呗

    回到这俩 人 的案例,代理对象与被代理对象之间的调用如下

    可不可不还能否 看出来,目标对象新增的最好的依据 TranMissCredit deposit(String loginName, BigDecimal amount, int integration) 在代理对象内是不难 织入事务的,也这俩 默认的自动提交,不难 异常抛出之前 的数据库操作全版都是 自动提交的,不用因后边的异常而回滚

    其实全版都是 事务丢失了,这俩 根本就找不到从前事务中

  再次校验

    不这俩 Spring 事务,这俩 的 AOP 也都一样,代码中直接操作的往往全版都是 目标对象,这俩 目标对象的代理,通过代理对象来间接操作目标对象,而在代理对象中这俩 人 可不可不还能否 做这俩 前置将会后置的增强处里,不信 ? 这俩 人 再次找焦点访谈

    打个断点,看看就知道了

    注入到 TransactionMissTest 的其实是代理对象

    这俩 人 在 TranMissCredit deposit(String loginName, BigDecimal amount) 上打个断点,有之前 五种最好的依据各调用一次,来看看调用链哪些地方地方不一样

    以 depositService.deposit("zhangsan", new BigDecimal(3000)); 最好的依据调用时

    此时调用链中有 事务拦截器,有事务的调用链

    以 depositService.deposit("zhangsan", new BigDecimal(3000), 10) 最好的依据调用时

    此时调用链中不难 事务拦截器,不难 事务的调用链

    是全版都是 很明了了,so easy

总结

  1、正常上线流程

    线上问提 → 问提定位 → 问提复现 → 问提修复 → 转测试 → 测试通过升线上

    而全版都是 像文中说的不难 轻描淡写

  2、事务去哪了

    Spring 事务的底层实现这俩 动态代理,是通过代理的最好的依据对目标对象做前后的增强处里,前置开启事务、后置提交(回滚)事务;

    增强处里在代理对象内,而全版都是 在目标对象内,若目标对象的最好的依据不难 被 @Transactional 修饰,则在代理对象的代理最好的依据内不用有关于事务的增强处里,这俩 直接调用目标对象的最好的依据,不难 后续的数据库操作就全版都是 在从前事务中了

    全版都是 事务消失了,这俩 找不到同从前事务了