当前位置: 首页> 游戏> 手游 > 广告网站建设及推广_施工企业突发环境事件应急预案_免费发布广告_系统开发

广告网站建设及推广_施工企业突发环境事件应急预案_免费发布广告_系统开发

时间:2025/7/9 12:26:49来源:https://blog.csdn.net/qq_37221991/article/details/145504195 浏览次数:0次
广告网站建设及推广_施工企业突发环境事件应急预案_免费发布广告_系统开发

最近我们在排查一个诡异的 空指针异常,整个分析过程可以说是跌宕起伏,最终的结论也颇具隐蔽性。今天就把这个问题分享出来,希望对大家有所帮助。

问题现象

在系统中,我们有 单据 B,它通过一个 关联 ID 字段与 上级单据 A 关联。

但在某次操作中,我们发现:

  1. 单据 B 存在,并且存储了 A 的 ID
  2. 但查询 A 时,却查不到数据,导致后续代码调用 A 的 get 方法时报空指针异常

理论上,B 既然存储了 A 的 ID,A 就应该存在,否则 A 的 ID 是怎么来的?

难道 A 被删除了?
然而,代码中并没有删除 A 的逻辑,而且 DBA 查询了数据库日志,确认 A 从未被删除。那么,这就只剩下一种可能:A 从未生成

代码分析

我们回溯代码,A 和 B 是在同一个事务内生成的,具体逻辑如下:

@Transactional
public void createA() {DB生成单据A;执行业务方法C;DB生成单据B;
}

代码逻辑很简单:

  1. 第一步 生成 A。
  2. 第二步 执行 业务方法 C
  3. 第三步 生成 B,并存储 A 的 ID。

由于 B 存储了 A 的 ID,说明 DB生成单据A 代码应该成功执行了。但为什么 A 最终没有出现在数据库中?

难道是 createA() 过程中发生了异常,导致 A 没有生成?
我们查询当时的日志,发现 业务方法 C 在执行时发生了 死锁异常

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

业务方法 C 的代码如下:

@Transactional
public void doC() {try {DB生成C;} catch (Exception e) {输出异常日志;}
}

DB生成C 这一步时,数据库发生了死锁异常,但代码使用了 try-catch,所以理论上不会影响事务的执行,A 和 C 都应该正常生成。

那么问题来了,A 为什么消失了?

问题的根本原因:MySQL 隐式回滚

最终,DBA 通过查询数据库的日志,发现了问题的真正原因——MySQL 发生了“隐式回滚”

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

MySQL 死锁处理机制
在 MySQL 中,当多个事务发生死锁时,数据库会自动选择一个代价较低的事务进行回滚,以解除死锁。这一行为是 数据库层面的自动回滚不会受到 try-catch 代码的影响

在本例中,事务执行时发生了死锁,MySQL 自动回滚了整个事务 createA(),导致 A 被回滚,实际上根本没被写入数据库。

但由于 doC() 代码中使用了 try-catch,异常并没有往上抛,导致事务继续执行到了 DB生成单据B;。由于 B 在一个新的事务中生成,它最终成功入库,并存储了 已被回滚的 A 的 ID,从而导致数据不一致的问题。

完整过程如下:

  1. DB生成单据A;执行成功(暂时)
  2. DB生成C;发生死锁,MySQL 选择回滚事务 createA(),A 被回滚
  3. 由于 try-catch 捕获了异常,事务继续执行
  4. DB生成单据B;B 在新的事务中成功插入,并存储了已回滚的 A 的 ID

最终,导致 B 关联了一个不存在的 A,后续调用 A 的 get 方法时报空指针异常

如何避免类似问题?

通过这次分析,我们可以总结出几点避免类似问题的经验:

  1. 避免在事务中吞掉异常
  • try-catch 不能仅仅记录日志,如果异常影响了事务的完整性,应该显式回滚整个事务

  • 改进 doC()方法:

    @Transactional
    public void doC() {try {DB生成C;} catch (Exception e) {log.error("生成 C 失败", e);throw e; // 让事务感知异常,避免错误继续执行}
    }
  1. 尽量控制事务粒度,避免长时间持有锁
  • 业务方法 C 的执行时间过长,可能加剧死锁风险。

  • 可以考虑将 业务方法 C 放到事务外部执行,避免影响 AB 的创建:

    @Transactional
    public void createA() {DB生成单据A;DB生成单据B;
    }public void doC() {DB生成C;  // 独立事务,避免影响 A、B
    }
  1. 调整执行顺序,将c方法挪至最后

    @Transactional
    public void createA() {DB生成单据A;DB生成单据B;执行业务方法C;
    }

因为B方法的trycatch逻辑因为业务原因没法改,所以我这边采用了3的方法,并且同时优化了B方法,降低了死锁发生的概率。希望这次排查经历,能给大家一些启发!🚀🚀🚀

关键字:广告网站建设及推广_施工企业突发环境事件应急预案_免费发布广告_系统开发

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: