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

  • 时间:
  • 浏览:1

开心一刻

  小羊:哎呀,前面有奶喝

  狗妈:这谁呀,走开

  小羊:愿意喝点,能为社 么的嘛

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

  小羊:真香!

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

问题报告 背景

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

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

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

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

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

  客服 MM:客户名是:xxx,对应的单号是:xxx,找到原困了跟你爱不爱我下

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

泰坦是楼主在公司内的花名,也是楼主的 LOL 本命英雄,慢慢的被传成太躺了,楼主也很无奈;
有小伙伴问楼主,你和客服 MM 哪些关系,光看完头像闪动就肾疼了 ?

  你这个 问题报告 问得好,改天楼主愿意加鸡腿,嘴笨 楼主和客服嘴笨 挺熟悉的,工作交流挺多的,为社 让仅限于同事关系! 吾乃心系天下之人,岂能被儿女情长所困 ? 只可惜客服 MM 已名花有主,不然就,嘿嘿嘿,我们歌词 我们歌词 我们歌词 懂的(是那姓吾的小子心系天下,楼主不姓吾!)

问题报告 外理

  积分赠送是最近新上的有另2个 功能,上了都不 有另另2个 星期了,到目前为止,也就你这个 客户反馈了你这个 问题报告 ,另外你这个 客户以前的积分都不 赠送到账了的,应该是触发了或多或少未考虑到的边界条件,产生了异常,原困积分未写入成功,照理来说,这应该是有另2个 事务,要么都成功,要么都不 成功呀

  不可能 你这个 功能都不 楼主开发的,出于快速外理问题报告 的考虑,楼主就找到了对应的开发同事小李,跟你爱不爱我明了下情況,愿意去排查下哪些原困

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

  小李:太躺,我看完下日志,不可能 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(100));

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

}
View Code

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

  我们歌词 我们歌词 我们歌词 先看下初始情況,目前只能客户 zhangsan ,其额度 100 ,积分 10 

  我们歌词 我们歌词 我们歌词 来手动造个异常,模拟边界条件的触发,修改新增的 deposit 最好的法律法律依据

  我们歌词 我们歌词 我们歌词 来看看结果

  哟嚯,额度加成功了,积分却没加成功,事务没生效!是都不 很糙懵 ?

问题报告 分析

  我们歌词 我们歌词 我们歌词 仔细观察下 deposit 最好的法律法律依据,有另2个 有 @Transactional 修饰,有另2个 那末,就这有另2个 差别;虽说只能这有另2个 差别,但 Spring 却在幕后替我们歌词 我们歌词 我们歌词 完成了或多或少事情

  Spring 事务原理

    关于你这个 ,我相信我们歌词 我们歌词 我们歌词 都能答上来或多或少,底层实现或多或少动态代理(你还不知道动态代理 ?那还不赶紧去看:设计模式之代理,手动实现动态代理,揭秘原理实现)

    当 Spring 检查到 @Transactional ,会给目标对象创建有另2个 代理对象,为社 让在代理对象中给目标对象中被 @Transactional 修饰的最好的法律法律依据织入事务增强外理,这个 从前

    不可能 目标对象中那末被 @Transactional 修饰的最好的法律法律依据,在代理类中是何如的了 ? 既然那末被 @Transactional ,说明不时要事务增强外理嘛,那就直调呗

    回到我们歌词 我们歌词 我们歌词 的案例,代理对象与被代理对象之间的调用如下

    可不也能 看出来,目标对象新增的最好的法律法律依据 TranMissCredit deposit(String loginName, BigDecimal amount, int integration) 在代理对象内是那末织入事务的,也或多或少默认的自动提交,那末异常抛出以前的数据库操作都不 自动提交的,不不因上端的异常而回滚

    嘴笨 都不 事务丢失了,或多或少根本就找不到有另2个 事务中

  再次校验

    不或多或少 Spring 事务,或多或少的 AOP 也都一样,代码中直接操作的往往都不 目标对象,或多或少目标对象的代理,通过代理对象来间接操作目标对象,而在代理对象中我们歌词 我们歌词 我们歌词 可不也能 做或多或少前置不可能 后置的增强外理,不信 ? 我们歌词 我们歌词 我们歌词 再次找焦点访谈

    打个断点,看看就知道了

    注入到 TransactionMissTest 的嘴笨 是代理对象

    我们歌词 我们歌词 我们歌词 在 TranMissCredit deposit(String loginName, BigDecimal amount) 上打个断点,为社 愿意这个 最好的法律法律依据各调用一次,来看看调用链哪些不一样

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

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

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

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

    是都不 很明了了,so easy

总结

  1、正常上线流程

    线上问题报告 → 问题报告 定位 → 问题报告 复现 → 问题报告 修复 → 转测试 → 测试通过升线上

    而都不 像文中说的那末轻描淡写

  2、事务去哪了

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

    增强外理在代理对象内,而都不 在目标对象内,若目标对象的最好的法律法律依据那末被 @Transactional 修饰,则在代理对象的代理最好的法律法律依据内不不有关于事务的增强外理,或多或少直接调用目标对象的最好的法律法律依据,那末后续的数据库操作就都不 在有另2个 事务中了

    都不 事务消失了,或多或少找不到同有另2个 事务了