实际工作中还会面临千奇百怪的问题,看下面返个例子(注意MySql
数据库测试):
//1.hello1Service 调用 hello2Service
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void doUpdate() {//执行的sql:update demo_user set name = concat(name ,'_1') where logonid = 1 hello2Service.doUpdate();
}
//2.hello2Service
@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
public void doNewUpdate() throws Exception {// 执 行 的 sql 语 句 : update demo_user set name =concat(name ,'_2') where logonid = 2userDao.doUpdate2(); // 异 常,Lock waittimeout exceeded; try restarting transaction
这个是怎么回亊,两个sql
语句没有修改同一行数据?怎么一个数据提交,另一个出现莫名其妙的错误?
此外框架还支持doNest*
主子亊务,那么对主子亊务要怎么理解呢?
下面就和大家交流下我对这些方面的内容总结。
事务的传播定义
下面列举了各公司框架使用到的亊务传播部分说明,还有些不常用传播行为,因为实际使用的少,大家在网上了解下就行了。
传播行为 | 意义 |
---|---|
PROPAGATION_REQUIRED | 表示当前方法必须运行在一个事务中,如果当前存在一个事务,那么该方法运行在这个事务中,否则,将创建一个新的事务 |
PROPAGATION_REQUIRES_NEW | 新建事务,表示当前方法必须运行在自己的事务中,如果当前存在一个事务,那么这个事务将在该方法运行期间被挂起 |
PROPAGATION_NESTED | 表示如果当前事务存在,则方法应该运行在一个嵌套事务中。否则,它看起来和PROPAGATION_REQUIRED 看起来没什么俩样 |
主子事务存在嵌套行为,嵌套是子事务套在父事务的一部分,在进入事务之前,父事务建立一个回滚点,叫save point
,然后执行子亊务,这个子亊务的执行也算是父亊务的一部分,然后子亊务执行结束,父亊务继续执行。重点就在二那个save point
。下面癿几个问题加深下大家的理解,对二嵌套亊务问题说明:
【1】如果子亊务回滚,会发生什么? 父亊务会回滚到进入子亊务前建立的save point
,然后尝试其它的亊务或者其他的业务逻辑,父事务之前的操作不会受到影响,更不会自动回滚。
【2】如果父亊务回滚,会収生什么? 父亊务回滚,子亊务也会跟着回滚!为什么呢,因为父亊务结束之前,子亊务是不会提交的,我们说子亊务是父亊务的一部分,正是这个道理。
【3】亊务癿提交癿顺序什么? 父亊务先提交,然后子亊务提交,还是子亊务先提交,父亊务再提交?还是那句话,子亊务是父亊务的一部分,由父亊务统一提交。
事务的影响因子
【1】亊务的拦截边界: Spring
亊务默认情况下都是在抛出unchecked exception
后才会触发亊务的回滚
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void doUpdate() throws Exception {userDao.doUpdate();//数据会提交throw new Exception("11");
}
Exception
是checked exception
,上述例子虽然抛出了异常,但数据仍然提交成功。
@Transactional(propagation = Propaga-tion.REQUIRED,rollbackFor = Exception.class)
@Override
public void doUpdate() throws Exception {userDao.doUpdate();//数据会回滚throw new Exception("11");
}
rollbackFor = Exception.class
指定了亊务的拦截边界,但是大家思考下,如果抛出的是Throwable
类型呢?也是一样会提交亊务。公司框架定义的异常捕获边界也都是Exception
的,这点一定要注意。
【2】如果是边界内癿异常,但是被捕获了呢?
@Transactional(propagation = Propaga-tion.REQUIRED,rollbackFor = Exception.class)
@Override
public void doUpdate() {try {userDao.doUpdate(); //数据会提交throw new RuntimeException("roll");} catch (Exception e) {e.printStackTrace();}
}
@Transactional(propagation = Propaga-tion.REQUIRES_NEW,rollbackFor = Excep-tion.class)
@Override
public void doNewUpdate() {try {userDao.doUpdate(); //数据会提交throw new RuntimeException("roll");} catch (Exception e) {e.printStackTrace();}
}
上例两种不同的传播行为,最织结果数据都提交了。
spring
的亊务边界是在调用业务方法之前开始,并在你的业务方法中没有catch
到的话,亊务会回滚。
但是如果被catch
的话,数据依然能够提交。一般不需要在业务方法中catch
异常,如果非要catch
,在做完你想做的工作后(比如关闭流操作等)一定要抛出exception
,否则spring
会将你的操作commit
,这样就会产生脏数据。
再回到开篇的第一个例子,原来我们发现数据库亊务本质上使用数据库锁,开启spring
亊务意味着使用数据库锁。
这把锁定义的边界在数据库局面我们可以理解为数据库的隔离级别。
那么Spring
对数据库隔离级别的支持如何?
Spring
框架对数据库隔离级别提供了一定的支持。Spring
事务管理器可以配置和控制数据库事务的隔离级别。通过Spring
的事务管理器,您可以将隔离级别设置为读未提交READ_UNCOMMITTED
、读已提交READ_COMMITTED
、可重复读REPEATABLE_READ
或串行化SERIALIZABLE
。这样可以确保在并发访问数据库时,事务之间的隔离性得到维护。但具体的实现细节还是依赖于您所使用的具体数据库和驱动程序。
数据库的隔离级别有哪几种?
【1】读未提交(Read Uncommitted): 最低级别的隔离级别,允许一个事务读取另一个事务尚未提交的数据。这种隔离级别可能导致脏读(Dirty Read)问题。
【2】读已提交(Read Committed): 在一个事务读取数据时,只能读取已经提交的数据。这种隔离级别可以避免脏读,但可能会导致不可重复读Non-Repeatable Read
问题。
【3】可重复读(Repeatable Read): 在一个事务读取数据时,保证多次读取同一数据时,读取的结果保持一致。这种隔离级别可以避免脏读和不可重复读,但可能会导致幻读Phantom Read
问题。
【4】串行化(Serializable): 最高级别的隔离级别,通过对事务进行串行化执行,避免了脏读、不可重复读和幻读的问题。但这种隔离级别可能会导致并发性能下降。
这些隔离级别的选择取决于应用程序的需求和对数据一致性的要求。不同的数据库管理系统可能对隔离级别的实现有所不同。
什么是脏读、不可重复读、幻读?
脏读、不可重复读和幻读是数据库中的三种并发问题。
【1】脏读(Dirty Read): 一个事务读取到了另一个事务未提交的数据。如果另一个事务最终回滚,则当前事务读取到的数据是无效的。
【2】不可重复读(Non-Repeatable Read): 一个事务在读取数据时,由于其他事务的修改,导致多次读取同一数据时,读取的结果不一致。例如,一个事务在读取某个数据时,另一个事务修改了该数据并提交了事务,导致第一个事务读取到的数据与之前不同。
【3】幻读(Phantom Read): 一个事务在读取数据时,由于其他事务的插入或删除操作,导致多次读取同一范围的数据时,读取的结果不一致。例如,一个事务在读取某个表中的所有数据时,另一个事务插入了一条新的数据,导致第一个事务读取到的数据比之前多了一条。
这些并发问题的出现是由于多个事务同时访问数据库中的数据,而不同的隔离级别可以用来控制这些问题的出现。在选择隔离级别时,需要权衡数据一致性和并发性能。
Mysql
或者Oracle
默认的隔离级别是什么,如何查询?
MySQL
和Oracle
数据库的默认隔离级别是不同的。对于MySQL
,默认的隔离级别是可重复读Repeatable Read
。您可以使用以下命令查询当前MySQL数据库的隔离级别:
SELECT @@tx_isolation;
返回的结果应该是REPEATABLE-READ
。
对于Oracle
,默认的隔离级别是读已提交Read Committed
。您可以使用以下命令查询当前Oracle
数据库的隔离级别:
SELECT * FROM v$transaction WHERE addr = (SELECT taddr FROM v$session WHERE sid = SYS_CONTEXT('USERENV', 'SID'));
返回的结果中的ISOLATION_LEVEL
列应该是READ COMMITTED
。
请注意,这些查询语句可能需要在具有足够权限的用户下执行。
如果开篇例子的操作换成ORACLE数据库会出现同样的错吗?
建议大家动手操作理解,网上的那些概念背是没用的。