真正的掌握应该是建立在行动的基础之上的,正所谓光说不做假把式,以下便谈谈对事务调试的心得。
1、问题背景
uat测试环境出现单元格1+单元格2=单元格3,单元格1没数据,单元格2没数据,但是单元格3却有数据,后台查代码逻辑发现有一个update开头的service方法调用了get开头的service方法,重要的是这两个service方法在xml配置文件aop配置的时候,配置了两个切面,被分别配置到了不同的切面上,所以猜想可能的原因是执行update开头的service方法时插入数据的时候是一个时候,调用get开头的service方法时重新创建了一个事务,由于事务的隔离性,所有导致两个事务的数据不一致,将get开头的切面注释,正常显示,进一步验证了是事务引起的原因。
2、问题分析
在本地开发环境分别在两个aop所配置的service下面,分别创建了一个测试的service类,模拟以上发生的场景,写一个方法调用update开头的service方法,然后在这个方法里面再调用另一个update开头的service方法,测试发现,update方法里插入一条数据,另一个切面的update方法执行查询可以查到数据,矛盾点就出现了,那么测试环境的是什么原因呢?
接下来的是先去百度查看了spring的操作手册,aop事务有组合切点的配置方式,分别为and,or,!,跟逻辑运算符的意思一样,如果配了and,则需要满足两则才会拦截方法进行事务的控制,or则是有一个满足就会进行事务的控制,这个方式似乎跟配置成多个切面的方式是一样的,!则是相反的逻辑判断。那么由此分析问题似乎不在组合配置这边。
接下来就又去百度查找了事务的知识,事务有四个特性,分别是原子性,一致性,隔离性,永久性,oracle数据库事务隔离级别有三种,分别为1、幻想读(即两个并行的事务,一个事务先查询没有数据,另一个事务插入了一条数据并提交,再用之前的事务查询,发现返回的数据结果集多了一条数据,产生了幻读的现象);2、不可重复读(两个并行的事务,一个事务先查询数据,一个事务修改了某条数据,再用这个事务查询,发现数据不一致,产生了不可重复读现象);3、脏读(指两个并行事务,一个事务插入一条数据但未提交,另一个事务能读取到未提交的事务)。分析猜想本地现象与测试环境不一致的原因有可能是数据库设置的隔离级别不一致,四种事务隔离级别如下:
(1)READ UNCOMMITTED 幻想读、不可重复读和脏读都允许;
(2)READ COMMITTED 允许幻想读、不可重复读,不允许脏读;
(3)REPEATABLE READ 允许幻想读,不允许不可重复读和脏读;
(4)SERIALIZABLE 幻想读、不可重复读和脏读都不允许。
过set transaction isolation level [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]发现oracle数据库只能设置(2)和(4),由此可否定未提交的事务出现脏读的现象,本地测试就算把它设置成(2),还是可以读取到未提交事务的数据。
接下来只能继续调试代码了,用了好几种方式后,又去研究了事务的传播特性,发现事务有七个特性分别为(1)PROPAGATION_MANDATORY(2)PROPAGATION_REQUIRED(3)PROPAGATION_NEVER(4)PROPAGATION_NOT_SUPPORT(5)PROPAGATION_SUPPORTS
(6)PROPAGATION_REQUIRES_NEW(7)PROPAGATION_REQUIRES;
发现配置文件中update*的事务传播特性是配置成REQUIRED,get*是配置成NOT_SUPPORT,update配置的意思是如果当前有事务则加入到当前事务,否则创建一个事务。NOT_SUPPORT的意思是如果有事务则会将当前事务挂起,去执行无事务的操作。由此可以猜想上述问题的原因很有可能是update先执行方法后,创建了一个事务,当前事务插入了未提交的数据,get方法执行时又将数据挂起查询,导致数据查询不到,本地测试果真如此,也就能解释本地和测试环境的问题了。
3、解决方案
由于get查询方法是不需要事务的,但存在一种场景也就是某个事务先insert一条语句,这个方法又需要去查询这条未提交的数据,那么这个get查询方法如果不用事务则就会产生上述数据不一致的问题,查询传播特性发现SUPPORTS可满足要求,如果当前没有事务,则直接无事务查询,否则直接用当前事务查询,则不会有数据不一致的问题。