jdbc postgresql 遇到一个问题,已有方法:saveAll findById 两个方法执行都是在一个单独的事务里的,测试代码如下:
先saveAll 返回一组ID,然后用这一组ID并行执行findById,偶尔会出现返回null。即,saveAll事务已经提交的数据,结果偶尔查不到。
saveAll 返回一组ID
这一组ID.parallelStream.forEach(id -> {
// 这行findById 偶尔返回null
findById(id);
});
saveAll 事务提交了,然后根据已插入的来并行查询,结果偶尔查不到。
问了chatgpt说,并行情况下,有可能虽然代码逻辑中findById是在saveAll执行结束后才执行的,但可能在到了数据库里,由于findById是并行执行的,所以可能findById比saveAll的提交事件更早执行,所以和代码表述的业务逻辑的预期不一致查不到数据。
如果是串行的,则findById一定是在saveAll提交后才会执行,并且我修改上面的测试代码为list.stream 改为非并行测试,结果是每次都可以查到数据。
我在照着spring data jpa功能写一个类似功能的框架,过程中遇到了7 8 个问题了,都是同样的代码mysql和sqlite测试没问题,pgsql就报错,比如前几天刚遇到的 在AOP类里根据自定义事务注解的隔离级别来更改java.sql.Connection的隔离级别,pgsql就报错[不能在事务交易过程中更改隔离级别]。
那么,这个并行查询查不到的问题,是否也因为pgsql的严格要求而出现的?同样的上面的测代码,mysql就没问题每次都测试通过。
我知道spring的事务注解就是在当前线程下,即目标方法代码都是在当前线程下单线程执行才有效的。而并行查询,比如16个线程,配置连接池10个连接,可能saveAll执行后,16个线程中有一个使用和saveAll相同的连接来执行findById,那么这个线程就可以查到,因为它相对于saveAll虽然不是用的同一个线程来执行,但是用的同一个java.sql.Connection连接对象,所以相当于和saveAll是串行执行的。而对于其他15个线程去抢另外9个连接,这9个连接执行的findById可能在执行saveAll的连接的事务还没commit时就先执行了,所以导致查不出数据。对吗?
这似乎和pgsql的事务隔离级别没关系,尽管这段测试代码是一个saveAll事务和N个findById事务,它默认为读已提交,我改为其他3种级别,这段测试代码仍然偶尔返回null。那就只能按上面描述那么理解:saveAll的commit事件还没执行,findById就已经执行了,导致查不到数据,对吗?但对于 READ UNCOMMITTED 又该怎么理解呢,它根本就没有[commit]这个概念对吗?
目前想到的解决方法有三个:
1、不用并行查询来测试。但现实业务逻辑中都是并行的,不可能让所有查询排队执行。但是否可以忍受很短时间的[查不到]?即数据库存在数据,但是查不到数据的这段时间非常非常短。
2、连接池个数配置为1,看似解决了问题,实际上是让所有的CRUD操作都排队用这唯一的连接来串行执行。这肯定不行
3、同样在内部代码处理为同一个TABLE的查询排队执行,比2好不了多少