1、当一个应用程序或者不同应用程序中的多个事务在同一个数据集上并发执行时,可能会出现许多意外的问题。必须指明希望事务间如何隔离。
2、并发事务所导致的问题可以分为下面3种类型
(1)脏读:对于两个事务T1和T2,T1读取了一个已经被T2更新但是还没有被提交的字段。之后,如果T2回滚,T1读取的内容就是临时且无效的。
(2)不可重复读:对于两个事务T1和T2,T1读取了一个字段,然后T2更新了该字段。之后,T1再次读取同一个字段,值就不一样了。
(3)幻读:对于两个事务T1和T2,T1从一个表中读取某几行,然后T2在该表中插入一些新的行。之后,如果T1再次读取同一个表,就会多出几行。
3、从理论上来说,事务应该彼此完全隔离(如按序列顺序访问),以避免上述的所有问题。然而,这种隔离级别会对性能产生巨大的影响,因为事务必须按顺序运行。在实践中,为了提升性能,事务会以较低的隔离级别运行。
4、事务的隔离级别可以通过隔离事务属性指定。Spring支持5中隔离级别,这些隔离级别是在org.springframework.transaction.TransactionDefinition接口中定义的。
DEFAULT:使用底层数据库的默认隔离级别。对于大多数数据库来说,默认隔离级别都杀死READ_COMMIT。
READ_UNCOMMITED:允许事务读取未被其他事务提交的变更。脏读、不可重复读和幻读问题都可能出现。
READ_COMMITED:只允许事务读取已经被其他事务提交的变更。可以避免脏读问题,但是不可重复读和幻读问题任然可能出现。
REPEATABLE_READ:确保事务可以多次从一个字段中读取相同的值。在这个事务持续期间,禁止其他事务对这个字段进行更新。可以避免脏读和不可重复读问题,但是幻读问题任然可能出现。
SERIALIZABLE:确保事务可以多次从一个表中读取相同的行。在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作。所有并发性问题都可以避免,但是性能十分低下。
Oracle 默认使用的是READ COMMITTED ,MySQL默认事务隔离级别是Repeatable Read
5、
@Transactional(isolation=Isolation.READ_UNCOMMITED)
public void update(...)
@Transactional
public void select(...)
update(...);//更新了,但未提交
Tread.sleep(5000);
select(...)
//update方法又回滚
在上面的代码中,由于update方法的事务隔离级别设置的是READ_UNCOMMITED,所以update为提交的更新,select方法也能读取到,就会出现脏读。
将update方法的隔离级别改为:
@Transactional(isolation=Isolation.READ_COMMITED)
public void update(...)
再次运行代码,select方法必须等待update回滚后才能读取数据库。
为了让底层的数据库可以支持READ_COMMITED隔离级别,它会在已经更新但没有提交的行 上获得更新锁(update lock)。然后其他事务必须等待更新锁被释放后才能读取该行,释放发生在锁定的事务提交或者回滚的时候。
@Transactional(isolation=Isolation.READ_UNCOMMITED)
public void update(...)
@Transactional(isolation=Isolation.READ_UNCOMMITED)
public void select(...)
select(...)//读取了某字段,但未提交
update(...);//更新了,且提交
Tread.sleep(5000);
select(...)//再次读取,值就不一样了
在READ_COMMITED隔离级别中,另一个事务能够更新已经被未提交事务读取过的值 。
@Transactional(isolation=Isolation.REPEATABLE_READ)
public void select(...)
为了让底层的数据库可以支持REPEATABLE_READ隔离级别,它在被读取但还没有被提交的行 上获得读取锁(read lock)。然后其他事务必须等到读取锁被释放后才能更新该行,释放发生在锁定的事务提交或者回滚的时候。