前言
几年前IT信息产业的一些核心技术包括架构、产品以及生态都是国外制定,然而自从“遥遥领先”公司被制裁后,国家开始大力支持信息产业“新基建”,自2020年开始市场上涌现出了大量的国产化软件,就国产化数据库而言我所在的公司有两款国产化数据库,一款是基于mysql,另一款则是基于postgreSQL,相信市面上大多国产化数据库也是基于这两款开源数据库演化而来。一般使用国产化数据库的项目大多是一些政企项目或者公司自己的核心项目,这些项目原先也大多使用的是oracle数据库,那么今天就来聊聊把项目从oracle迁移到基于PostgreSql的国产化数据库(下文将直接使用postgreSql替代基于postgresql的国产化数据库)上遇到的一个问题。
一、系统事务管理
事务管理是任何系统都应该具备的核心功能,然而一些java开发者包括我在内在写代码时往往会忽略这个重要的东西,这并不是我不了解事务管理的重要性,其最大的原因就是日常使用的框架都提供了很完善的事务管理机制,不需要开发者针对事务上写大量代码,比如spring,spring提供的事务管理有两种一种是声明式,另一种是编程式,目前我个人使用最多的就是声明式事务了。然而声明式事务我个人认为有个缺点,就是事务控制的粒度太大,因为声明式事务时基于注解实现的,所以最小粒度为方法级别。
二、问题的暴露
工作中维护老代码时,发现祖传写法都是下图这个样子,在service类上添加事务注解,方法上和方法调用的子方法并不做事务控制,这个例子中AutomaticRechargeInitiationProcess类中还是很多逻辑,换句话说这个程序中只要出现了一点问题,那么事务就要全部进行回滚。
于是代码维护者们发现有些程序即使错误了也不应该进行回滚,比如记录一些程序处理过程等或者错误是在预料之中的。于是又在AutomaticRechargeInitiationProcess代码中添加了类似下图的代码。
类似这种代码其实在一些大型老旧项目中是很常见的,原因是项目迭代时间太久,经手的开发人员技术水平参差不齐,比如我目前维护的项目截至2024年一月已经整整12个年头了,经手的开发人员没有一百也有八十了,代码的规范性已经被破坏,代码的维护变得困难。而另一种情况是,随着时间的发展,业务也在演进,比如从2020年新增了一种业务,需要新增表,正确的做法是程序中根据时间节点判断是否查询新表,然而总有大聪明不按套路出牌,他的逆向思维促使他程序中不做任何判断,反而新建了若干张历史月份表。后续开发者可能发现了隐患,于是在程序中使用try catch的方式将查询新表的代码进行处理。这样看似完美,实际运行倒也未发生什么问题。
问题的暴露发生在了今年的国产化替代项目上,项目在功能测试阶段由于都是使用的近几月的数据进行的自动化回归验证,所以并没有发现问题,当上到生产上时,发现业务在查询比较久远的历史数据时,前台直接报错了,查看日志发现报错几乎全部为“current transaction is aborted, commands ignored until end of transaction block”,根据日志可以发现是数据库驱动抛出的异常,日志还打了某张表不存在。异常如下:
既然找到了日志那问题的定位也就简单了,原因就是因为表不存在触发了postgreSql的事务机制,导致了继续使用出现过异常的事务进行数据库操作就会报出上图异常,那么问题来了,原来的oracle为什么不会出现问题?
PostgreSql和Oracle的事务机制区别
咨询了公司大佬,给出的答复是:Oracle具有子事务功能,Oracle没有遵循SQL规范,只做了部分回滚;postgreSql遵循SQL规范,一个事务的多条SQL要么全部成功要么全部失败。
自己查询了资料发现:
orale的子事务是指一个大事务可以包含多个小事务,而每个小事务可以单独提交或者回滚,同时也支持跟随父事务进行提交或回滚。其实现原理为嵌套事务结合保存点来实现的。
postgresql也支持“子事务”,但是pg的子事务和oracle不同,他不具备独独立提交和回滚的能力,必须依赖大事务,但是这个功能也需要在jdbc连接串中配置autosave=always&cleanupSavepoints=true来开启。但是这种方式存在风险,如果一个事务中存在多个DML语句,这样失败的回滚,成功的提交,会造成业务数据不一致,使用时需要谨慎。
官方对两个参数的解释如下:
以上便是我在去O实践中遇到的一个问题。