1、什么是事务?
事务,就是一组对数据库做一系列操作的过程,要么完全地执行,要么完全地不执行。简单的说,事务就是并发控制的单位,是用户定义的一个操作序列。
2、事务的四大特征(简称ACID)
(1)原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
(2) 一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
(3) 隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
(4)持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
3、事务的隔离级别
(1)读未提交(脏读)
一个事务读取到另一事务未提交的更新数据(会出现脏读, 不可重复读),如:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
(2)读已提交(不可重复读)
在某事务中, 多次读取同一数据返回的结果有所不同, 换句话说, 后续读取可以读到另一事务已提交的更新数据,如:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。(会出现不可重复读和幻读)
@Transactional(isolation = Isolation.READ_COMMITTED)
(3)可重复读(幻读),mysql默认的隔离级别
在同一事务中多次读取数据时, 能够保证所读数据一样, 也就是后续读取不能读到另一事务已提交的更新数据,但能读取到另一个事务已提交的insert数据(幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ)
不可重复读的和幻读很容易混淆,不可重复读侧重于修改(更新数据),幻读(可重复读)侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
(4)可串行化
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它在每个读的数据行上加上共享锁。 可能导致大量的超时现象和锁竞争
@Transactional(isolation = Isolation.SERIALIZABLE)
(5)五种隔离级别
隔离级别 | 意义 |
ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别 |
ISOLATION_READ_UNCOMMITTED | 允许读取未提交的数据(对应未提交读),可能导致脏读、不可重复读、幻读 |
ISOLATION_READ_COMMITTED | 允许在一个事务中读取另一个已经提交的事务中的数据(对应已提交读)。可以避免脏读,但是无法避免不可重复读和幻读 |
ISOLATION_REPEATABLE_READ | 一个事务不可能更新由另一个事务修改但尚未提交(回滚)的数据(对应可重复读)。可以避免脏读和不可重复读,但无法避免幻读 |
ISOLATION_SERIALIZABLE | 这种隔离级别是所有的事务都在一个执行队列中,依次顺序执行,而不是并行(对应可序列化)。可以避免脏读、不可重复读、幻读。但是这种隔离级别效率很低,因此,除非必须,否则不建议使用。 |
注意:
1、事务隔离级别为读未提交时,更新数据只会锁住相应的行
2、事务隔离级别为可重复读时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key 锁;如果检索条件没有索引,插入数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。
3、事务隔离级别为串行化时,读写数据都会锁住整张表
4、隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
4、事务的传播行为
传播行为 | 意义 |
PROPAGATION_REQUIRED | 支持当前事务,如果当前没有事务,就新建一个事务。也就是说业务方法需要在一个事务中运行,如果业务方法被调用时,调用业务方法的行为(方法)已经处在一个事务中,那么就加入到该事务,否则为自己创建一个新的事务。(默认传播属性) |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。也就是说如果业务方法在某个事务范围内被调用,则该方法成为该事务的一部分。如果业务方法在事务范围外被调用,则该方法在没有事务的环境下执行。 |
PROPAGATION_MANDATOR | 支持当前事务,如果当前没有事务,就抛出异常。也就是说业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在没有事务的环境下被调用,容器就会抛出例外。 |
PROPAGATION_REQUIRESNEW | 新建事务,如果当前存在事务,把当前事务挂起。也就是说业务方法被调用时,不管是否已经存在事务,业务方法总会为自己发起一个新的事务。如果调用业务方法的行为(方法)已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到业务方法执行结束,新事务才算结束,原先的事务才会恢复执行。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行,如果当前存在事务,就把当前事务挂起。也就是说业务方法不需要事务。如果方法没有被关联到一个事务中,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。也就是说业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会抛出例外,只有业务方法没有关联到任何事务,才能正常执行。 |
PROPAGATION_NESTED | 如果一个活动的事务存在,则运行在一个嵌套的事务中。 如果没有活动事务, 则按REQUIRED属性执行。它使用了一个单独的事务, 这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。 |
5、事务的生效条件
(1) @Transactional 只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能
(2)Spring团队的建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。你当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是 不能继承 的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因 此,请接受Spring团队的建议并且在具体的类上使用 @Transactional 注解。
(3)@Transactional注释的方法,必须是有接口的方法实现(通用的Spring面向接口编程的套路),即:通过该接口来调用方法才确保事务生效(或@Transactional注释放到类上)
(4)要想事务起作用,必须是主方法名上有@Transactional注解,方法体内不能用try-catch;如果用try-catch,则catch中必须用throw new RuntimeException()
(5)类内部方法调用内部的其他方法,被调用的方法体中如果有try-catch,则catch中必须用throw new RuntimeException(),否则即使主方法上加上@Transactional注解,如果被调用的子方法出错也不会抛出异常,不会引起事务起作用。